diff --git a/app/src/main/java/com/osfans/trime/ime/keyboard/Key.kt b/app/src/main/java/com/osfans/trime/ime/keyboard/Key.kt index c7e3747208..5b3a1c1d12 100644 --- a/app/src/main/java/com/osfans/trime/ime/keyboard/Key.kt +++ b/app/src/main/java/com/osfans/trime/ime/keyboard/Key.kt @@ -281,3 +281,10 @@ class Key( else -> if (isPressed) hlKeySymbolColor else keySymbolColor } } + +private const val ICON_PREFIX = "ic@" + +val String.isIconFont: Boolean + get() = startsWith(ICON_PREFIX) + +fun String.toIconName(): String = replace(ICON_PREFIX, "cmd_") diff --git a/app/src/main/java/com/osfans/trime/ime/keyboard/KeyboardView.kt b/app/src/main/java/com/osfans/trime/ime/keyboard/KeyboardView.kt index 699dae9b98..c79cae6f1a 100644 --- a/app/src/main/java/com/osfans/trime/ime/keyboard/KeyboardView.kt +++ b/app/src/main/java/com/osfans/trime/ime/keyboard/KeyboardView.kt @@ -10,14 +10,18 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color +import android.graphics.ColorFilter import android.graphics.Paint import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.view.KeyEvent import androidx.core.graphics.createBitmap import androidx.core.graphics.withClip +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.sizeDp import com.osfans.trime.daemon.RimeDaemon import com.osfans.trime.data.prefs.AppPrefs import com.osfans.trime.data.theme.ColorManager @@ -369,6 +373,17 @@ class KeyboardView( val keyDrawY = (key.y + paddingTop).toFloat() canvas.translate(keyDrawX, keyDrawY) + drawKeyBackground(key, canvas) + drawKeyLabel(key, canvas, paint) + drawKeySymbol(key, canvas, paint) + + canvas.translate(-keyDrawX, -keyDrawY) + } + + private fun drawKeyBackground( + key: Key, + canvas: Canvas, + ) { val keyBackground = key.getBackgroundDrawable() if (keyBackground != null) { if (keyBackground is GradientDrawable) { @@ -381,73 +396,110 @@ class KeyboardView( } onDrawKeyBackground(key, canvas, keyBackground) } + } + + private fun drawKeyLabel( + key: Key, + canvas: Canvas, + paint: Paint, + ) { + val keyLabel = key.getLabel().let { + if (it == "enter_labels") labelEnter else it + } + if (keyLabel.isEmpty()) return + + if (keyLabel.isIconFont) { + val size = if (key.keyTextSize > 0) sp(key.keyTextSize) else sp(if (keyLabel.length > 1) labelTextSize else keyTextSize) + drawIconLabel(key, keyLabel, canvas, size, centered = true) + } else { + drawTextLabel(key, keyLabel, canvas, paint) + } + } + + private fun drawIconLabel( + key: Key, + label: String, + canvas: Canvas, + size: Float, + color: Int = key.getTextColor(), + offsetX: Float = key.keyTextOffsetX, + offsetY: Float = key.keyTextOffsetY, + centered: Boolean = true, + ) { + val iconSize = size / resources.displayMetrics.density + val icon = IconicsDrawable(context, label.toIconName()).apply { + sizeDp = iconSize.toInt() + colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + } + + val centerX = key.width / 2f - icon.intrinsicWidth / 2f + val centerY = if (centered) key.height / 2f else key.height.toFloat() + val x = (centerX + sp(offsetX)).toInt() + val y = (centerY - icon.intrinsicHeight / 2f + sp(offsetY)).toInt() + + icon.setBounds(x, y, x + icon.intrinsicWidth, y + icon.intrinsicHeight) + icon.draw(canvas) + } - // Switch the character to uppercase if shift is pressed + private fun drawTextLabel( + key: Key, + label: String, + canvas: Canvas, + paint: Paint, + ) { + paint.typeface = FontManager.getTypeface("key_font") + paint.textSize = if (key.keyTextSize > 0) sp(key.keyTextSize) else sp(if (label.length > 1) labelTextSize else keyTextSize) + paint.color = key.getTextColor() val centerX = key.width * 0.5f val centerY = key.height * 0.5f - val keyLabel = - key.getLabel().let { - if (it == "enter_labels") { - labelEnter - } else { - it - } - } - if (keyLabel.isNotEmpty()) { - paint.typeface = FontManager.getTypeface("key_font") - // For characters, use large font. For labels like "Done", use small font. - paint.textSize = - if (key.keyTextSize > 0) { - sp(key.keyTextSize) - } else { - sp(if (keyLabel.length > 1) labelTextSize else keyTextSize) - } + val labelX = centerX + sp(key.keyTextOffsetX) + val labelBaseline = centerY + sp(key.keyTextOffsetY) + + if (mShadowRadius > 0f) { + paint.setShadowLayer(mShadowRadius, 0f, 0f, mShadowColor) + } - val labelX = centerX + sp(key.keyTextOffsetX) - val labelBaseline = centerY + sp(key.keyTextOffsetY) + val adjustmentY = (paint.textSize - paint.descent()) / 2f + canvas.drawText(label, labelX, labelBaseline + adjustmentY, paint) - paint.color = key.getTextColor() + paint.clearShadowLayer() + } - // Draw a drop shadow for the text - if (mShadowRadius > 0f) { - paint.setShadowLayer(mShadowRadius, 0f, 0f, mShadowColor) - } else { - paint.clearShadowLayer() + private fun drawKeySymbol( + key: Key, + canvas: Canvas, + paint: Paint, + ) { + val showSymbol = rime.run { !getRuntimeOption("_hide_key_symbol") } + val showHint = rime.run { !getRuntimeOption("_hide_key_hint") } + if (!showSymbol && !showHint) return + + paint.apply { + typeface = FontManager.getTypeface("symbol_font") + textSize = listOf(key.symbolTextSize, symbolTextSize).firstOrNull { it > 0f }?.let { sp(it) } ?: sp(symbolTextSize) + color = key.getSymbolColor() + } + + val fontMetrics = paint.fontMetrics + val centerX = key.width / 2f + val symbolSize = listOf(key.symbolTextSize, symbolTextSize).firstOrNull { it > 0f }?.let { sp(it) } ?: sp(symbolTextSize) + + showSymbol.takeIf { key.symbolLabel.isNotEmpty() }?.let { + val label = key.symbolLabel + when { + label.isIconFont -> drawIconLabel(key, label, canvas, symbolSize, key.getSymbolColor(), key.keySymbolOffsetX, key.keySymbolOffsetY, centered = false) + else -> canvas.drawText(label, centerX + sp(key.keySymbolOffsetX), -fontMetrics.top + sp(key.keySymbolOffsetY), paint) } + } - val adjustmentY = paint.run { textSize - descent() } / 2f - // Draw the text - canvas.drawText(keyLabel, labelX, labelBaseline + adjustmentY, paint) - // Turn off drop shadow - paint.clearShadowLayer() - - val showKeySymbol = rime.run { !getRuntimeOption("_hide_key_symbol") } - val showKeyHint = rime.run { !getRuntimeOption("_hide_key_hint") } - if (showKeySymbol || showKeyHint) { - paint.typeface = FontManager.getTypeface("symbol_font") - floatArrayOf(key.symbolTextSize, symbolTextSize) - .firstOrNull { it > 0f } - ?.let { paint.textSize = sp(it) } - paint.color = key.getSymbolColor() - - val fontMetrics = paint.fontMetrics - - val symbolLabel = key.symbolLabel - if (showKeySymbol && symbolLabel.isNotEmpty()) { - val symbolX = centerX + sp(key.keySymbolOffsetX) - val symbolBaseline = -fontMetrics.top - canvas.drawText(symbolLabel, symbolX, symbolBaseline + sp(key.keySymbolOffsetY), paint) - } - val hintLabel = key.hint - if (showKeyHint && hintLabel.isNotEmpty()) { - val hintX = centerX + sp(key.keyHintOffsetX) - val hintBaseline = -fontMetrics.bottom - canvas.drawText(hintLabel, hintX, hintBaseline + key.height + sp(key.keyHintOffsetY), paint) - } + showHint.takeIf { key.hint.isNotEmpty() }?.let { + val label = key.hint + when { + label.isIconFont -> drawIconLabel(key, label, canvas, symbolSize, key.getSymbolColor(), key.keyHintOffsetX, key.keyHintOffsetY, centered = false) + else -> canvas.drawText(label, centerX + sp(key.keyHintOffsetX), key.height - fontMetrics.bottom + sp(key.keyHintOffsetY), paint) } } - canvas.translate(-keyDrawX, -keyDrawY) } private fun onDrawKeyBackground( diff --git a/app/src/main/java/com/osfans/trime/ime/popup/PopupEntryUi.kt b/app/src/main/java/com/osfans/trime/ime/popup/PopupEntryUi.kt index 9ebc326cb7..09f834e571 100644 --- a/app/src/main/java/com/osfans/trime/ime/popup/PopupEntryUi.kt +++ b/app/src/main/java/com/osfans/trime/ime/popup/PopupEntryUi.kt @@ -5,12 +5,20 @@ package com.osfans.trime.ime.popup import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.graphics.drawable.GradientDrawable import android.view.ViewOutlineProvider +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.sizeDp import com.osfans.trime.data.theme.ColorManager import com.osfans.trime.data.theme.FontManager import com.osfans.trime.data.theme.Theme import com.osfans.trime.ime.core.AutoScaleTextView +import com.osfans.trime.ime.keyboard.isIconFont +import com.osfans.trime.ime.keyboard.toIconName import splitties.dimensions.dp import splitties.views.dsl.constraintlayout.centerHorizontally import splitties.views.dsl.constraintlayout.constraintLayout @@ -18,21 +26,26 @@ import splitties.views.dsl.constraintlayout.lParams import splitties.views.dsl.constraintlayout.topOfParent import splitties.views.dsl.core.Ui import splitties.views.dsl.core.add -import splitties.views.dsl.core.matchParent import splitties.views.dsl.core.view +import splitties.views.dsl.core.wrapContent import splitties.views.gravityCenter -class PopupEntryUi(override val ctx: Context, theme: Theme, keyHeight: Int, radius: Float) : Ui { +class PopupEntryUi(override val ctx: Context, private val theme: Theme, keyHeight: Int, radius: Float) : Ui { var lastShowTime = -1L val textView = view(::AutoScaleTextView) { + scaleMode = AutoScaleTextView.Mode.Proportional textSize = theme.generalStyle.popupTextSize gravity = gravityCenter setTextColor(ColorManager.getColor("popup_text_color")) typeface = FontManager.getTypeface("POPUP_FONT") } + val imageView = view(::AppCompatImageView) { + visibility = android.view.View.GONE + } + override val root = constraintLayout { background = GradientDrawable().apply { cornerRadius = radius @@ -42,7 +55,14 @@ class PopupEntryUi(override val ctx: Context, theme: Theme, keyHeight: Int, radi elevation = dp(2f) add( textView, - lParams(matchParent, keyHeight) { + lParams(wrapContent, keyHeight) { + topOfParent() + centerHorizontally() + }, + ) + add( + imageView, + lParams(wrapContent, keyHeight) { topOfParent() centerHorizontally() }, @@ -50,6 +70,19 @@ class PopupEntryUi(override val ctx: Context, theme: Theme, keyHeight: Int, radi } fun setText(text: String) { - textView.text = text + if (text.isIconFont) { + imageView.setImageDrawable( + IconicsDrawable(ctx, text.toIconName()).apply { + sizeDp = theme.generalStyle.popupTextSize.toInt() + colorFilter = PorterDuffColorFilter(ColorManager.getColor("popup_text_color"), PorterDuff.Mode.SRC_IN) + }, + ) + imageView.isVisible = true + textView.isVisible = false + } else { + textView.text = text + textView.isVisible = true + imageView.isVisible = false + } } } diff --git a/app/src/main/java/com/osfans/trime/ime/popup/PopupKeyboardUi.kt b/app/src/main/java/com/osfans/trime/ime/popup/PopupKeyboardUi.kt index b788a22882..2331f3e369 100644 --- a/app/src/main/java/com/osfans/trime/ime/popup/PopupKeyboardUi.kt +++ b/app/src/main/java/com/osfans/trime/ime/popup/PopupKeyboardUi.kt @@ -5,15 +5,23 @@ package com.osfans.trime.ime.popup import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.graphics.Rect import android.graphics.drawable.GradientDrawable import android.view.ViewOutlineProvider +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.sizeDp import com.osfans.trime.data.theme.ColorManager import com.osfans.trime.data.theme.FontManager import com.osfans.trime.data.theme.KeyActionManager import com.osfans.trime.data.theme.Theme import com.osfans.trime.ime.core.AutoScaleTextView import com.osfans.trime.ime.keyboard.KeyboardSwitcher +import com.osfans.trime.ime.keyboard.isIconFont +import com.osfans.trime.ime.keyboard.toIconName import splitties.dimensions.dp import splitties.views.dsl.core.Ui import splitties.views.dsl.core.add @@ -62,13 +70,14 @@ class PopupKeyboardUi( class PopupKeyUi(override val ctx: Context, val theme: Theme, val text: String) : Ui { val textView = view(::AutoScaleTextView) { - text = this@PopupKeyUi.text scaleMode = AutoScaleTextView.Mode.Proportional textSize = theme.generalStyle.popupTextSize setTextColor(ColorManager.getColor("popup_text_color")) typeface = FontManager.getTypeface("POPUP_FONT") } + val imageView = view(::AppCompatImageView) {} + override val root = frameLayout { add( textView, @@ -76,6 +85,29 @@ class PopupKeyboardUi( gravity = gravityCenter }, ) + add( + imageView, + lParams { + gravity = gravityCenter + }, + ) + } + + init { + if (text.isIconFont) { + imageView.setImageDrawable( + IconicsDrawable(ctx, text.toIconName()).apply { + sizeDp = theme.generalStyle.popupTextSize.toInt() + colorFilter = PorterDuffColorFilter(ColorManager.getColor("popup_text_color"), PorterDuff.Mode.SRC_IN) + }, + ) + imageView.isVisible = true + textView.isVisible = false + } else { + textView.text = text + textView.isVisible = true + imageView.isVisible = false + } } } @@ -163,10 +195,13 @@ class PopupKeyboardUi( if (label.length == 1 && label[0].code < 128) { label } else { - KeyActionManager - .getAction(label) - .getLabel(KeyboardSwitcher.currentKeyboard) - .let { if (it.isNotEmpty()) String(Character.toChars(it.codePointAt(0))) else "" } + KeyActionManager.getAction(label).getLabel(KeyboardSwitcher.currentKeyboard).let { + when { + it.isIconFont -> it + it.isNotEmpty() -> String(Character.toChars(it.codePointAt(0))) + else -> "" + } + } } PopupKeyUi(ctx, theme, displayLabel) @@ -205,14 +240,18 @@ class PopupKeyboardUi( private fun markFocus(index: Int) { keyUis.getOrNull(index)?.apply { root.background = focusBackground - textView.setTextColor(ColorManager.getColor("hilited_popup_text_color")) + val color = ColorManager.getColor("hilited_popup_text_color") + textView.setTextColor(color) + imageView.drawable?.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) } } private fun markInactive(index: Int) { keyUis.getOrNull(index)?.apply { root.background = null - textView.setTextColor(ColorManager.getColor("popup_text_color")) + val color = ColorManager.getColor("popup_text_color") + textView.setTextColor(color) + imageView.drawable?.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) } }