Add option for key borders and other styling options

This commit is contained in:
Aleksandras Kostarevas 2024-04-10 23:13:13 -05:00
parent 2531a74b71
commit d5a4a0b4b3
9 changed files with 227 additions and 38 deletions

View File

@ -24,8 +24,8 @@
<!-- Default theme values -->
<style name="Keyboard">
<item name="rowHeight">25%p</item>
<item name="horizontalGap">@fraction/config_key_horizontal_gap_holo</item>
<item name="verticalGap">@fraction/config_key_vertical_gap_holo</item>
<item name="horizontalGap">0.9%p</item>
<item name="verticalGap">4%p</item>
<item name="touchPositionCorrectionData">@array/touch_position_correction_data_holo</item>
<item name="keyboardTopPadding">@fraction/config_keyboard_top_padding_holo</item>
<item name="keyboardBottomPadding">@fraction/config_keyboard_bottom_padding_holo</item>

View File

@ -100,7 +100,7 @@
latin:backgroundType="functional" />
<key-style
latin:styleName="bottomEmojiKeyStyle"
latin:keySpec="!icon/emoji_action_key|!code/key_emoji"
latin:keySpec="!icon/emoji_normal_key|!code/key_emoji"
latin:keyActionFlags="noKeyPreview" />
<key-style
latin:styleName="emojiKeyStyle"

View File

@ -95,7 +95,7 @@
<key-style
latin:styleName="defaultEnterKeyStyle"
latin:keySpec="!icon/enter_key|!code/key_enter"
latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor|keepBackgroundAspectRatio"
latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor"
latin:keyActionFlags="noKeyPreview"
latin:backgroundType="action"
latin:parentStyle="navigateMoreKeysStyle" />

View File

@ -126,7 +126,8 @@
latin:backgroundType="functional" />
<key-style
latin:styleName="bottomEmojiKeyStyle"
latin:keySpec="!icon/emoji_action_key|!code/key_emoji"
latin:keySpec="!icon/emoji_normal_key|!code/key_emoji"
latin:backgroundType="functional"
latin:keyActionFlags="noKeyPreview" />
<key-style
latin:styleName="tabKeyStyle"

View File

@ -227,7 +227,7 @@
<key-style
latin:styleName="defaultEnterKeyStyle"
latin:keySpec="!icon/enter_key|!code/key_enter"
latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor|keepBackgroundAspectRatio"
latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor"
latin:keyActionFlags="noKeyPreview"
latin:backgroundType="action"
latin:parentStyle="navigateMoreKeysStyle" />

View File

@ -148,7 +148,7 @@ public class Key implements Comparable<Key> {
private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
/** Background type that represents different key background visual than normal one. */
private final int mBackgroundType;
private int mBackgroundType;
public static final int BACKGROUND_TYPE_EMPTY = 0;
public static final int BACKGROUND_TYPE_NORMAL = 1;
public static final int BACKGROUND_TYPE_FUNCTIONAL = 2;
@ -183,8 +183,8 @@ public class Key implements Comparable<Key> {
mOutputText = outputText;
mAltCode = altCode;
mDisabledIconId = disabledIconId;
mVisualInsetsLeft = visualInsetsLeft;
mVisualInsetsRight = visualInsetsRight;
mVisualInsetsLeft = 0;//visualInsetsLeft;
mVisualInsetsRight = 0;//visualInsetsRight;
}
@Nullable
@ -284,6 +284,10 @@ public class Key implements Comparable<Key> {
final int visualInsetsRight = Math.round(keyAttr.getFraction(
R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0));
if(visualInsetsLeft > 0 || visualInsetsRight > 0) {
mBackgroundType = BACKGROUND_TYPE_FUNCTIONAL;
}
mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
| row.getDefaultKeyLabelFlags();
final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId);
@ -973,7 +977,7 @@ public class Key implements Comparable<Key> {
// 1: BACKGROUND_TYPE_NORMAL
new KeyBackgroundState(),
// 2: BACKGROUND_TYPE_FUNCTIONAL
new KeyBackgroundState(),
new KeyBackgroundState(android.R.attr.state_first),
// 3: BACKGROUND_TYPE_STICKY_OFF
new KeyBackgroundState(android.R.attr.state_checkable),
// 4: BACKGROUND_TYPE_STICKY_ON

View File

@ -49,10 +49,14 @@ import org.futo.inputmethod.latin.uix.DynamicThemeProvider
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
import org.futo.inputmethod.latin.uix.EmojiTracker.unuseEmoji
import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji
import org.futo.inputmethod.latin.uix.HiddenKeysSetting
import org.futo.inputmethod.latin.uix.KeyBordersSetting
import org.futo.inputmethod.latin.uix.KeyHintsSetting
import org.futo.inputmethod.latin.uix.SUGGESTION_BLACKLIST
import org.futo.inputmethod.latin.uix.THEME_KEY
import org.futo.inputmethod.latin.uix.UixManager
import org.futo.inputmethod.latin.uix.createInlineSuggestionsRequest
import org.futo.inputmethod.latin.uix.dataStore
import org.futo.inputmethod.latin.uix.deferGetSetting
import org.futo.inputmethod.latin.uix.deferSetSetting
import org.futo.inputmethod.latin.uix.differsFrom
@ -230,6 +234,28 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
lifecycleScope.launch { uixManager.showUpdateNoticeIfNeeded() }
suggestionBlacklist.init()
lifecycleScope.launch {
dataStore.data.collect {
drawableProvider?.let { provider ->
if(provider is BasicThemeProvider) {
if (it[HiddenKeysSetting] != provider.expertMode
|| it[KeyBordersSetting] != provider.keyBorders
|| it[KeyHintsSetting] != provider.showKeyHints
) {
activeThemeOption?.obtainColors?.let { f ->
updateDrawableProvider(f(this@LatinIME))
if (!uixManager.isMainKeyboardHidden) {
recreateKeyboard()
} else {
pendingRecreateKeyboard = true
}
}
}
}
}
}
}
}
override fun onDestroy() {

View File

@ -1,7 +1,7 @@
package org.futo.inputmethod.latin.uix
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
@ -13,14 +13,44 @@ import androidx.annotation.ColorInt
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.graphics.ColorUtils
import androidx.datastore.preferences.core.booleanPreferencesKey
import com.google.android.material.color.DynamicColors
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
import kotlin.math.roundToInt
val KeyBordersSetting = booleanPreferencesKey("keyBorders")
val HiddenKeysSetting = booleanPreferencesKey("hiddenKeys")
val KeyHintsSetting = booleanPreferencesKey("keyHints")
fun adjustColorBrightnessForContrast(bgColor: Int, fgColor: Int, desiredContrast: Float, adjustSaturation: Boolean = false): Int {
// Convert RGB colors to HSL
val bgHSL = FloatArray(3)
ColorUtils.colorToHSL(bgColor, bgHSL)
val fgHSL = FloatArray(3)
ColorUtils.colorToHSL(fgColor, fgHSL)
// Estimate the adjustment needed in lightness to achieve the desired contrast
// This is a simplified approach and may not be perfectly accurate
val lightnessAdjustment = (desiredContrast - 1) / 10.0f // Simplified and heuristic-based adjustment
// Adjust the background color's lightness
bgHSL[2] = bgHSL[2] + lightnessAdjustment
bgHSL[2] = bgHSL[2].coerceIn(0f, 1f) // Ensure the lightness stays within valid range
if(adjustSaturation) {
bgHSL[1] = (bgHSL[1] + lightnessAdjustment).coerceIn(0f, 1f)
}
// Convert back to RGB and return the adjusted color
return ColorUtils.HSLToColor(bgHSL)
}
class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorScheme? = null) :
DynamicThemeProvider {
override val primaryKeyboardColor: Int
@ -88,6 +118,10 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
addState(stateSet, drawable)
}
val expertMode: Boolean
val keyBorders: Boolean
val showKeyHints: Boolean
init {
val colorScheme = if(overrideColorScheme != null) {
overrideColorScheme
@ -99,6 +133,9 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
dynamicLightColorScheme(dCtx)
}
expertMode = context.getSettingBlocking(HiddenKeysSetting, false)
keyBorders = context.getSettingBlocking(KeyBordersSetting, false)
showKeyHints = context.getSettingBlocking(KeyHintsSetting, false)
val primary = colorScheme.primary.toArgb()
val secondary = colorScheme.secondary.toArgb()
@ -112,11 +149,13 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
val onPrimaryContainer = colorScheme.onPrimaryContainer.toArgb()
val onPrimary = colorScheme.onPrimary.toArgb()
val onPrimaryThird = colorScheme.onPrimary.copy(alpha = 0.33f).toArgb()
val onSecondary = colorScheme.onSecondary.toArgb()
val onBackground = colorScheme.onBackground.toArgb()
val onBackgroundHalf = colorScheme.onBackground.copy(alpha = 0.5f).toArgb()
val onBackgroundThird = colorScheme.onBackground.copy(alpha = 0.33f).toArgb()
val transparent = Color.TRANSPARENT
val transparent = Color.Transparent.toArgb()
colors[R.styleable.Keyboard_Key_keyTextColor] = onBackground
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = onBackgroundHalf
@ -139,7 +178,7 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
}
}
// No good replacements for these cons yet, but we set them anyway for setTint
// No good replacements for these icons yet, but we set them anyway for setTint
overrideDrawable(R.styleable.Keyboard_iconEnterKey, R.drawable.sym_keyboard_return_lxx_light, onPrimary)
overrideDrawable(R.styleable.Keyboard_iconGoKey, R.drawable.sym_keyboard_go_lxx_light, onPrimary)
overrideDrawable(R.styleable.Keyboard_iconNextKey, R.drawable.sym_keyboard_next_lxx_light, onPrimary)
@ -155,14 +194,69 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
overrideDrawable(R.styleable.Keyboard_iconShiftKey, R.drawable.shift, onBackground)
overrideDrawable(R.styleable.Keyboard_iconShiftKeyShifted, R.drawable.shiftshifted, onBackground)
primaryKeyboardColor = background
if(!showKeyHints) {
colors[R.styleable.Keyboard_Key_keyHintLetterColor] = transparent
colors[R.styleable.Keyboard_Key_keyHintLabelColor] = transparent
}
keyboardBackground = coloredRectangle(background)
if(expertMode) {
colors[R.styleable.Keyboard_Key_keyTextColor] = transparent
colors[R.styleable.Keyboard_Key_keyTextInactivatedColor] = transparent
colors[R.styleable.Keyboard_Key_keyHintLetterColor] = transparent
colors[R.styleable.Keyboard_Key_keyHintLabelColor] = transparent
colors[R.styleable.MainKeyboardView_languageOnSpacebarTextColor] = transparent
// Note: We don't fully hide some things, but fade them away as they may be important landmarks
colors[R.styleable.Keyboard_Key_functionalTextColor] = onBackgroundThird
overrideDrawable(R.styleable.Keyboard_iconShiftKey, R.drawable.shift, onBackgroundThird)
overrideDrawable(R.styleable.Keyboard_iconShiftKeyShifted, R.drawable.shiftshifted, onBackgroundThird)
overrideDrawable(R.styleable.Keyboard_iconDeleteKey, R.drawable.delete, onBackgroundThird)
overrideDrawable(R.styleable.Keyboard_iconEmojiNormalKey, R.drawable.smile, transparent)
}
/*
primaryKeyboardColor = if(keyBorders) {
colorScheme.outline.copy(alpha = 0.33f).compositeOver(colorScheme.background).toArgb()
} else {
background
}
*/
primaryKeyboardColor = if(keyBorders) {
colorScheme.background.toArgb()
} else {
colorScheme.surface.toArgb()
}
val ratio = 1.5f
val keyColor = if(keyBorders) {
var c = adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, ratio)
if(c == primaryKeyboardColor) {
// May happen if the color is already 100% white
c = adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, 1.0f / (ratio / 2.0f + 0.5f))
}
c
} else {
transparent
}
val functionalKeyColor = if(keyBorders) {
//var c = adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, 1.0f / ratio, adjustSaturation = true)
//if(true || c == primaryKeyboardColor) {
// // May happen if the color is already 100% black
var c = adjustColorBrightnessForContrast(primaryKeyboardColor, primaryKeyboardColor, ratio / 2.0f + 0.5f, adjustSaturation = true)
//}
c
} else {
transparent
}
keyboardBackground = coloredRectangle(primaryKeyboardColor)
keyBackground = StateListDrawable().apply {
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_active),
coloredRoundedRectangle(primary, dp(8.dp)).apply {
setSize(dp(64.dp).toInt(), dp(48.dp).toInt())
coloredRoundedRectangle(primary, dp(128.dp)).apply {
if(!keyBorders) {
setSize(dp(64.dp).toInt(), dp(48.dp).toInt())
}
}
)
@ -171,29 +265,61 @@ class BasicThemeProvider(val context: Context, val overrideColorScheme: ColorSch
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_checkable),
coloredRectangle(transparent)
if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_first),
if(keyBorders) {
coloredRoundedRectangle(functionalKeyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(android.R.attr.state_empty),
coloredRectangle(transparent)
if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
addStateWithHighlightLayerOnPressed(highlight, intArrayOf(),
coloredRectangle(transparent)
if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(8.dp))
} else {
coloredRectangle(transparent)
}
)
}
val spaceCornerRadius = if(keyBorders) {
8.dp
} else {
48.dp
}
val spaceDrawable = if(keyBorders) {
coloredRoundedRectangle(keyColor, dp(spaceCornerRadius))
} else {
coloredRoundedRectangle(highlight, dp(spaceCornerRadius))
}
spaceBarBackground = StateListDrawable().apply {
addState(intArrayOf(android.R.attr.state_pressed),
LayerDrawable(
arrayOf(
coloredRoundedRectangle(highlight, dp(32.dp)),
coloredRoundedRectangle(highlight, dp(32.dp))
spaceDrawable,
coloredRoundedRectangle(highlight, dp(spaceCornerRadius))
)
)
)
addState(intArrayOf(),
coloredRoundedRectangle(highlight, dp(32.dp))
spaceDrawable
)
}

View File

@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@ -32,7 +33,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.futo.inputmethod.latin.uix.HiddenKeysSetting
import org.futo.inputmethod.latin.uix.KeyBordersSetting
import org.futo.inputmethod.latin.uix.KeyHintsSetting
import org.futo.inputmethod.latin.uix.SettingsKey
import org.futo.inputmethod.latin.uix.THEME_KEY
import org.futo.inputmethod.latin.uix.settings.SettingToggleDataStore
import org.futo.inputmethod.latin.uix.settings.useDataStore
import org.futo.inputmethod.latin.uix.theme.ThemeOption
import org.futo.inputmethod.latin.uix.theme.ThemeOptionKeys
@ -174,23 +180,49 @@ fun ThemePicker(onSelected: (ThemeOption) -> Unit) {
}
}
LazyVerticalGrid(
modifier = Modifier.fillMaxWidth(),
columns = GridCells.Adaptive(minSize = 172.dp)
) {
items(availableThemeOptions.count()) {
val themeOption = availableThemeOptions[it].second
ThemePreview(themeOption, isSelected = themeOption.key == currentTheme) {
onSelected(themeOption)
Column {
LazyVerticalGrid(
modifier = Modifier.fillMaxWidth(),
columns = GridCells.Adaptive(minSize = 172.dp)
) {
item(span = { GridItemSpan(maxCurrentLineSpan) }) {
SettingToggleDataStore(
title = "Key borders",
setting = SettingsKey(KeyBordersSetting, false)
)
}
}
item(span = { GridItemSpan(maxCurrentLineSpan) }) {
SettingToggleDataStore(
title = "Show symbol hints",
subtitle = "",
setting = SettingsKey(KeyHintsSetting, false)
)
}
item(span = { GridItemSpan(maxCurrentLineSpan) }) {
SettingToggleDataStore(
title = "Expert mode",
subtitle = "Hides all keys. Touch typists only",
setting = SettingsKey(HiddenKeysSetting, false)
)
}
items(availableThemeOptions.count()) {
val themeOption = availableThemeOptions[it].second
item {
AddCustomThemeButton {
// TODO: Custom themes
val toast = Toast.makeText(context, "Custom themes coming eventually", Toast.LENGTH_SHORT)
toast.show()
ThemePreview(themeOption, isSelected = themeOption.key == currentTheme) {
onSelected(themeOption)
}
}
item {
AddCustomThemeButton {
// TODO: Custom themes
val toast = Toast.makeText(
context,
"Custom themes coming eventually",
Toast.LENGTH_SHORT
)
toast.show()
}
}
}
}