From 94fcdae7f819e07df8b47b0717fb54479e66d5d4 Mon Sep 17 00:00:00 2001 From: Julian Raphael Jautz <julian.jautz@student.tugraz.at> Date: Mon, 12 Feb 2024 17:29:55 +0100 Subject: [PATCH] PAINTROID_684_apply_outline_to_text TextTool feature: text with outline -added new button for outline -outline created by painting twice (second time in stroke mode) --- .../espresso/tools/TextToolIntegrationTest.kt | 148 +++++++++++- .../serialization/CommandSerializationTest.kt | 4 +- .../command/implementation/TextToolCommand.kt | 22 +- .../serialization/SerializableTypeface.kt | 6 +- .../tools/implementation/TextTool.kt | 58 ++++- .../tools/options/TextToolOptionsView.kt | 8 +- .../ui/tools/DefaultTextToolOptionsView.kt | 75 +++++- .../ic_pocketpaint_a_blue_outline.xml | 7 + .../ic_pocketpaint_a_white_outline.xml | 7 + .../res/drawable/outline_button_selector.xml | 4 + .../layout/dialog_pocketpaint_text_tool.xml | 215 ++++++++++++------ Paintroid/src/main/res/values/string.xml | 1 + 12 files changed, 472 insertions(+), 83 deletions(-) create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml create mode 100644 Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml create mode 100644 Paintroid/src/main/res/drawable/outline_button_selector.xml diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt index f17d6d50b0..3d0473fd3c 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/tools/TextToolIntegrationTest.kt @@ -27,7 +27,9 @@ import android.graphics.Paint import android.graphics.PointF import android.graphics.Typeface import android.widget.EditText +import android.widget.RelativeLayout import androidx.core.content.res.ResourcesCompat +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onView @@ -61,6 +63,7 @@ import org.catrobat.paintroid.tools.FontType import org.catrobat.paintroid.tools.ToolReference import org.catrobat.paintroid.tools.ToolType import org.catrobat.paintroid.tools.implementation.BOX_OFFSET +import org.catrobat.paintroid.tools.implementation.DEFAULT_TEXT_OUTLINE_WIDTH import org.catrobat.paintroid.tools.implementation.MARGIN_TOP import org.catrobat.paintroid.tools.implementation.TEXT_SIZE_MAGNIFICATION_FACTOR import org.catrobat.paintroid.tools.implementation.TextTool @@ -90,6 +93,8 @@ class TextToolIntegrationTest { private var underlinedToggleButton: MaterialButton? = null private var italicToggleButton: MaterialButton? = null private var boldToggleButton: MaterialButton? = null + private var outlineToggleButton: MaterialButton? = null + private var outlineWidthLayout: RelativeLayout? = null private var textSize: EditText? = null private var layerModel: LayerContracts.Model? = null private lateinit var activity: MainActivity @@ -116,6 +121,8 @@ class TextToolIntegrationTest { activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_underlined) italicToggleButton = activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_italic) boldToggleButton = activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_bold) + outlineToggleButton = activity.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_outline) + outlineWidthLayout = activity.findViewById(R.id.pocketpaint_outline_width_layout) textSize = activity.findViewById(R.id.pocketpaint_font_size_text) textTool?.resetBoxPosition() } @@ -125,6 +132,7 @@ class TextToolIntegrationTest { selectFormatting(FormattingOptions.ITALIC) selectFormatting(FormattingOptions.BOLD) selectFormatting(FormattingOptions.UNDERLINE) + selectFormatting(FormattingOptions.OUTLINE) enterTestText() onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)).perform(click()) onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)).perform( @@ -135,12 +143,14 @@ class TextToolIntegrationTest { italicToggleButton?.let { Assert.assertTrue(it.isChecked) } boldToggleButton?.let { Assert.assertTrue(it.isChecked) } underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) } + outlineToggleButton?.let { Assert.assertTrue(it.isChecked) } Assert.assertEquals(TEST_TEXT_ADVANCED, textEditText?.text?.toString()) onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)).check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_text_tool_dialog_list_font)).check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_underlined)).check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_italic)).check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_bold)).check(matches(isDisplayed())) + onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_outline)).check(matches(isDisplayed())) onView(withId(R.id.pocketpaint_font_size_text)).check(matches(isDisplayed())) } @@ -149,6 +159,7 @@ class TextToolIntegrationTest { selectFormatting(FormattingOptions.ITALIC) selectFormatting(FormattingOptions.BOLD) selectFormatting(FormattingOptions.UNDERLINE) + selectFormatting(FormattingOptions.OUTLINE) enterTestText() onDrawingSurfaceView() .perform(UiInteractions.touchAt(DrawingSurfaceLocationProvider.MIDDLE)) @@ -156,6 +167,7 @@ class TextToolIntegrationTest { italicToggleButton?.let { Assert.assertTrue(it.isChecked) } boldToggleButton?.let { Assert.assertTrue(it.isChecked) } underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) } + outlineToggleButton?.let { Assert.assertTrue(it.isChecked) } Assert.assertEquals(TEST_TEXT, textEditText?.text?.toString()) onView(withId(R.id.pocketpaint_text_tool_dialog_input_text)) .check(matches(not(isDisplayed()))) @@ -167,6 +179,8 @@ class TextToolIntegrationTest { .check(matches(not(isDisplayed()))) onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_bold)) .check(matches(not(isDisplayed()))) + onView(withId(R.id.pocketpaint_text_tool_dialog_toggle_outline)) + .check(matches(not(isDisplayed()))) onView(withId(R.id.pocketpaint_font_size_text)) .check(matches(not(isDisplayed()))) } @@ -247,6 +261,8 @@ class TextToolIntegrationTest { textTool?.let { Assert.assertFalse(it.underlined) } textTool?.let { Assert.assertFalse(it.italic) } textTool?.let { Assert.assertFalse(it.bold) } + textTool?.let { Assert.assertFalse(it.outlined) } + outlineWidthLayout?.let { Assert.assertFalse(it.isVisible) } } @Test @@ -292,6 +308,20 @@ class TextToolIntegrationTest { Assert.assertFalse(toolMemberBold) boldToggleButton?.let { Assert.assertFalse(it.isChecked) } Assert.assertEquals(getFormattingOptionAsString(FormattingOptions.BOLD), boldToggleButton?.text.toString()) + selectFormatting(FormattingOptions.OUTLINE) + textTool?.let { Assert.assertTrue(it.outlined) } + outlineToggleButton?.let { Assert.assertTrue(it.isChecked) } + Assert.assertEquals( + getFormattingOptionAsString(FormattingOptions.OUTLINE), + outlineToggleButton?.text.toString() + ) + selectFormatting(FormattingOptions.OUTLINE) + textTool?.let { Assert.assertFalse(it.outlined) } + outlineToggleButton?.let { Assert.assertFalse(it.isChecked) } + Assert.assertEquals( + getFormattingOptionAsString(FormattingOptions.OUTLINE), + outlineToggleButton?.text.toString() + ) } @Test @@ -301,6 +331,7 @@ class TextToolIntegrationTest { selectFormatting(FormattingOptions.UNDERLINE) selectFormatting(FormattingOptions.ITALIC) selectFormatting(FormattingOptions.BOLD) + selectFormatting(FormattingOptions.OUTLINE) onToolBarView().performCloseToolOptionsView() val oldBoxWidth = toolMemberBoxWidth @@ -318,6 +349,7 @@ class TextToolIntegrationTest { underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) } italicToggleButton?.let { Assert.assertTrue(it.isChecked) } boldToggleButton?.let { Assert.assertTrue(it.isChecked) } + outlineToggleButton?.let { Assert.assertTrue(it.isChecked) } Assert.assertTrue(oldBoxWidth == toolMemberBoxWidth && oldBoxHeight == toolMemberBoxHeight) } @@ -328,6 +360,7 @@ class TextToolIntegrationTest { selectFormatting(FormattingOptions.UNDERLINE) selectFormatting(FormattingOptions.ITALIC) selectFormatting(FormattingOptions.BOLD) + selectFormatting(FormattingOptions.OUTLINE) val toolMemberBoxPosition = toolMemberBoxPosition val expectedPosition = toolMemberBoxPosition?.y?.let { PointF(toolMemberBoxPosition.x, it) } @@ -344,6 +377,7 @@ class TextToolIntegrationTest { underlinedToggleButton?.let { Assert.assertTrue(it.isChecked) } italicToggleButton?.let { Assert.assertTrue(it.isChecked) } boldToggleButton?.let { Assert.assertTrue(it.isChecked) } + outlineToggleButton?.let { Assert.assertTrue(it.isChecked) } Assert.assertEquals(expectedPosition, toolMemberBoxPosition) Assert.assertEquals(oldBoxWidth.toDouble(), toolMemberBoxWidth.toDouble(), EQUALS_DELTA) Assert.assertEquals(oldBoxHeight.toDouble(), toolMemberBoxHeight.toDouble(), EQUALS_DELTA) @@ -750,6 +784,7 @@ class TextToolIntegrationTest { selectFormatting(FormattingOptions.ITALIC) selectFormatting(FormattingOptions.BOLD) selectFormatting(FormattingOptions.UNDERLINE) + selectFormatting(FormattingOptions.OUTLINE) } val boxWidth = toolMemberBoxWidth val boxHeight = toolMemberBoxHeight @@ -759,10 +794,120 @@ class TextToolIntegrationTest { selectFormatting(FormattingOptions.ITALIC) selectFormatting(FormattingOptions.BOLD) selectFormatting(FormattingOptions.UNDERLINE) + selectFormatting(FormattingOptions.OUTLINE) Assert.assertTrue(boxWidth < toolMemberBoxWidth && boxHeight < toolMemberBoxHeight) } } + @Test + fun testTextOutlineMode() { + enterTestText() + val canvasPoint = centerBox() + onTopBarView().performClickCheckmark() + val surfaceBitmapWidth = layerModel?.width + val pixelsDrawingSurface = surfaceBitmapWidth?.let { IntArray(it) } + if (surfaceBitmapWidth != null && canvasPoint != null) { + layerModel?.currentLayer?.bitmap?.getPixels( + pixelsDrawingSurface, 0, surfaceBitmapWidth, 0, + canvasPoint.y.toInt(), surfaceBitmapWidth, 1 + ) + } + val blackPixelAmountNoOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) } + val whitePixelAmountNoOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) } + onTopBarView().performUndo() + + selectFormatting(FormattingOptions.OUTLINE) + textTool?.let { Assert.assertTrue(it.outlined) } + + onTopBarView().performClickCheckmark() + + if (surfaceBitmapWidth != null && canvasPoint != null) { + layerModel?.currentLayer?.bitmap?.getPixels( + pixelsDrawingSurface, 0, surfaceBitmapWidth, 0, + canvasPoint.y.toInt(), surfaceBitmapWidth, 1 + ) + } + + val blackPixelAmountWithOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) } + if (blackPixelAmountNoOutline != null && blackPixelAmountWithOutline != null) { + assert(blackPixelAmountNoOutline > blackPixelAmountWithOutline) + assert(blackPixelAmountWithOutline > 0) + } + + val whitePixelAmountWithOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) } + if (whitePixelAmountNoOutline != null && whitePixelAmountWithOutline != null) { + assert(whitePixelAmountNoOutline < whitePixelAmountWithOutline) + } + } + + @Test + fun testTextOutlineWidth() { + val canvasPoint = centerBox() + selectFormatting(FormattingOptions.OUTLINE) + textTool?.let { Assert.assertTrue(it.outlined) } + outlineWidthLayout?.let { Assert.assertTrue(it.isVisible) } + val outlineWidthInput = onView(withId(R.id.pocketpaint_outline_width_text)) + val outlineWidthSeekbar = onView(withId(R.id.pocketpaint_outline_width_seek_bar)) + outlineWidthInput.check(matches(ViewMatchers.withText(DEFAULT_TEXT_OUTLINE_WIDTH.toString()))) + outlineWidthSeekbar.check(matches(UiMatcher.withProgress(DEFAULT_TEXT_OUTLINE_WIDTH))) + + enterTestText() + + var testOutlineWidthText = "1" + + outlineWidthInput.perform( + ViewActions.replaceText(testOutlineWidthText), + ViewActions.closeSoftKeyboard() + ) + outlineWidthInput.check(matches(ViewMatchers.withText(testOutlineWidthText))) + outlineWidthSeekbar.check(matches(UiMatcher.withProgress(testOutlineWidthText.toInt()))) + + onTopBarView().performClickCheckmark() + val surfaceBitmapWidth = layerModel?.width + val pixelsDrawingSurface = surfaceBitmapWidth?.let { IntArray(it) } + if (surfaceBitmapWidth != null && canvasPoint != null) { + layerModel?.currentLayer?.bitmap?.getPixels( + pixelsDrawingSurface, 0, surfaceBitmapWidth, 0, + canvasPoint.y.toInt(), surfaceBitmapWidth, 1 + ) + } + val blackPixelAmountThinOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) } + val whitePixelAmountThinOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) } + onTopBarView().performUndo() + + testOutlineWidthText = "60" + + outlineWidthInput.perform( + ViewActions.replaceText(testOutlineWidthText), + ViewActions.closeSoftKeyboard() + ) + outlineWidthInput.check(matches(ViewMatchers.withText(testOutlineWidthText))) + outlineWidthSeekbar.check(matches(UiMatcher.withProgress(testOutlineWidthText.toInt()))) + + onTopBarView().performClickCheckmark() + + if (surfaceBitmapWidth != null && canvasPoint != null) { + layerModel?.currentLayer?.bitmap?.getPixels( + pixelsDrawingSurface, 0, surfaceBitmapWidth, 0, + canvasPoint.y.toInt(), surfaceBitmapWidth, 1 + ) + } + + val blackPixelAmountThickOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.BLACK) } + if (blackPixelAmountThinOutline != null && blackPixelAmountThickOutline != null) { + assert(blackPixelAmountThinOutline > 0) + assert(blackPixelAmountThickOutline > 0) + assert(blackPixelAmountThickOutline > blackPixelAmountThinOutline) + } + + val whitePixelAmountThickOutline = pixelsDrawingSurface?.let { countPixelsWithColor(it, Color.WHITE) } + if (whitePixelAmountThinOutline != null && whitePixelAmountThickOutline != null) { + assert(whitePixelAmountThinOutline > 0) + assert(whitePixelAmountThickOutline > 0) + assert(whitePixelAmountThickOutline < whitePixelAmountThinOutline) + } + } + private fun centerBox(): PointF? { val screenPoint = activityHelper?.displayWidth?.div(2.0f) @@ -892,6 +1037,7 @@ class TextToolIntegrationTest { FormattingOptions.UNDERLINE -> activity.getString(R.string.text_tool_dialog_underline_shortcut) FormattingOptions.ITALIC -> activity.getString(R.string.text_tool_dialog_italic_shortcut) FormattingOptions.BOLD -> activity.getString(R.string.text_tool_dialog_bold_shortcut) + FormattingOptions.OUTLINE -> activity.getString(R.string.text_tool_dialog_outline_shortcut) } } @@ -921,7 +1067,7 @@ class TextToolIntegrationTest { private val toolMemberMultilineText: Array<String> get() = textTool!!.multilineText - private enum class FormattingOptions { UNDERLINE, ITALIC, BOLD } + private enum class FormattingOptions { UNDERLINE, ITALIC, BOLD, OUTLINE } companion object { private const val TEST_TEXT = "123 www 123" diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt index 2f11a6d7d1..893e0addab 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/junit/serialization/CommandSerializationTest.kt @@ -190,7 +190,9 @@ class CommandSerializationTest { underline = false, italic = true, textSize = 25f, - textSkewX = -0.25f + textSkewX = -0.25f, + outlined = false, + outlineWidth = 25 ) expectedModel.commands.add( diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt index 713420f81e..8e30024020 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/implementation/TextToolCommand.kt @@ -20,12 +20,16 @@ package org.catrobat.paintroid.command.implementation import android.graphics.Canvas +import android.graphics.Color import android.graphics.Paint import android.graphics.PointF import org.catrobat.paintroid.command.Command import org.catrobat.paintroid.command.serialization.SerializableTypeface import org.catrobat.paintroid.common.ITALIC_FONT_BOX_ADJUSTMENT import org.catrobat.paintroid.contract.LayerContracts +import org.catrobat.paintroid.tools.implementation.OUTLINED_FONT_WIDTH_ADJUSTMENT + +const val TEXT_SIZE_MAGNIFICATION_FACTOR = 3f class TextToolCommand( multilineText: Array<String>, @@ -74,14 +78,30 @@ class TextToolCommand( val scaledBoxWidth = boxWidth / widthScaling val scaledBoxHeight = boxHeight / heightScaling + val fillPaint = Paint(textPaint) + if (typeFaceInfo.outlined) fillPaint.color = Color.WHITE multilineText.forEachIndexed { index, textLine -> canvas.drawText( textLine, scaledWidthOffset - scaledBoxWidth / 2 / if (typeFaceInfo.italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f, -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index, - textPaint + fillPaint ) } + if (typeFaceInfo.outlined) { + val outlinePaint = Paint(textPaint) + val adjustedStrokeWidth = if (typeFaceInfo.outlineWidth == 0) 0f else java.lang.Float.max(textPaint.textSize / TEXT_SIZE_MAGNIFICATION_FACTOR * (typeFaceInfo.outlineWidth / OUTLINED_FONT_WIDTH_ADJUSTMENT), 1f) + outlinePaint.style = Paint.Style.STROKE + outlinePaint.strokeWidth = adjustedStrokeWidth + multilineText.forEachIndexed { index, textLine -> + canvas.drawText( + textLine, + scaledWidthOffset - scaledBoxWidth / 2 / if (typeFaceInfo.italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f, + -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index, + outlinePaint + ) + } + } restore() } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt index 1aea05d5b6..45dd4499a4 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/command/serialization/SerializableTypeface.kt @@ -23,7 +23,7 @@ import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import org.catrobat.paintroid.tools.FontType -data class SerializableTypeface(val font: FontType, val bold: Boolean, val underline: Boolean, val italic: Boolean, val textSize: Float, val textSkewX: Float) { +data class SerializableTypeface(val font: FontType, val bold: Boolean, val underline: Boolean, val italic: Boolean, val textSize: Float, val textSkewX: Float, val outlined: Boolean, val outlineWidth: Int) { class TypefaceSerializer(version: Int) : VersionSerializer<SerializableTypeface>(version) { override fun write(kryo: Kryo, output: Output, typeface: SerializableTypeface) { @@ -34,6 +34,8 @@ data class SerializableTypeface(val font: FontType, val bold: Boolean, val under writeBoolean(typeface.italic) writeFloat(typeface.textSize) writeFloat(typeface.textSkewX) + writeBoolean(typeface.outlined) + writeInt(typeface.outlineWidth) } } @@ -42,7 +44,7 @@ data class SerializableTypeface(val font: FontType, val bold: Boolean, val under override fun readCurrentVersion(kryo: Kryo, input: Input, type: Class<out SerializableTypeface>): SerializableTypeface { return with(input) { - SerializableTypeface(FontType.valueOf(readString()), readBoolean(), readBoolean(), readBoolean(), readFloat(), readFloat()) + SerializableTypeface(FontType.valueOf(readString()), readBoolean(), readBoolean(), readBoolean(), readFloat(), readFloat(), readBoolean(), readInt()) } } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt index 5b9a1372a1..c8e303ffee 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt @@ -19,6 +19,7 @@ package org.catrobat.paintroid.tools.implementation import android.graphics.Canvas +import android.graphics.Color import android.graphics.Paint import android.graphics.PointF import android.graphics.Typeface @@ -38,6 +39,7 @@ import org.catrobat.paintroid.tools.ToolType import org.catrobat.paintroid.tools.Workspace import org.catrobat.paintroid.tools.options.TextToolOptionsView import org.catrobat.paintroid.tools.options.ToolOptionsViewController +import java.lang.Float.max import kotlin.Exception import kotlin.math.abs @@ -50,17 +52,23 @@ const val BOX_OFFSET = 20 @VisibleForTesting const val MARGIN_TOP = 200f +@VisibleForTesting +const val DEFAULT_TEXT_OUTLINE_WIDTH = 25 + private const val ROTATION_ENABLED = true private const val RESIZE_POINTS_VISIBLE = true private const val ITALIC_TEXT_SKEW = -0.25f private const val DEFAULT_TEXT_SKEW = 0.0f private const val DEFAULT_TEXT_SIZE = 20 +const val OUTLINED_FONT_WIDTH_ADJUSTMENT = 350f private const val BUNDLE_TOOL_UNDERLINED = "BUNDLE_TOOL_UNDERLINED" private const val BUNDLE_TOOL_ITALIC = "BUNDLE_TOOL_ITALIC" private const val BUNDLE_TOOL_BOLD = "BUNDLE_TOOL_BOLD" +private const val BUNDLE_TOOL_OUTLINED = "BUNDLE_TOOL_OUTLINED" private const val BUNDLE_TOOL_TEXT = "BUNDLE_TOOL_TEXT" private const val BUNDLE_TOOL_TEXT_SIZE = "BUNDLE_TOOL_TEXT_SIZE" private const val BUNDLE_TOOL_FONT = "BUNDLE_TOOL_FONT" +private const val BUNDLE_TOOL_TEXT_OUTLINE_WIDTH = "BUNDLE_TOOL_TEXT_OUTLINE_WIDTH" private const val TAG = "Can't set custom font" class TextTool( @@ -104,12 +112,17 @@ class TextTool( @JvmField var bold = false + @VisibleForTesting + @JvmField + var outlined = false + private var textSize = DEFAULT_TEXT_SIZE private val stc: Typeface? private val dubai: Typeface? private var oldBoxWidth = 0f private var oldBoxHeight = 0f private var oldToolPosition: PointF? = null + private var outlineWidth = DEFAULT_TEXT_OUTLINE_WIDTH @get:VisibleForTesting val multilineText: Array<String> @@ -191,6 +204,14 @@ class TextTool( applyAttributes() } + override fun setOutline(outlined: Boolean) { + this@TextTool.outlined = outlined + storeAttributes() + resetPreview() + workspace.invalidate() + applyAttributes() + } + override fun setTextSize(size: Int) { textSize = size textPaint.textSize = textSize * TEXT_SIZE_MAGNIFICATION_FACTOR @@ -201,6 +222,10 @@ class TextTool( override fun hideToolOptions() { this@TextTool.toolOptionsViewController.hide() } + + override fun onOutlineWidthChanged(outlineWidth: Int) { + updateOutlineWidth(outlineWidth) + } } textToolOptionsView.setCallback(callback) toolOptionsViewController.showDelayed() @@ -303,14 +328,30 @@ class TextTool( val scaledBoxWidth = boxWidth / widthScaling val scaledBoxHeight = boxHeight / heightScaling + val fillPaint = Paint(textPaint) + if (outlined) fillPaint.color = Color.WHITE multilineText.forEachIndexed { index, textLine -> canvas.drawText( textLine, scaledWidthOffset - scaledBoxWidth / 2 / if (italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f, -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index, - textPaint + fillPaint ) } + if (outlined) { + val outlinePaint = Paint(textPaint) + val adjustedStrokeWidth = if (outlineWidth == 0) 0f else max(textPaint.textSize / TEXT_SIZE_MAGNIFICATION_FACTOR * (outlineWidth / OUTLINED_FONT_WIDTH_ADJUSTMENT), 1f) + outlinePaint.style = Paint.Style.STROKE + outlinePaint.strokeWidth = adjustedStrokeWidth + multilineText.forEachIndexed { index, textLine -> + canvas.drawText( + textLine, + scaledWidthOffset - scaledBoxWidth / 2 / if (italic) ITALIC_FONT_BOX_ADJUSTMENT else 1f, + -(scaledBoxHeight / 2) + scaledHeightOffset - textAscent + lineHeight * index, + outlinePaint + ) + } + } canvas.restore() } @@ -350,9 +391,11 @@ class TextTool( putBoolean(BUNDLE_TOOL_UNDERLINED, underlined) putBoolean(BUNDLE_TOOL_ITALIC, italic) putBoolean(BUNDLE_TOOL_BOLD, bold) + putBoolean(BUNDLE_TOOL_OUTLINED, outlined) putString(BUNDLE_TOOL_TEXT, text) putInt(BUNDLE_TOOL_TEXT_SIZE, textSize) putString(BUNDLE_TOOL_FONT, font.name) + putInt(BUNDLE_TOOL_TEXT_OUTLINE_WIDTH, outlineWidth) } } @@ -362,11 +405,13 @@ class TextTool( underlined = getBoolean(BUNDLE_TOOL_UNDERLINED, underlined) italic = getBoolean(BUNDLE_TOOL_ITALIC, italic) bold = getBoolean(BUNDLE_TOOL_BOLD, bold) + outlined = getBoolean(BUNDLE_TOOL_OUTLINED, outlined) text = getString(BUNDLE_TOOL_TEXT, text) textSize = getInt(BUNDLE_TOOL_TEXT_SIZE, textSize) font = FontType.valueOf(getString(BUNDLE_TOOL_FONT, font.name)) + outlineWidth = getInt(BUNDLE_TOOL_TEXT_OUTLINE_WIDTH, outlineWidth) } - textToolOptionsView.setState(bold, italic, underlined, text, textSize, font) + textToolOptionsView.setState(bold, italic, underlined, outlined, text, textSize, font, outlineWidth) textPaint.isUnderlineText = underlined textPaint.isFakeBoldText = bold updateTypeface() @@ -419,7 +464,9 @@ class TextTool( underlined, italic, textPaint.textSize, - textPaint.textSkewX + textPaint.textSkewX, + outlined, + outlineWidth ) val command = commandFactory.createTextToolCommand( @@ -447,4 +494,9 @@ class TextTool( super.changePaintColor(color, invalidate) changeTextColor() } + + fun updateOutlineWidth(outlineWidth: Int) { + this.outlineWidth = outlineWidth + workspace.invalidate() + } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt index bd05b34d46..2885c56e1c 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/options/TextToolOptionsView.kt @@ -26,9 +26,11 @@ interface TextToolOptionsView { bold: Boolean, italic: Boolean, underlined: Boolean, + outlined: Boolean, text: String, textSize: Int, - fontType: FontType + fontType: FontType, + outlineWidth: Int ) fun setCallback(listener: Callback) @@ -52,8 +54,12 @@ interface TextToolOptionsView { fun setBold(bold: Boolean) + fun setOutline(outlined: Boolean) + fun setTextSize(size: Int) fun hideToolOptions() + + fun onOutlineWidthChanged(outlineWidth: Int) } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt index 113feb7e01..aac51a054b 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/tools/DefaultTextToolOptionsView.kt @@ -21,7 +21,9 @@ package org.catrobat.paintroid.ui.tools import android.content.Context import android.graphics.Paint import android.text.Editable +import android.text.InputFilter import android.text.TextWatcher +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.View.OnFocusChangeListener @@ -29,17 +31,24 @@ import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.Checkable import android.widget.EditText +import android.widget.RelativeLayout +import android.widget.SeekBar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton import org.catrobat.paintroid.R import org.catrobat.paintroid.tools.FontType +import org.catrobat.paintroid.tools.helper.DefaultNumberRangeFilter import org.catrobat.paintroid.tools.options.TextToolOptionsView +import java.util.Locale private const val DEFAULT_TEXTSIZE = "20" private const val MAX_TEXTSIZE = "300" private const val MIN_FONT_SIZE = 1 private const val MAX_FONT_SIZE = 300 +private const val DEFAULT_OUTLINE_WIDTH = "25" +private const val MIN_OUTLINE_THICKNESS = 0 +private const val MAX_OUTLINE_THICKNESS = 100 class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { private val context: Context = rootView.context @@ -50,9 +59,13 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { private val underlinedToggleButton: MaterialButton private val italicToggleButton: MaterialButton private val boldToggleButton: MaterialButton + private val outlineToggleButton: MaterialButton private val fontTypes: List<FontType> private val topLayout: View private val bottomLayout: View + private val outlineWidthLayout: RelativeLayout + private val outlineWidthText: EditText + private val outlineWidthSeekBar: SeekBar init { val inflater = LayoutInflater.from(context) @@ -66,12 +79,45 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { italicToggleButton = textToolView.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_italic) boldToggleButton = textToolView.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_bold) + outlineToggleButton = textToolView.findViewById(R.id.pocketpaint_text_tool_dialog_toggle_outline) fontSizeText = textToolView.findViewById(R.id.pocketpaint_font_size_text) + outlineWidthLayout = textToolView.findViewById(R.id.pocketpaint_outline_width_layout) + outlineWidthText = textToolView.findViewById(R.id.pocketpaint_outline_width_text) + outlineWidthSeekBar = textToolView.findViewById(R.id.pocketpaint_outline_width_seek_bar) fontSizeText.setText(DEFAULT_TEXTSIZE) underlinedToggleButton.paintFlags = underlinedToggleButton.paintFlags or Paint.UNDERLINE_TEXT_FLAG @Suppress("SpreadOperator") fontTypes = FontType.values().toList() + outlineWidthLayout.visibility = View.GONE + outlineWidthText.filters = arrayOf<InputFilter>(DefaultNumberRangeFilter(MIN_OUTLINE_THICKNESS, MAX_OUTLINE_THICKNESS)) + outlineWidthSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + setOutlineWidthText(progress) + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar) = Unit + }) + + outlineWidthText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = + Unit + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit + override fun afterTextChanged(s: Editable) { + try { + val outlineWidthInPercent = s.toString().toInt() + outlineWidthSeekBar.progress = outlineWidthInPercent + updateOutlineWidth(outlineWidthInPercent) + } catch (e: java.lang.NumberFormatException) { + Log.e("Error parsing outline", "result was null") + } + } + }) + outlineWidthText.setText(DEFAULT_OUTLINE_WIDTH) initializeListeners() textEditText.requestFocus() } @@ -110,6 +156,11 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { notifyBoldChanged(bold) hideKeyboard() } + outlineToggleButton.setOnClickListener { v -> + val outline = (v as Checkable).isChecked + notifyOutlineChanged(outline) + hideKeyboard() + } fontSizeText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) = Unit @@ -149,6 +200,15 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { callback?.setBold(bold) } + private fun notifyOutlineChanged(outlined: Boolean) { + if (outlined) { + outlineWidthLayout.visibility = View.VISIBLE + } else { + outlineWidthLayout.visibility = View.GONE + } + callback?.setOutline(outlined) + } + private fun notifyTextSizeChanged(textSize: Int) { callback?.setTextSize(textSize) } @@ -161,16 +221,21 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { bold: Boolean, italic: Boolean, underlined: Boolean, + outlined: Boolean, text: String, textSize: Int, - fontType: FontType + fontType: FontType, + outlineWidth: Int ) { boldToggleButton.isChecked = bold italicToggleButton.isChecked = italic underlinedToggleButton.isChecked = underlined + outlineToggleButton.isChecked = outlined + notifyOutlineChanged(outlined) textEditText.setText(text) (fontList.adapter as FontListAdapter).setSelectedIndex(fontTypes.indexOf(fontType)) fontSizeText.setText(DEFAULT_TEXTSIZE) + outlineWidthText.setText(outlineWidth.toString()) } override fun setCallback(listener: TextToolOptionsView.Callback) { @@ -190,4 +255,12 @@ class DefaultTextToolOptionsView(rootView: ViewGroup) : TextToolOptionsView { override fun getTopLayout(): View = topLayout override fun getBottomLayout(): View = bottomLayout + + private fun setOutlineWidthText(widthInPercent: Int) { + outlineWidthText.setText(String.format(Locale.getDefault(), "%d", widthInPercent)) + } + + private fun updateOutlineWidth(outlineWidth: Int) { + callback?.onOutlineWidthChanged(outlineWidth) + } } diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml new file mode 100644 index 0000000000..9fe0e82bf7 --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_blue_outline.xml @@ -0,0 +1,7 @@ +<vector android:height="24dp" android:viewportHeight="48" + android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#ffffff" + android:pathData="M6.5,41.5 L20.862,6.5l5.332,0L41.5,41.5L35.862,41.5L31.5,30.9L15.862,30.9L11.755,41.5ZM17.291,27.128L29.969,27.128L26.066,17.435q-1.786,-4.417 -2.653,-7.258 -0.714,3.366 -2.015,6.685z" android:strokeColor="#00000000"/> + <path android:fillColor="#3d8091" + android:pathData="M20.506,6.016 L5.748,41.984l0.753,0 5.617,0l4.106,-10.6l14.922,0l4.361,10.6l6.763,0L26.541,6.016ZM21.217,6.983l4.629,0L40.732,41.017L36.217,41.017L31.855,30.417L15.499,30.417L11.393,41.017L7.254,41.017ZM23.91,10.044 L22.907,10.082c-0.471,2.219 -1.136,4.424 -1.995,6.613l-4.367,10.915l0.745,0 13.426,0l-4.167,-10.347c-1.187,-2.937 -2.067,-5.346 -2.639,-7.22zM23.46,11.796c0.564,1.692 1.173,3.459 2.124,5.81l3.638,9.037L18.037,26.643l3.846,-9.614l0,-0.002c0.679,-1.731 1.135,-3.481 1.577,-5.232z" android:strokeColor="#00000000"/> +</vector> diff --git a/Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml new file mode 100644 index 0000000000..5b03294a8f --- /dev/null +++ b/Paintroid/src/main/res/drawable/ic_pocketpaint_a_white_outline.xml @@ -0,0 +1,7 @@ +<vector android:height="24dp" android:viewportHeight="48" + android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#3d8091" + android:pathData="M6.5,41.5 L20.862,6.5l5.332,0L41.5,41.5L35.862,41.5L31.5,30.9L15.862,30.9L11.755,41.5ZM17.291,27.128L29.969,27.128L26.066,17.435q-1.786,-4.417 -2.653,-7.258 -0.714,3.366 -2.015,6.685z" android:strokeColor="#00000000"/> + <path android:fillColor="#ffffff" + android:pathData="M20.506,6.016 L5.748,41.984l0.753,0 5.617,0l4.106,-10.6l14.922,0l4.361,10.6l6.763,0L26.541,6.016ZM21.217,6.983l4.629,0L40.732,41.017L36.217,41.017L31.855,30.417L15.499,30.417L11.393,41.017L7.254,41.017ZM23.91,10.044 L22.907,10.082c-0.471,2.219 -1.136,4.424 -1.995,6.613l-4.367,10.915l0.745,0 13.426,0l-4.167,-10.347c-1.187,-2.937 -2.067,-5.346 -2.639,-7.22zM23.46,11.796c0.564,1.692 1.173,3.459 2.124,5.81l3.638,9.037L18.037,26.643l3.846,-9.614l0,-0.002c0.679,-1.731 1.135,-3.481 1.577,-5.232z" android:strokeColor="#00000000"/> +</vector> diff --git a/Paintroid/src/main/res/drawable/outline_button_selector.xml b/Paintroid/src/main/res/drawable/outline_button_selector.xml new file mode 100644 index 0000000000..5b3e7548db --- /dev/null +++ b/Paintroid/src/main/res/drawable/outline_button_selector.xml @@ -0,0 +1,4 @@ +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:drawable="@drawable/ic_pocketpaint_a_white_outline" /> + <item android:state_checked="false" android:drawable="@drawable/ic_pocketpaint_a_blue_outline" /> +</selector> \ No newline at end of file diff --git a/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml b/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml index 2d2cb40695..be293073ec 100644 --- a/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml +++ b/Paintroid/src/main/res/layout/dialog_pocketpaint_text_tool.xml @@ -27,89 +27,158 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - <com.google.android.material.textfield.TextInputLayout - style="@style/TextFieldTheme" + <RelativeLayout + android:id="@+id/pocketpaint_text_button_layout" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:layout_margin="10dp"> + android:layout_height="wrap_content"> - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/pocketpaint_font_size_text" + <com.google.android.material.textfield.TextInputLayout + style="@style/TextFieldTheme" android:layout_width="wrap_content" - android:layout_height="40dp" - android:background="@drawable/pocketpaint_round_rect_shape" - android:gravity="center" - android:imeOptions="actionDone" - android:importantForAutofill="no" - android:inputType="number" - android:minEms="3" - android:padding="4dp" - android:saveEnabled="false" - android:textColor="?attr/colorAccent" - android:textStyle="bold" - tools:ignore="LabelFor" - tools:text="100" /> - - </com.google.android.material.textfield.TextInputLayout> + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_margin="10dp"> + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/pocketpaint_font_size_text" + android:layout_width="wrap_content" + android:layout_height="40dp" + android:background="@drawable/pocketpaint_round_rect_shape" + android:gravity="center" + android:imeOptions="actionDone" + android:importantForAutofill="no" + android:inputType="number" + android:minEms="3" + android:padding="4dp" + android:saveEnabled="false" + android:textColor="?attr/colorAccent" + android:textStyle="bold" + tools:ignore="LabelFor" + tools:text="100" /> + + </com.google.android.material.textfield.TextInputLayout> + + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_alignParentRight="true" + android:layout_margin="10dp" + android:layout_marginTop="@dimen/toolbar_height" + tools:ignore="RelativeOverlap,RtlHardcoded"> + + <com.google.android.material.button.MaterialButtonToggleGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/pocketpaint_text_tool_dialog_toggle_outline" + style="@style/CircularToggleButtonStyle" + android:layout_width="40dp" + android:layout_height="40dp" + android:padding="8dp" + android:text="@string/text_tool_dialog_outline_shortcut" + android:drawableLeft="@drawable/outline_button_selector" /> + </com.google.android.material.button.MaterialButtonToggleGroup> + + <Space style="@style/PocketPaintToolHorizontalSpace" /> + + <com.google.android.material.button.MaterialButtonToggleGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/pocketpaint_text_tool_dialog_toggle_underlined" + style="@style/CircularToggleButtonStyle" + android:layout_width="40dp" + android:layout_height="40dp" + android:text="@string/text_tool_dialog_underline_shortcut" + android:textSize="20sp" /> + </com.google.android.material.button.MaterialButtonToggleGroup> + + <Space style="@style/PocketPaintToolHorizontalSpace" /> + + <com.google.android.material.button.MaterialButtonToggleGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/pocketpaint_text_tool_dialog_toggle_italic" + style="@style/CircularToggleButtonStyle" + android:layout_width="40dp" + android:layout_height="40dp" + android:text="@string/text_tool_dialog_italic_shortcut" + android:textSize="20sp" + android:textStyle="italic" /> + </com.google.android.material.button.MaterialButtonToggleGroup> + + <Space style="@style/PocketPaintToolHorizontalSpace" /> + + <com.google.android.material.button.MaterialButtonToggleGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/pocketpaint_text_tool_dialog_toggle_bold" + style="@style/CircularToggleButtonStyle" + android:layout_width="40dp" + android:layout_height="40dp" + android:text="@string/text_tool_dialog_bold_shortcut" + android:textSize="20sp" + android:textStyle="bold" /> + </com.google.android.material.button.MaterialButtonToggleGroup> + </LinearLayout> + </RelativeLayout> - <LinearLayout + <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_alignParentRight="true" - android:layout_margin="10dp" - android:layout_marginTop="@dimen/toolbar_height" - tools:ignore="RelativeOverlap,RtlHardcoded"> + android:id="@+id/pocketpaint_outline_width_layout" + android:layout_below="@id/pocketpaint_text_button_layout"> - <com.google.android.material.button.MaterialButtonToggleGroup + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/pocketpaint_outline_width_text_layout" + style="@style/TextFieldTheme" android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/pocketpaint_text_tool_dialog_toggle_underlined" - style="@style/CircularToggleButtonStyle" - android:layout_width="40dp" - android:layout_height="40dp" - android:text="@string/text_tool_dialog_underline_shortcut" - android:textSize="20sp" /> - </com.google.android.material.button.MaterialButtonToggleGroup> - - <Space style="@style/PocketPaintToolHorizontalSpace" /> - - <com.google.android.material.button.MaterialButtonToggleGroup - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/pocketpaint_text_tool_dialog_toggle_italic" - style="@style/CircularToggleButtonStyle" - android:layout_width="40dp" - android:layout_height="40dp" - android:text="@string/text_tool_dialog_italic_shortcut" - android:textSize="20sp" - android:textStyle="italic" /> - </com.google.android.material.button.MaterialButtonToggleGroup> - - <Space style="@style/PocketPaintToolHorizontalSpace" /> - - <com.google.android.material.button.MaterialButtonToggleGroup + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_margin="10dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/pocketpaint_outline_width_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/pocketpaint_round_rect_shape" + android:gravity="center" + android:imeOptions="actionDone" + android:importantForAutofill="no" + android:inputType="number" + android:layoutDirection="ltr" + android:minEms="3" + android:padding="4dp" + android:saveEnabled="false" + android:textColor="?attr/colorAccent" + android:textStyle="bold" + tools:ignore="LabelFor" + tools:text="100" /> + + </com.google.android.material.textfield.TextInputLayout> + + <SeekBar + android:id="@+id/pocketpaint_outline_width_seek_bar" android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <com.google.android.material.button.MaterialButton - android:id="@+id/pocketpaint_text_tool_dialog_toggle_bold" - style="@style/CircularToggleButtonStyle" - android:layout_width="40dp" - android:layout_height="40dp" - android:text="@string/text_tool_dialog_bold_shortcut" - android:textSize="20sp" - android:textStyle="bold" /> - </com.google.android.material.button.MaterialButtonToggleGroup> - </LinearLayout> - + android:layout_height="wrap_content" + android:layout_alignTop="@id/pocketpaint_outline_width_text_layout" + android:layout_alignBottom="@id/pocketpaint_outline_width_text_layout" + android:layout_alignParentEnd="true" + android:layout_marginEnd="10dp" + android:layout_toEndOf="@+id/pocketpaint_outline_width_text_layout" + android:max="100" + android:minHeight="30dip" /> + </RelativeLayout> </RelativeLayout> <RelativeLayout diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index de2ea72fb6..9eb81a111e 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -117,6 +117,7 @@ <string name="text_tool_dialog_underline_shortcut">U</string> <string name="text_tool_dialog_italic_shortcut">I</string> <string name="text_tool_dialog_bold_shortcut">B</string> + <string name="text_tool_dialog_outline_shortcut" translatable="false">O</string> <string name="text_tool_dialog_input_hint">Tap here to write</string> <string name="text_tool_dialog_font_monospace">Monospace</string> <string name="text_tool_dialog_font_serif">Serif</string>