Add KeyboardSizingCalculator

This commit is contained in:
Aleksandras Kostarevas 2024-09-21 19:31:34 +03:00
parent 9f40bfd574
commit d692e7b96a
14 changed files with 293 additions and 61 deletions

View File

@ -227,6 +227,7 @@ dependencies {
implementation 'androidx.datastore:datastore-preferences:1.1.1' implementation 'androidx.datastore:datastore-preferences:1.1.1'
implementation 'androidx.autofill:autofill:1.1.0' implementation 'androidx.autofill:autofill:1.1.0'
implementation 'androidx.window:window:1.3.0'
stableImplementation 'ch.acra:acra-mail:5.11.1' stableImplementation 'ch.acra:acra-mail:5.11.1'
stableImplementation 'ch.acra:acra-dialog:5.11.1' stableImplementation 'ch.acra:acra-dialog:5.11.1'

View File

@ -0,0 +1,11 @@
package org.futo.inputmethod.latin
import androidx.window.layout.FoldingFeature
data class FoldingOptions(
val feature: FoldingFeature?
)
interface FoldStateProvider {
val foldState: FoldingOptions
}

View File

@ -16,6 +16,7 @@
package org.futo.inputmethod.keyboard; package org.futo.inputmethod.keyboard;
import android.graphics.Rect;
import android.util.SparseArray; import android.util.SparseArray;
import org.futo.inputmethod.keyboard.internal.KeyVisualAttributes; import org.futo.inputmethod.keyboard.internal.KeyVisualAttributes;
@ -64,8 +65,8 @@ public class Keyboard {
/** Base width of the keyboard, used to calculate keys' width */ /** Base width of the keyboard, used to calculate keys' width */
public final int mBaseWidth; public final int mBaseWidth;
/** The padding above the keyboard */ /** left, right, top, bottom specify respective padding */
public final int mTopPadding; public final Rect mPadding;
/** Default gap between rows */ /** Default gap between rows */
public final int mVerticalGap; public final int mVerticalGap;
@ -112,7 +113,7 @@ public class Keyboard {
mMoreKeysTemplate = params.mMoreKeysTemplate; mMoreKeysTemplate = params.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn; mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
mKeyVisualAttributes = params.mKeyVisualAttributes; mKeyVisualAttributes = params.mKeyVisualAttributes;
mTopPadding = params.mTopPadding; mPadding = new Rect(params.mLeftPadding, params.mTopPadding, params.mRightPadding, params.mBottomPadding);
mVerticalGap = params.mVerticalGap; mVerticalGap = params.mVerticalGap;
mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys)); mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys));
@ -140,7 +141,7 @@ public class Keyboard {
mMoreKeysTemplate = keyboard.mMoreKeysTemplate; mMoreKeysTemplate = keyboard.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = keyboard.mMaxMoreKeysKeyboardColumn; mMaxMoreKeysKeyboardColumn = keyboard.mMaxMoreKeysKeyboardColumn;
mKeyVisualAttributes = keyboard.mKeyVisualAttributes; mKeyVisualAttributes = keyboard.mKeyVisualAttributes;
mTopPadding = keyboard.mTopPadding; mPadding = keyboard.mPadding;
mVerticalGap = keyboard.mVerticalGap; mVerticalGap = keyboard.mVerticalGap;
mSortedKeys = keyboard.mSortedKeys; mSortedKeys = keyboard.mSortedKeys;
@ -178,7 +179,6 @@ public class Keyboard {
/** /**
* Return the sorted list of keys of this keyboard. * Return the sorted list of keys of this keyboard.
* The keys are sorted from top-left to bottom-right order. * The keys are sorted from top-left to bottom-right order.
* The list may contain {@link Key.Spacer} object as well.
* @return the sorted unmodifiable list of {@link Key}s of this keyboard. * @return the sorted unmodifiable list of {@link Key}s of this keyboard.
*/ */
@Nonnull @Nonnull

View File

@ -17,8 +17,8 @@
package org.futo.inputmethod.keyboard; package org.futo.inputmethod.keyboard;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -46,8 +46,13 @@ import org.futo.inputmethod.latin.settings.SettingsValues;
import org.futo.inputmethod.latin.utils.LanguageOnSpacebarUtils; import org.futo.inputmethod.latin.utils.LanguageOnSpacebarUtils;
import org.futo.inputmethod.latin.utils.ResourceUtils; import org.futo.inputmethod.latin.utils.ResourceUtils;
import org.futo.inputmethod.latin.utils.ScriptUtils; import org.futo.inputmethod.latin.utils.ScriptUtils;
import org.futo.inputmethod.v2keyboard.ComputedKeyboardSize;
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetKt;
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2; import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2;
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2Params; import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2Params;
import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator;
import org.futo.inputmethod.v2keyboard.RegularKeyboardSize;
import org.futo.inputmethod.v2keyboard.SplitKeyboardSize;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -124,18 +129,48 @@ public final class KeyboardSwitcher implements SwitchActions {
final int currentAutoCapsState, final int currentRecapitalizeState) { final int currentAutoCapsState, final int currentRecapitalizeState) {
final Resources res = mThemeContext.getResources(); final Resources res = mThemeContext.getResources();
final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
final RichInputMethodSubtype subtype = mRichImm.getCurrentSubtype(); final RichInputMethodSubtype subtype = mRichImm.getCurrentSubtype();
String layoutSetName = subtype.getKeyboardLayoutSetName();
String overrideLayoutSet = KeyboardLayoutSetKt.getPrimaryLayoutOverride(editorInfo);
if(overrideLayoutSet != null) {
layoutSetName = overrideLayoutSet;
}
final KeyboardSizingCalculator sizingCalculator = new KeyboardSizingCalculator(mLatinIMELegacy.getInputMethodService());
final ComputedKeyboardSize computedSize = sizingCalculator.calculate(layoutSetName, settingsValues.mIsNumberRowEnabled);
int keyboardWidth = 0;
int keyboardHeight = 0;
int splitLayoutWidth = 0;
Rect padding = new Rect();
if(computedSize instanceof SplitKeyboardSize) {
keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
keyboardHeight = ((SplitKeyboardSize) computedSize).getHeight();
splitLayoutWidth = ((SplitKeyboardSize) computedSize).getSplitLayoutWidth();
padding = ((SplitKeyboardSize) computedSize).getPadding();
}else if(computedSize instanceof RegularKeyboardSize) {
keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
keyboardHeight = ((RegularKeyboardSize) computedSize).getHeight();
padding = ((RegularKeyboardSize) computedSize).getPadding();
}
final KeyboardLayoutSetV2Params params = new KeyboardLayoutSetV2Params( final KeyboardLayoutSetV2Params params = new KeyboardLayoutSetV2Params(
keyboardWidth, keyboardWidth,
null, // Auto keyboard height keyboardHeight,
subtype.getKeyboardLayoutSetName(), padding,
layoutSetName,
subtype.getLocale(), subtype.getLocale(),
editorInfo == null ? new EditorInfo() : editorInfo, editorInfo == null ? new EditorInfo() : editorInfo,
settingsValues.mIsNumberRowEnabled, settingsValues.mIsNumberRowEnabled,
4.0f, sizingCalculator.calculateGap(),
res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE, splitLayoutWidth != 0,
splitLayoutWidth,
settingsValues.mShowsActionKey ? settingsValues.mActionKeyId : null, settingsValues.mShowsActionKey ? settingsValues.mActionKeyId : null,
LongPressKeySettings.load(mThemeContext) LongPressKeySettings.load(mThemeContext)
); );
@ -176,7 +211,6 @@ public final class KeyboardSwitcher implements SwitchActions {
final Keyboard oldKeyboard = keyboardView.getKeyboard(); final Keyboard oldKeyboard = keyboardView.getKeyboard();
final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(element); final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(element);
keyboardView.setKeyboard(newKeyboard); keyboardView.setKeyboard(newKeyboard);
mCurrentInputView.setKeyboardTopPadding(newKeyboard.mTopPadding);
keyboardView.setKeyPreviewPopupEnabled( keyboardView.setKeyPreviewPopupEnabled(
currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay); currentSettingsValues.mKeyPreviewPopupDismissDelay);

View File

@ -401,6 +401,8 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
} else { } else {
mAccessibilityDelegate = null; mAccessibilityDelegate = null;
} }
setPadding(keyboard.mPadding.left, keyboard.mPadding.top, keyboard.mPadding.right, keyboard.mPadding.bottom);
} }
/** /**

View File

@ -41,10 +41,6 @@ public final class InputView extends FrameLayout {
mMainKeyboardView = (MainKeyboardView) findViewById(R.id.keyboard_view); mMainKeyboardView = (MainKeyboardView) findViewById(R.id.keyboard_view);
} }
public void setKeyboardTopPadding(final int keyboardTopPadding) {
}
@Override @Override
protected boolean dispatchHoverEvent(final MotionEvent event) { protected boolean dispatchHoverEvent(final MotionEvent event) {
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled() if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()

View File

@ -19,10 +19,8 @@ import android.view.inputmethod.InlineSuggestionsResponse
import android.view.inputmethod.InputConnection import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputMethodSubtype import android.view.inputmethod.InputMethodSubtype
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
@ -58,7 +56,6 @@ import org.futo.inputmethod.latin.uix.DynamicThemeProvider
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
import org.futo.inputmethod.latin.uix.EmojiTracker.unuseEmoji import org.futo.inputmethod.latin.uix.EmojiTracker.unuseEmoji
import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji
import org.futo.inputmethod.latin.uix.KeyboardBottomOffsetSetting
import org.futo.inputmethod.latin.uix.KeyboardColorScheme import org.futo.inputmethod.latin.uix.KeyboardColorScheme
import org.futo.inputmethod.latin.uix.SUGGESTION_BLACKLIST import org.futo.inputmethod.latin.uix.SUGGESTION_BLACKLIST
import org.futo.inputmethod.latin.uix.THEME_KEY import org.futo.inputmethod.latin.uix.THEME_KEY
@ -70,7 +67,6 @@ import org.futo.inputmethod.latin.uix.deferSetSetting
import org.futo.inputmethod.latin.uix.differsFrom import org.futo.inputmethod.latin.uix.differsFrom
import org.futo.inputmethod.latin.uix.getSetting import org.futo.inputmethod.latin.uix.getSetting
import org.futo.inputmethod.latin.uix.getSettingBlocking import org.futo.inputmethod.latin.uix.getSettingBlocking
import org.futo.inputmethod.latin.uix.getSettingFlow
import org.futo.inputmethod.latin.uix.isDirectBootUnlocked import org.futo.inputmethod.latin.uix.isDirectBootUnlocked
import org.futo.inputmethod.latin.uix.setSetting import org.futo.inputmethod.latin.uix.setSetting
import org.futo.inputmethod.latin.uix.theme.ThemeOption import org.futo.inputmethod.latin.uix.theme.ThemeOption
@ -79,6 +75,8 @@ import org.futo.inputmethod.latin.uix.theme.applyWindowColors
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
import org.futo.inputmethod.updates.scheduleUpdateCheckingJob import org.futo.inputmethod.updates.scheduleUpdateCheckingJob
import org.futo.inputmethod.v2keyboard.KeyboardSizeSettingKind
import org.futo.inputmethod.v2keyboard.KeyboardSizeStateProvider
private class UnlockedBroadcastReceiver(val onDeviceUnlocked: () -> Unit) : BroadcastReceiver() { private class UnlockedBroadcastReceiver(val onDeviceUnlocked: () -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
@ -90,7 +88,8 @@ private class UnlockedBroadcastReceiver(val onDeviceUnlocked: () -> Unit) : Broa
} }
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner, class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner { LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, FoldStateProvider,
KeyboardSizeStateProvider {
private lateinit var mLifecycleRegistry: LifecycleRegistry private lateinit var mLifecycleRegistry: LifecycleRegistry
private lateinit var mViewModelStore: ViewModelStore private lateinit var mViewModelStore: ViewModelStore
@ -205,7 +204,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
} }
} }
private fun invalidateKeyboard(refreshSettings: Boolean = false) { fun invalidateKeyboard(refreshSettings: Boolean = false) {
settingsRefreshRequired = settingsRefreshRequired || refreshSettings settingsRefreshRequired = settingsRefreshRequired || refreshSettings
if(!uixManager.isMainKeyboardHidden) { if(!uixManager.isMainKeyboardHidden) {
@ -427,12 +426,12 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
} }
} }
val padding = getSettingFlow(KeyboardBottomOffsetSetting).collectAsState(initial = 0.0f)
key(legacyInputView) { key(legacyInputView) {
AndroidView(factory = { AndroidView(factory = {
legacyInputView!! legacyInputView!!
}, modifier = modifier.padding(0.dp, 0.dp, 0.dp, padding.value.dp), onRelease = { }, modifier = modifier, onRelease = {
val view = it as InputView val view = it as InputView
view.deallocateMemory() view.deallocateMemory()
view.removeAllViews() view.removeAllViews()
@ -762,4 +761,19 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
// TODO: Spell checker service // TODO: Spell checker service
} }
override val foldState: FoldingOptions
get() = uixManager.foldingOptions.value
override val currentSizeState: KeyboardSizeSettingKind
get() = when {
foldState.feature != null ->
KeyboardSizeSettingKind.FoldableInnerDisplay
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE ->
KeyboardSizeSettingKind.Landscape
else ->
KeyboardSizeSettingKind.Portrait
}
} }

View File

@ -1,6 +1,6 @@
package org.futo.inputmethod.latin.uix package org.futo.inputmethod.latin.uix
import android.content.res.Configuration import android.graphics.Rect
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -109,12 +109,14 @@ fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp, locale: Locale? = null
KeyboardLayoutSetV2Params( KeyboardLayoutSetV2Params(
width = widthPx, width = widthPx,
height = heightPx, height = heightPx,
padding = Rect(),
gap = 4.0f, gap = 4.0f,
keyboardLayoutSet = id, keyboardLayoutSet = id,
locale = loc ?: Locale.ENGLISH, locale = loc ?: Locale.ENGLISH,
editorInfo = editorInfo, editorInfo = editorInfo,
numberRow = numberRow, numberRow = numberRow,
useSplitLayout = isLandscape, useSplitLayout = isLandscape,
splitLayoutWidth = widthPx * 2 / 3,
bottomActionKey = null bottomActionKey = null
) )
) )

View File

@ -22,10 +22,11 @@ import kotlinx.coroutines.delay
import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.Action import org.futo.inputmethod.latin.uix.Action
import org.futo.inputmethod.latin.uix.ActionWindow import org.futo.inputmethod.latin.uix.ActionWindow
import org.futo.inputmethod.latin.uix.settings.ScreenTitle import org.futo.inputmethod.latin.uix.LocalFoldingState
import org.futo.inputmethod.latin.uix.settings.ScrollableList import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.theme.ThemeOptions import org.futo.inputmethod.latin.uix.theme.ThemeOptions
import org.futo.inputmethod.latin.uix.theme.Typography import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.v2keyboard.KeyboardSizeStateProvider
val DebugLabel = Typography.labelSmall.copy(fontFamily = FontFamily.Monospace) val DebugLabel = Typography.labelSmall.copy(fontFamily = FontFamily.Monospace)
val DebugTitle = Typography.titleSmall.copy(fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold) val DebugTitle = Typography.titleSmall.copy(fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold)
@ -190,6 +191,8 @@ val MemoryDebugAction = Action(
} }
} }
val foldingState = LocalFoldingState.current
ScrollableList { ScrollableList {
Text("Editor Info", style = DebugTitle) Text("Editor Info", style = DebugTitle)
latinIme.currentInputEditorInfo?.let { info -> latinIme.currentInputEditorInfo?.let { info ->
@ -228,6 +231,16 @@ val MemoryDebugAction = Action(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text("Screen State Info", style = DebugTitle)
Text("size mode = ${(manager.getContext() as KeyboardSizeStateProvider).currentSizeState}", style = DebugLabel)
Text("Fold State", style = DebugTitle)
Text("state = ${foldingState.feature?.state}", style = DebugLabel)
Text("orientation = ${foldingState.feature?.orientation}", style = DebugLabel)
Text("isSeparating = ${foldingState.feature?.isSeparating}", style = DebugLabel)
Text("occlusionType = ${foldingState.feature?.occlusionType}", style = DebugLabel)
Spacer(modifier = Modifier.height(8.dp))
Text("Memory Use", style = DebugTitle) Text("Memory Use", style = DebugTitle)
state.value.forEach { state.value.forEach {
val value = it.value.toInt().toFloat() / 1000.0f val value = it.value.toInt().toFloat() / 1000.0f

View File

@ -1,6 +1,7 @@
package org.futo.inputmethod.v2keyboard package org.futo.inputmethod.v2keyboard
import android.content.Context import android.content.Context
import android.graphics.Rect
import android.text.InputType import android.text.InputType
import android.util.Log import android.util.Log
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
@ -20,7 +21,6 @@ import org.futo.inputmethod.latin.uix.getSettingBlocking
import org.futo.inputmethod.latin.utils.InputTypeUtils import org.futo.inputmethod.latin.utils.InputTypeUtils
import org.futo.inputmethod.latin.utils.ResourceUtils import org.futo.inputmethod.latin.utils.ResourceUtils
import java.util.Locale import java.util.Locale
import kotlin.math.roundToInt
@Serializable @Serializable
enum class Script(val id: Int, val iso4letterCode: String) { enum class Script(val id: Int, val iso4letterCode: String) {
@ -71,24 +71,25 @@ private fun EditorInfo.getPrivateImeOptions(): Map<String, String> {
return options return options
} }
private fun EditorInfo.getPrimaryLayoutOverride(): String? = fun getPrimaryLayoutOverride(editorInfo: EditorInfo?): String? {
getPrivateImeOptions()["org.futo.inputmethod.latin.ForceLayout"] return editorInfo?.getPrivateImeOptions()?.get("org.futo.inputmethod.latin.ForceLayout")
}
data class KeyboardLayoutSetV2Params( data class KeyboardLayoutSetV2Params(
val width: Int, val width: Int,
val height: Int?, val height: Int,
val padding: Rect,
val keyboardLayoutSet: String, val keyboardLayoutSet: String,
val locale: Locale, val locale: Locale,
val editorInfo: EditorInfo?, val editorInfo: EditorInfo?,
val numberRow: Boolean, val numberRow: Boolean,
val gap: Float = 4.0f, val gap: Float = 4.0f,
val useSplitLayout: Boolean, val useSplitLayout: Boolean,
val splitLayoutWidth: Int,
val bottomActionKey: Int?, val bottomActionKey: Int?,
val longPressKeySettings: LongPressKeySettings? = null val longPressKeySettings: LongPressKeySettings? = null
) )
class KeyboardLayoutSetV2 internal constructor( class KeyboardLayoutSetV2 internal constructor(
private val context: Context, private val context: Context,
private val params: KeyboardLayoutSetV2Params private val params: KeyboardLayoutSetV2Params
@ -183,31 +184,22 @@ class KeyboardLayoutSetV2 internal constructor(
NumberRowMode.AlwaysDisabled -> false NumberRowMode.AlwaysDisabled -> false
} }
private val widthMinusPadding = params.width - params.padding.left - params.padding.right
private val heightMinusPadding = params.height - params.padding.top - params.padding.bottom
private val singularRowHeight: Double private val singularRowHeight: Double
get() = params.height?.let { it / 4.0 } ?: run { get() = heightMinusPadding?.let { it / 4.0 } ?: run {
(ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) * (ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) *
keyboardHeightMultiplier keyboardHeightMultiplier
} }
private fun getRecommendedKeyboardHeight(): Int {
val numRows = 4.0 +
((mainLayout.effectiveRows.size - 5) / 2.0).coerceAtLeast(0.0) +
if(isNumberRowActive) { 0.5 } else { 0.0 }
// Clamp if necessary (disabled for now)
if(false && params.height == null) {
return ResourceUtils.clampKeyboardHeight(context.resources, (singularRowHeight * numRows).roundToInt())
} else {
return (singularRowHeight * numRows).roundToInt()
}
}
fun getKeyboard(element: KeyboardLayoutElement): Keyboard { fun getKeyboard(element: KeyboardLayoutElement): Keyboard {
val keyboardId = KeyboardId( val keyboardId = KeyboardId(
params.keyboardLayoutSet, params.keyboardLayoutSet,
forcedLocale ?: params.locale, forcedLocale ?: params.locale,
params.width, widthMinusPadding,
params.height ?: getRecommendedKeyboardHeight(), heightMinusPadding,
keyboardMode, keyboardMode,
element.elementId, element.elementId,
editorInfo, editorInfo,
@ -231,6 +223,8 @@ class KeyboardLayoutSetV2 internal constructor(
val layoutParams = LayoutParams( val layoutParams = LayoutParams(
gap = params.gap.dp, gap = params.gap.dp,
useSplitLayout = params.useSplitLayout, useSplitLayout = params.useSplitLayout,
splitLayoutWidth = params.splitLayoutWidth,
padding = params.padding,
standardRowHeight = singularRowHeight, standardRowHeight = singularRowHeight,
element = element element = element
) )

View File

@ -0,0 +1,158 @@
package org.futo.inputmethod.v2keyboard
import android.content.Context
import android.graphics.Rect
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.window.layout.FoldingFeature
import org.futo.inputmethod.latin.FoldStateProvider
import org.futo.inputmethod.latin.uix.SettingsKey
import org.futo.inputmethod.latin.uix.getSettingBlocking
import org.futo.inputmethod.latin.utils.ResourceUtils
import kotlin.math.roundToInt
interface KeyboardSizeStateProvider {
val currentSizeState: KeyboardSizeSettingKind
}
sealed class ComputedKeyboardSize()
class RegularKeyboardSize(val height: Int, val padding: Rect) : ComputedKeyboardSize()
class SplitKeyboardSize(val height: Int, val padding: Rect, val splitLayoutWidth: Int) : ComputedKeyboardSize()
//class OneHandedKeyboardSize(val height: Int, val offset: Int, val sideInset: Int, val isLeft: Boolean, val width: Int): ComputedKeyboardSize()
//class FloatingKeyboardSize(val x: Int, val y: Int, val width: Int, val height: Int): ComputedKeyboardSize()
enum class KeyboardSizeSettingKind {
Portrait,
Landscape,
FoldableInnerDisplay
}
val SplitKeyboardSettings = mapOf(
KeyboardSizeSettingKind.Portrait to SettingsKey(
booleanPreferencesKey("split_keyboard_portrait"), false),
KeyboardSizeSettingKind.Landscape to SettingsKey(
booleanPreferencesKey("split_keyboard_landscape"), true),
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
booleanPreferencesKey("split_keyboard_fold"), true),
)
val KeyboardHeightSettings = mapOf(
KeyboardSizeSettingKind.Portrait to SettingsKey(
floatPreferencesKey("keyboardHeightMultiplier"), 1.0f),
KeyboardSizeSettingKind.Landscape to SettingsKey(
floatPreferencesKey("keyboard_height_landscape"), 0.9f),
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
floatPreferencesKey("keyboard_height_fold"), 0.67f),
)
val KeyboardOffsetSettings = mapOf(
KeyboardSizeSettingKind.Portrait to SettingsKey(
floatPreferencesKey("keyboard_offset_portrait"), 8.0f),
KeyboardSizeSettingKind.Landscape to SettingsKey(
floatPreferencesKey("keyboard_offset_landscape"), 0.0f),
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
floatPreferencesKey("keyboard_offset_fold"), 8.0f),
)
val KeyboardSideInsetSettings = mapOf(
KeyboardSizeSettingKind.Portrait to SettingsKey(
floatPreferencesKey("keyboard_inset_portrait"), 2.0f),
KeyboardSizeSettingKind.Landscape to SettingsKey(
floatPreferencesKey("keyboard_inset_landscape"), 8.0f),
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
floatPreferencesKey("keyboard_inset_fold"), 44.0f),
)
class KeyboardSizingCalculator(val context: Context) {
private val sizeStateProvider = context as KeyboardSizeStateProvider
private val foldStateProvider = context as FoldStateProvider
private fun isSplitKeyboard(mode: KeyboardSizeSettingKind): Boolean =
context.getSettingBlocking(SplitKeyboardSettings[mode]!!)
private fun heightMultiplier(mode: KeyboardSizeSettingKind): Float =
context.getSettingBlocking(KeyboardHeightSettings[mode]!!)
private fun bottomOffsetPx(mode: KeyboardSizeSettingKind): Int =
(context.getSettingBlocking(KeyboardOffsetSettings[mode]!!) * context.resources.displayMetrics.density).toInt()
private fun sideInsetPx(mode: KeyboardSizeSettingKind): Int =
(context.getSettingBlocking(KeyboardSideInsetSettings[mode]!!) * context.resources.displayMetrics.density).toInt()
private fun topPaddingPx(mode: KeyboardSizeSettingKind): Int =
(when(mode) {
KeyboardSizeSettingKind.Portrait -> 4.0f
KeyboardSizeSettingKind.Landscape -> 0.0f
KeyboardSizeSettingKind.FoldableInnerDisplay -> 8.0f
} * context.resources.displayMetrics.density).toInt()
fun calculate(layoutName: String, isNumberRowActive: Boolean): ComputedKeyboardSize {
val layout = LayoutManager.getLayout(context, layoutName)
val effectiveRowCount = layout.effectiveRows.size
val configuration = context.resources.configuration
val displayMetrics = context.resources.displayMetrics
val mode = sizeStateProvider.currentSizeState
val isSplit = isSplitKeyboard(mode)
val heightMultiplier = heightMultiplier(mode)
val bottomOffset = bottomOffsetPx(mode)
val sideInset = sideInsetPx(mode)
val topPadding = topPaddingPx(mode)
val singularRowHeight = (ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) * heightMultiplier
val numRows = 4.0 +
((effectiveRowCount - 5) / 2.0).coerceAtLeast(0.0) +
if(isNumberRowActive) { 0.5 } else { 0.0 }
println("Num rows; $numRows, $effectiveRowCount ($layoutName) ($layout)")
val recommendedHeight = numRows * singularRowHeight
val foldState = foldStateProvider.foldState.feature
return when {
// Special case: 50% screen height no matter the row count or settings
foldState != null && foldState.state == FoldingFeature.State.HALF_OPENED && foldState.orientation == FoldingFeature.Orientation.HORIZONTAL ->
SplitKeyboardSize(
displayMetrics.heightPixels / 2 - (displayMetrics.density * 80.0f).toInt(),
Rect(
(displayMetrics.density * 44.0f).roundToInt(),
(displayMetrics.density * 20.0f).roundToInt(),
(displayMetrics.density * 44.0f).roundToInt(),
(displayMetrics.density * 12.0f).roundToInt(),
),
displayMetrics.widthPixels * 3 / 5
)
isSplit -> SplitKeyboardSize(
recommendedHeight.roundToInt(),
Rect(sideInset, topPadding, sideInset, bottomOffset),
displayMetrics.widthPixels * 3 / 5)
else -> RegularKeyboardSize(
recommendedHeight.roundToInt(),
Rect(sideInset, topPadding, sideInset, bottomOffset),
)
}
}
fun calculateGap(): Float {
val displayMetrics = context.resources.displayMetrics
val widthDp = displayMetrics.widthPixels / displayMetrics.density
val heightDp = displayMetrics.heightPixels / displayMetrics.density
val minDp = Math.min(widthDp, heightDp)
return (minDp / 100.0f).coerceIn(3.0f, 6.0f)
}
}

View File

@ -1,6 +1,7 @@
package org.futo.inputmethod.v2keyboard package org.futo.inputmethod.v2keyboard
import android.content.Context import android.content.Context
import android.graphics.Rect
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import org.futo.inputmethod.keyboard.KeyConsts import org.futo.inputmethod.keyboard.KeyConsts
import org.futo.inputmethod.keyboard.internal.KeyboardLayoutElement import org.futo.inputmethod.keyboard.internal.KeyboardLayoutElement
@ -82,6 +83,8 @@ data class LayoutRow(
data class LayoutParams( data class LayoutParams(
val gap: Dp, val gap: Dp,
val useSplitLayout: Boolean, val useSplitLayout: Boolean,
val splitLayoutWidth: Int,
val padding: Rect,
val standardRowHeight: Double, val standardRowHeight: Double,
val element: KeyboardLayoutElement, val element: KeyboardLayoutElement,
) )
@ -144,7 +147,7 @@ data class LayoutEngine(
private val isSplitLayout = layoutParams.useSplitLayout private val isSplitLayout = layoutParams.useSplitLayout
private val layoutWidth = if(isSplitLayout) { private val layoutWidth = if(isSplitLayout) {
params.mId.mWidth * 2 / 3 layoutParams.splitLayoutWidth
} else { } else {
params.mId.mWidth params.mId.mWidth
} }
@ -526,10 +529,10 @@ data class LayoutEngine(
} }
private fun addRowAlignLeft(row: List<LayoutEntry>, y: Int, height: Int) private fun addRowAlignLeft(row: List<LayoutEntry>, y: Int, height: Int)
= addRow(row, 0.0f, y, height) = addRow(row, 0.0f + layoutParams.padding.left, y, height)
private fun addRowAlignRight(row: List<LayoutEntry>, y: Int, height: Int) { private fun addRowAlignRight(row: List<LayoutEntry>, y: Int, height: Int) {
val startingOffset = params.mId.mWidth - row.sumOf { it.widthPx.toDouble() }.toFloat() val startingOffset = params.mId.mWidth - row.sumOf { it.widthPx.toDouble() }.toFloat() + layoutParams.padding.left
addRow(row, startingOffset, y, height) addRow(row, startingOffset, y, height)
} }
@ -545,7 +548,7 @@ data class LayoutEngine(
} }
private fun addKeys(rows: List<LayoutRow>): Int { private fun addKeys(rows: List<LayoutRow>): Int {
var currentY = 0.0f var currentY = 0.0f + layoutParams.padding.top
rows.forEach { row -> rows.forEach { row ->
addRow(row, currentY.toInt()) addRow(row, currentY.toInt())
currentY += row.height currentY += row.height
@ -563,14 +566,14 @@ data class LayoutEngine(
val rows = computeRows(this.rows) val rows = computeRows(this.rows)
val totalKeyboardHeight = addKeys(rows).let { totalRowHeight.roundToInt() } val totalKeyboardHeight = addKeys(rows).let { totalRowHeight.roundToInt() } + layoutParams.padding.top + layoutParams.padding.bottom
params.mOccupiedHeight = totalKeyboardHeight - verticalGapPx.roundToInt() params.mOccupiedHeight = totalKeyboardHeight - verticalGapPx.roundToInt()
params.mOccupiedWidth = params.mId.mWidth params.mOccupiedWidth = params.mId.mWidth + layoutParams.padding.left + layoutParams.padding.right
params.mTopPadding = 0 params.mTopPadding = 0//layoutParams.padding.top
params.mBottomPadding = 0 params.mBottomPadding = 0//layoutParams.padding.bottom
params.mLeftPadding = 0 params.mLeftPadding = 0//layoutParams.padding.left
params.mRightPadding = 0 params.mRightPadding = 0//layoutParams.padding.right
params.mBaseWidth = params.mOccupiedWidth params.mBaseWidth = params.mOccupiedWidth
params.mDefaultKeyWidth = regularKeyWidth.roundToInt() params.mDefaultKeyWidth = regularKeyWidth.roundToInt()

View File

@ -18,6 +18,7 @@ package org.futo.inputmethod.keyboard;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@ -150,11 +151,11 @@ public abstract class KeyboardLayoutSetTestsBase extends AndroidTestCase {
return new KeyboardLayoutSetV2( return new KeyboardLayoutSetV2(
context, context,
new KeyboardLayoutSetV2Params( new KeyboardLayoutSetV2Params(
keyboardWidth, keyboardHeight, keyboardWidth, keyboardHeight, new Rect(),
richInputMethodSubtype.getKeyboardLayoutSetName(), richInputMethodSubtype.getKeyboardLayoutSetName(),
richInputMethodSubtype.getLocale(), richInputMethodSubtype.getLocale(),
editorInfo, false, editorInfo, false,
4.0f, splitLayoutEnabled, 4.0f, splitLayoutEnabled, 0,
languageSwitchKeyEnabled ? ActionRegistry.INSTANCE.actionStringIdToIdx("switch_language") : null, languageSwitchKeyEnabled ? ActionRegistry.INSTANCE.actionStringIdToIdx("switch_language") : null,
LongPressKeySettings.forTest() LongPressKeySettings.forTest()
) )

View File

@ -1,5 +1,6 @@
package org.futo.inputmethod.keyboard.internal package org.futo.inputmethod.keyboard.internal
import android.graphics.Rect
import android.test.AndroidTestCase import android.test.AndroidTestCase
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
@ -14,12 +15,14 @@ import kotlin.math.absoluteValue
class KeyboardLayoutSetV2Tests : AndroidTestCase() { class KeyboardLayoutSetV2Tests : AndroidTestCase() {
private val layoutParams = KeyboardLayoutSetV2Params( private val layoutParams = KeyboardLayoutSetV2Params(
width = 1024, width = 1024,
height = null, height = 1024,
padding = Rect(),
keyboardLayoutSet = "qwerty", keyboardLayoutSet = "qwerty",
locale = Locale.ENGLISH, locale = Locale.ENGLISH,
editorInfo = EditorInfo(), editorInfo = EditorInfo(),
numberRow = false, numberRow = false,
useSplitLayout = false, useSplitLayout = false,
splitLayoutWidth = 0,
bottomActionKey = null bottomActionKey = null
) )