diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml
index 255ca5416..ca7c7222e 100644
--- a/java/res/values/strings-uix.xml
+++ b/java/res/values/strings-uix.xml
@@ -16,6 +16,11 @@
All Actions
Bug Viewer
+ Left-Handed Keyboard
+ Right-Handed Keyboard
+ Split Keyboard
+ Floating Keyboard
+
Action Key
Pinned Action(s)
Favorite Actions
diff --git a/java/src/org/futo/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/org/futo/inputmethod/keyboard/KeyboardSwitcher.java
index 828312c7c..d00e64276 100644
--- a/java/src/org/futo/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/org/futo/inputmethod/keyboard/KeyboardSwitcher.java
@@ -140,40 +140,16 @@ public final class KeyboardSwitcher implements SwitchActions {
}
- final KeyboardSizingCalculator sizingCalculator = new KeyboardSizingCalculator(mLatinIMELegacy.getInputMethodService());
+ final KeyboardSizingCalculator sizingCalculator = ((LatinIME)mLatinIMELegacy.getInputMethodService()).getSizingCalculator();
final ComputedKeyboardSize computedSize = sizingCalculator.calculate(layoutSetName, settingsValues.mIsNumberRowEnabled);
- int keyboardWidth = 0;
- int keyboardHeight = 0;
-
- int splitLayoutWidth = 0;
-
- Rect padding = new Rect();
-
- Window window = mLatinIMELegacy.getInputMethodService().getWindow().getWindow();
-
- if(computedSize instanceof SplitKeyboardSize) {
- keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(window, res);
- keyboardHeight = ((SplitKeyboardSize) computedSize).getHeight();
- splitLayoutWidth = ((SplitKeyboardSize) computedSize).getSplitLayoutWidth();
- padding = ((SplitKeyboardSize) computedSize).getPadding();
- }else if(computedSize instanceof RegularKeyboardSize) {
- keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(window, res);
- keyboardHeight = ((RegularKeyboardSize) computedSize).getHeight();
- padding = ((RegularKeyboardSize) computedSize).getPadding();
- }
-
final KeyboardLayoutSetV2Params params = new KeyboardLayoutSetV2Params(
- keyboardWidth,
- keyboardHeight,
- padding,
+ computedSize,
layoutSetName,
subtype.getLocale(),
editorInfo == null ? new EditorInfo() : editorInfo,
settingsValues.mIsNumberRowEnabled,
sizingCalculator.calculateGap(),
- splitLayoutWidth != 0,
- splitLayoutWidth,
settingsValues.mShowsActionKey ? settingsValues.mActionKeyId : null,
LongPressKeySettings.load(mThemeContext)
);
diff --git a/java/src/org/futo/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/org/futo/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index 1da993bff..6875ef3ed 100644
--- a/java/src/org/futo/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/org/futo/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -132,16 +132,8 @@ public final class KeyPreviewChoreographer {
final int keyPreviewPosition;
int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
+ CoordinateUtils.x(originCoords);
- if (previewX < 0) {
- previewX = 0;
- keyPreviewPosition = KeyPreviewView.POSITION_LEFT;
- } else if (previewX > keyboardViewWidth - previewWidth) {
- previewX = keyboardViewWidth - previewWidth;
- keyPreviewPosition = KeyPreviewView.POSITION_RIGHT;
- } else {
- keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
- }
- final boolean hasMoreKeys = (key.getMoreKeys() != null);
+ keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
+ final boolean hasMoreKeys = !key.getMoreKeys().isEmpty();
keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition);
// The key preview is placed vertically above the top edge of the parent key with an
// arbitrary offset.
diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt
index a741ea0c0..4d9b72996 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIME.kt
+++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt
@@ -21,10 +21,14 @@ import android.view.inputmethod.InputMethodSubtype
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
@@ -75,28 +79,27 @@ import org.futo.inputmethod.latin.uix.theme.applyWindowColors
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
import org.futo.inputmethod.updates.scheduleUpdateCheckingJob
+import org.futo.inputmethod.v2keyboard.ComputedKeyboardSize
+import org.futo.inputmethod.v2keyboard.FloatingKeyboardSize
+import org.futo.inputmethod.v2keyboard.KeyboardSettings
import org.futo.inputmethod.v2keyboard.KeyboardSizeSettingKind
import org.futo.inputmethod.v2keyboard.KeyboardSizeStateProvider
+import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
+import org.futo.inputmethod.v2keyboard.getHeight
private class UnlockedBroadcastReceiver(val onDeviceUnlocked: () -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- println("Unlocked Broadcast Receiver: ${intent?.action}")
if (intent?.action == Intent.ACTION_USER_UNLOCKED) {
onDeviceUnlocked()
}
}
}
-class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
- LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, FoldStateProvider,
- KeyboardSizeStateProvider {
-
+open class InputMethodServiceCompose : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
private lateinit var mLifecycleRegistry: LifecycleRegistry
private lateinit var mViewModelStore: ViewModelStore
private lateinit var mSavedStateRegistryController: SavedStateRegistryController
-
-
fun setOwners() {
val decorView = window.window?.decorView
if (decorView?.findViewTreeLifecycleOwner() == null) {
@@ -110,11 +113,59 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
}
+ override fun onCreate() {
+ super.onCreate()
+
+ mLifecycleRegistry = LifecycleRegistry(this)
+ mLifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
+
+ mViewModelStore = ViewModelStore()
+
+ mSavedStateRegistryController = SavedStateRegistryController.create(this)
+ mSavedStateRegistryController.performRestore(null)
+
+ mLifecycleRegistry.currentState = Lifecycle.State.CREATED
+ }
+
+ override fun onStartInputView(editorInfo: EditorInfo?, restarting: Boolean) {
+ super.onStartInputView(editorInfo, restarting)
+
+ mLifecycleRegistry.currentState = Lifecycle.State.STARTED
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mLifecycleRegistry.currentState = Lifecycle.State.DESTROYED
+ }
+
+ override val lifecycle: Lifecycle
+ get() = mLifecycleRegistry
+ override val savedStateRegistry: SavedStateRegistry
+ get() = mSavedStateRegistryController.savedStateRegistry
+ override val viewModelStore: ViewModelStore
+ get() = mViewModelStore
+
+ internal var composeView: ComposeView? = null
+
+ override fun onCreateInputView(): View =
+ ComposeView(this).apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setParentCompositionContext(null)
+
+ setOwners()
+
+ composeView = this
+ }
+}
+
+class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripController,
+ DynamicThemeProviderOwner, FoldStateProvider, KeyboardSizeStateProvider {
val latinIMELegacy = LatinIMELegacy(
this as InputMethodService,
this as LatinIMELegacy.SuggestionStripController
)
+
val inputLogic get() = latinIMELegacy.mInputLogic
lateinit var languageModelFacilitator: LanguageModelFacilitator
@@ -122,6 +173,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
val uixManager = UixManager(this)
lateinit var suggestionBlacklist: SuggestionBlacklist
+ val sizingCalculator = KeyboardSizingCalculator(this, uixManager)
+
private var activeThemeOption: ThemeOption? = null
private var activeColorScheme = VoiceInputTheme.obtainColors(this)
private var pendingRecreateKeyboard: Boolean = false
@@ -130,6 +183,13 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
val colorScheme get() = activeColorScheme
val keyboardColor get() = drawableProvider?.primaryKeyboardColor?.let { androidx.compose.ui.graphics.Color(it) } ?: colorScheme.surface
+ val size: MutableState = mutableStateOf(null)
+ private fun calculateSize(): ComputedKeyboardSize
+ = sizingCalculator.calculate(
+ latinIMELegacy.mKeyboardSwitcher.keyboard?.mId?.mKeyboardLayoutSetName ?: "qwerty",
+ latinIMELegacy.mKeyboardSwitcher.keyboard?.mId?.mNumberRow ?: false
+ )
+
private var drawableProvider: DynamicThemeProvider? = null
private var lastEditorInfo: EditorInfo? = null
@@ -204,7 +264,26 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
}
+ private fun onSizeUpdated() {
+ val newSize = calculateSize()
+ val shouldInvalidateKeyboard = size.value?.let { oldSize ->
+ when {
+ oldSize is FloatingKeyboardSize && newSize is FloatingKeyboardSize -> {
+ oldSize.width != newSize.width || oldSize.height != newSize.height
+ }
+ else -> true
+ }
+ } ?: true
+
+ size.value = newSize
+
+ if(shouldInvalidateKeyboard) {
+ invalidateKeyboard(true)
+ }
+ }
+
fun invalidateKeyboard(refreshSettings: Boolean = false) {
+ size.value = calculateSize()
settingsRefreshRequired = settingsRefreshRequired || refreshSettings
if(!uixManager.isMainKeyboardHidden) {
@@ -256,16 +335,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED)
registerReceiver(unlockReceiver, filter)
- mLifecycleRegistry = LifecycleRegistry(this)
- mLifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
-
- mViewModelStore = ViewModelStore()
-
- mSavedStateRegistryController = SavedStateRegistryController.create(this)
- mSavedStateRegistryController.performRestore(null)
-
- mLifecycleRegistry.currentState = Lifecycle.State.CREATED
-
suggestionBlacklist = SuggestionBlacklist(latinIMELegacy.mSettings, this, lifecycleScope)
Subtypes.addDefaultSubtypesIfNecessary(this)
@@ -348,6 +417,21 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
}
+ // Listen to size changes
+ launchJob {
+ val prev: MutableMap =
+ KeyboardSizeSettingKind.entries.associateWith { null }.toMutableMap()
+
+ dataStore.data.collect { data ->
+ prev.keys.toList().forEach {
+ if(data[KeyboardSettings[it]!!.key] != prev[it]) {
+ prev[it] = data[KeyboardSettings[it]!!.key]
+ onSizeUpdated()
+ }
+ }
+ }
+ }
+
uixManager.onCreate()
Settings.getInstance().settingsChangedListeners.add { oldSettings, newSettings ->
@@ -364,7 +448,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
unregisterReceiver(unlockReceiver)
stopJobs()
- mLifecycleRegistry.currentState = Lifecycle.State.DESTROYED
viewModelStore.clear()
languageModelFacilitator.saveHistoryLog()
@@ -381,6 +464,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun onConfigurationChanged(newConfig: Configuration) {
Log.w("LatinIME", "Configuration changed")
+ size.value = calculateSize()
latinIMELegacy.onConfigurationChanged(newConfig)
super.onConfigurationChanged(newConfig)
}
@@ -390,22 +474,21 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
private var legacyInputView: View? = null
- private var touchableHeight: Int = 0
override fun onCreateInputView(): View {
- Log.w("LatinIME", "Create input view")
- legacyInputView = latinIMELegacy.onCreateInputView()
+ val composeView = super.onCreateInputView()
- val composeView = uixManager.createComposeView()
+ legacyInputView = latinIMELegacy.onCreateInputView()
latinIMELegacy.setComposeInputView(composeView)
+ uixManager.setContent()
+
return composeView
}
private var inputViewHeight: Int = -1
- // Both called by UixManager
- fun updateTouchableHeight(to: Int) { touchableHeight = to }
fun getInputViewHeight(): Int = inputViewHeight
+ fun getViewHeight(): Int = composeView?.height ?: resources.displayMetrics.heightPixels
private var isInputModal = false
fun setInputModal(to: Boolean) {
@@ -426,8 +509,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
}
-
-
key(legacyInputView) {
AndroidView(factory = {
legacyInputView!!
@@ -445,7 +526,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
legacyInputView = newView
uixManager.setContent()
- uixManager.getComposeView()?.let {
+ composeView?.let {
latinIMELegacy.setComposeInputView(it)
}
@@ -455,7 +536,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
override fun setInputView(view: View?) {
super.setInputView(view)
- uixManager.getComposeView()?.let {
+ composeView?.let {
latinIMELegacy.setComposeInputView(it)
}
@@ -473,8 +554,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
- mLifecycleRegistry.currentState = Lifecycle.State.STARTED
-
lastEditorInfo = info
super.onStartInputView(info, restarting)
@@ -561,36 +640,55 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
}
override fun onComputeInsets(outInsets: Insets?) {
- val composeView = uixManager.getComposeView()
-
// This method may be called before {@link #setInputView(View)}.
if (legacyInputView == null || composeView == null) {
return
}
- val inputHeight: Int = composeView.height
- if (latinIMELegacy.isImeSuppressedByHardwareKeyboard && !legacyInputView!!.isShown) {
- // If there is a hardware keyboard and a visible software keyboard view has been hidden,
- // no visual element will be shown on the screen.
- latinIMELegacy.setInsets(outInsets!!.apply {
- contentTopInsets = inputHeight
- visibleTopInsets = inputHeight
- })
- return
- }
-
- val visibleTopY = inputHeight - touchableHeight
-
- val touchLeft = 0
- val touchTop = if(isInputModal) { 0 } else { visibleTopY }
- val touchRight = composeView.width
- val touchBottom = inputHeight
-
+ val viewHeight = composeView!!.height
+ val size = size.value ?: return
latinIMELegacy.setInsets(outInsets!!.apply {
- touchableInsets = Insets.TOUCHABLE_INSETS_REGION;
- touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
- contentTopInsets = visibleTopY
- visibleTopInsets = visibleTopY
+ when(size) {
+ is FloatingKeyboardSize -> {
+ val height = uixManager.touchableHeight
+
+ val left = size.bottomOrigin.first
+ val bottomYFromBottom = size.bottomOrigin.second
+ var bottom = viewHeight - bottomYFromBottom
+ var top = bottom - height
+ val right = left + size.width
+
+ if(top < 0) {
+ bottom -= top
+ top -= top
+ }
+
+ touchableInsets = Insets.TOUCHABLE_INSETS_REGION
+ touchableRegion.set(left, top, right, bottom)
+ contentTopInsets = viewHeight
+ visibleTopInsets = viewHeight
+ }
+ else -> {
+ touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT
+
+ val touchableHeight = uixManager.touchableHeight
+ val topInset = if(touchableHeight < 1 || touchableHeight >= viewHeight - 1) {
+ val actionBarHeight = sizingCalculator.calculateTotalActionBarHeightPx()
+
+ viewHeight - size.getHeight() - actionBarHeight
+ } else {
+ viewHeight - touchableHeight
+ }
+
+ contentTopInsets = topInset
+ visibleTopInsets = topInset
+ }
+ }
+
+ if(isInputModal) {
+ touchableInsets = Insets.TOUCHABLE_INSETS_REGION
+ touchableRegion.set(0, 0, composeView!!.width, composeView!!.height)
+ }
})
}
@@ -737,14 +835,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
return super.getCurrentInputConnection()
}
- override val lifecycle: Lifecycle
- get() = mLifecycleRegistry
- override val savedStateRegistry: SavedStateRegistry
- get() = mSavedStateRegistryController.savedStateRegistry
- override val viewModelStore: ViewModelStore
- get() = mViewModelStore
-
-
private fun onDeviceUnlocked() {
Log.i("LatinIME", "DEVICE has UNLOCKED!!! Reloading settings...")
// Every place that called getDefaultSharedPreferences now needs to be refreshed or call it again
diff --git a/java/src/org/futo/inputmethod/latin/uix/Action.kt b/java/src/org/futo/inputmethod/latin/uix/Action.kt
index 75f6ff9bb..9a88c3afc 100644
--- a/java/src/org/futo/inputmethod/latin/uix/Action.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/Action.kt
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.LifecycleCoroutineScope
import org.futo.inputmethod.latin.LatinIME
import org.futo.inputmethod.latin.uix.theme.ThemeOption
+import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
import java.util.Locale
interface ActionInputTransaction {
@@ -68,6 +69,8 @@ interface KeyboardManagerForAction {
fun getLatinIMEForDebug(): LatinIME
fun isDeviceLocked(): Boolean
+
+ fun getSizingCalculator(): KeyboardSizingCalculator
}
interface ActionWindow {
diff --git a/java/src/org/futo/inputmethod/latin/uix/KeyboardLayoutPreview.kt b/java/src/org/futo/inputmethod/latin/uix/KeyboardLayoutPreview.kt
index e0b1b1174..23b55c798 100644
--- a/java/src/org/futo/inputmethod/latin/uix/KeyboardLayoutPreview.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/KeyboardLayoutPreview.kt
@@ -13,7 +13,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
@@ -30,6 +29,7 @@ import org.futo.inputmethod.latin.R
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2Params
import org.futo.inputmethod.v2keyboard.LayoutManager
+import org.futo.inputmethod.v2keyboard.RegularKeyboardSize
import java.util.Locale
import kotlin.math.roundToInt
@@ -76,23 +76,8 @@ fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp, locale: Locale? = null
}
}
- val configuration = LocalConfiguration.current
- val isLandscape = false//configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
-
- val widthPx: Int
- val heightPx: Int
-
- when {
- isLandscape -> {
- widthPx = (500.0 * context.resources.displayMetrics.density).roundToInt()
- heightPx = (180.0 * context.resources.displayMetrics.density).roundToInt()
- }
-
- else -> {
- widthPx = (320.0 * context.resources.displayMetrics.density).roundToInt()
- heightPx = (200.0 * context.resources.displayMetrics.density).roundToInt()
- }
- }
+ val widthPx: Int = (320.0 * context.resources.displayMetrics.density).roundToInt()
+ val heightPx: Int = (200.0 * context.resources.displayMetrics.density).roundToInt()
val keyboard = remember { mutableStateOf(null) }
@@ -107,16 +92,12 @@ fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp, locale: Locale? = null
val layoutSet = KeyboardLayoutSetV2(
context,
KeyboardLayoutSetV2Params(
- width = widthPx,
- height = heightPx,
- padding = Rect(),
+ computedSize = RegularKeyboardSize(width = widthPx, height = heightPx, padding = Rect()),
gap = 4.0f,
keyboardLayoutSet = id,
locale = loc ?: Locale.ENGLISH,
editorInfo = editorInfo,
numberRow = numberRow,
- useSplitLayout = isLandscape,
- splitLayoutWidth = widthPx * 2 / 3,
bottomActionKey = null
)
)
diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
index 079f7847b..af168e7d0 100644
--- a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
@@ -23,22 +23,29 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.absoluteOffset
+import androidx.compose.foundation.layout.absolutePadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
+import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -46,17 +53,21 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleCoroutineScope
@@ -96,6 +107,14 @@ import org.futo.inputmethod.updates.deferManualUpdate
import org.futo.inputmethod.updates.isManualUpdateTimeExpired
import org.futo.inputmethod.updates.openManualUpdateCheck
import org.futo.inputmethod.updates.retrieveSavedLastUpdateCheckResult
+import org.futo.inputmethod.v2keyboard.FloatingKeyboardSize
+import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
+import org.futo.inputmethod.v2keyboard.OneHandedDirection
+import org.futo.inputmethod.v2keyboard.OneHandedKeyboardSize
+import org.futo.inputmethod.v2keyboard.RegularKeyboardSize
+import org.futo.inputmethod.v2keyboard.SplitKeyboardSize
+import org.futo.inputmethod.v2keyboard.getPadding
+import org.futo.inputmethod.v2keyboard.getWidth
import java.util.Locale
val LocalManager = staticCompositionLocalOf {
@@ -279,7 +298,7 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
override fun announce(s: String) {
AccessibilityUtils.init(getContext())
if(AccessibilityUtils.getInstance().isAccessibilityEnabled) {
- AccessibilityUtils.getInstance().announceForAccessibility(uixManager.getComposeView(), s)
+ AccessibilityUtils.getInstance().announceForAccessibility(uixManager.composeView, s)
}
}
@@ -295,6 +314,9 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
return getContext().isDeviceLocked
}
+ override fun getSizingCalculator(): KeyboardSizingCalculator =
+ latinIME.sizingCalculator
+
override fun getLatinIMEForDebug(): LatinIME = latinIME
}
@@ -305,11 +327,12 @@ data class ActiveDialogRequest(
)
class UixManager(private val latinIME: LatinIME) {
+ internal val composeView: ComposeView?
+ get() = latinIME.composeView
+
private var shouldShowSuggestionStrip: Boolean = true
private var suggestedWords: SuggestedWords? = null
- private var composeView: ComposeView? = null
-
private var currWindowAction: Action? = null
private var persistentStates: HashMap = hashMapOf()
@@ -327,6 +350,9 @@ class UixManager(private val latinIME: LatinIME) {
latinIME.deferSetSetting(ActionBarExpanded, isActionsExpanded.value)
}
+ val actionsExpanded: Boolean
+ get() = isActionsExpanded.value
+
private var isShowingActionEditor = mutableStateOf(false)
fun showActionEditor() {
isShowingActionEditor.value = true
@@ -337,6 +363,12 @@ class UixManager(private val latinIME: LatinIME) {
var isInputOverridden = mutableStateOf(false)
var currWindowActionWindow: ActionWindow? = null
+ val isActionWindowDocked: Boolean
+ get() = currWindowActionWindow != null
+
+ private var measuredTouchableHeight = 0
+ val touchableHeight: Int
+ get() = measuredTouchableHeight
val isMainKeyboardHidden get() = mainKeyboardHidden
@@ -626,6 +658,129 @@ class UixManager(private val latinIME: LatinIME) {
)
}
+ @Composable
+ fun SizePositionerSurface(content: @Composable BoxScope.(actionBarGap: Dp) -> Unit) {
+ val size = latinIME.size.value
+ when(size) {
+ is FloatingKeyboardSize -> {
+ val offset = remember(size) { mutableStateOf(Offset(size.bottomOrigin.first.toFloat(), size.bottomOrigin.second.toFloat())) }
+ val configuration = LocalConfiguration.current
+ with(LocalDensity.current) {
+ Column(modifier = Modifier.fillMaxHeight().absoluteOffset { IntOffset(offset.value.x.toInt(), 0) }) {
+ Spacer(Modifier.weight(1.0f))
+ Column(Modifier
+ .background(latinIME.keyboardColor, RoundedCornerShape(8.dp))
+ .requiredWidth(size.width.toDp())
+ .onSizeChanged {
+ measuredTouchableHeight = it.height
+ }
+ .absolutePadding(
+ left = size.decorationPadding.left.toDp(),
+ top = 0.dp,
+ right = size.decorationPadding.right.toDp(),
+ bottom = 0.dp,
+ )
+ ) {
+ Box(Modifier.fillMaxWidth()) {
+ CompositionLocalProvider(
+ LocalContentColor provides contentColorFor(
+ latinIME.keyboardColor
+ )
+ ) {
+ content(4.dp)
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Box(modifier = Modifier
+ .fillMaxWidth()
+ .height(20.dp)
+ .pointerInput(size) {
+ detectDragGestures(onDrag = { change, dragAmount ->
+ var newOffset = offset.value.copy(
+ x = offset.value.x + dragAmount.x,
+ y = offset.value.y - dragAmount.y
+ )
+
+ // Ensure we are not out of bounds
+ newOffset = newOffset.copy(
+ newOffset.x.coerceAtLeast(0.0f),
+ newOffset.y.coerceAtLeast(0.0f)
+ )
+ newOffset = newOffset.copy(
+ newOffset.x.coerceAtMost(configuration.screenWidthDp.dp.toPx() - size.width),
+ newOffset.y.coerceAtMost(latinIME.getViewHeight().toFloat() - measuredTouchableHeight)
+ )
+
+ offset.value = newOffset
+ }, onDragEnd = {
+ latinIME.sizingCalculator.editSavedSettings { settings ->
+ settings.copy(
+ floatingBottomCenterOriginDp = Pair(
+ offset.value.x.toDp().value,
+ offset.value.y.toDp().value
+ )
+ )
+ }
+ })
+ }) {
+ Box(modifier = Modifier.fillMaxWidth(0.6f).height(4.dp).align(Alignment.TopCenter).background(
+ MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f), RoundedCornerShape(100)
+ ))
+ }
+ }
+ Spacer(Modifier.height(offset.value.y.toDp()))
+ }
+ }
+ }
+ is OneHandedKeyboardSize,
+ is RegularKeyboardSize,
+ is SplitKeyboardSize -> {
+ Column {
+ Spacer(modifier = Modifier.weight(1.0f))
+ Surface(modifier = Modifier.onSizeChanged {
+ measuredTouchableHeight = it.height
+ }, color = latinIME.keyboardColor) {
+ with(LocalDensity.current) {
+ val actionBarGap = (size.getPadding().top / 2).toDp()
+ Box(Modifier.absolutePadding(
+ left = size.getPadding().left.toDp(),
+ top = actionBarGap,
+ right = size.getPadding().right.toDp(),
+ bottom = size.getPadding().bottom.toDp(),
+ ).width(
+ (size.getWidth() - size.getPadding().left - size.getPadding().right).toDp()
+ )) {
+ when(size) {
+ is OneHandedKeyboardSize -> {
+ Box(modifier = Modifier.width(size.layoutWidth.toDp()).align(
+ when(size.direction) {
+ OneHandedDirection.Left -> Alignment.CenterStart
+ OneHandedDirection.Right -> Alignment.CenterEnd
+ }
+ )) {
+ content(actionBarGap)
+ }
+ }
+ is RegularKeyboardSize -> {
+ content(actionBarGap)
+ }
+ is SplitKeyboardSize -> {
+ content(actionBarGap)
+ }
+ else -> throw IllegalStateException()
+ }
+ }
+
+ }
+ }
+ }
+ }
+ null -> return
+ }
+ }
+
fun setContent() {
composeView?.setContent {
UixThemeWrapper(latinIME.colorScheme) {
@@ -639,29 +794,22 @@ class UixManager(private val latinIME: LatinIME) {
isShowingActionEditor.value = false
}
- Column {
- Spacer(modifier = Modifier.weight(1.0f))
- Surface(modifier = Modifier.onSizeChanged {
- latinIME.updateTouchableHeight(it.height)
- }, color = latinIME.keyboardColor) {
- Box {
- Column {
- when {
- currWindowActionWindow != null -> ActionViewWithHeader(
- currWindowActionWindow!!
- )
+ SizePositionerSurface { gap ->
+ Column {
+ when {
+ currWindowActionWindow != null -> ActionViewWithHeader(
+ currWindowActionWindow!!
+ )
- else -> MainKeyboardViewWithActionBar()
- }
-
- latinIME.LegacyKeyboardView(hidden = isMainKeyboardHidden)
- }
-
- ForgetWordDialog()
+ else -> MainKeyboardViewWithActionBar()
}
+ Spacer(modifier = Modifier.height(gap))
+
+ latinIME.LegacyKeyboardView(hidden = isMainKeyboardHidden)
}
+ ForgetWordDialog()
}
ActionEditorHost()
@@ -734,28 +882,6 @@ class UixManager(private val latinIME: LatinIME) {
}
}
- fun createComposeView(): View {
- if(composeView != null) {
- composeView = null
- //throw IllegalStateException("Attempted to create compose view, when one is already created!")
- }
-
- composeView = ComposeView(latinIME).apply {
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setParentCompositionContext(null)
-
- latinIME.setOwners()
- }
-
- setContent()
-
- return composeView!!
- }
-
- fun getComposeView(): View? {
- return composeView
- }
-
fun onColorSchemeChanged() {
setContent()
}
diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt b/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt
new file mode 100644
index 000000000..c49db1a87
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt
@@ -0,0 +1,86 @@
+package org.futo.inputmethod.latin.uix.actions
+
+import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.uix.Action
+import org.futo.inputmethod.v2keyboard.KeyboardMode
+import org.futo.inputmethod.v2keyboard.OneHandedDirection
+
+val LeftHandedKeyboardAction = Action(
+ icon = R.drawable.arrow_left,
+ name = R.string.left_handed_keyboard_action_title,
+ simplePressImpl = { manager, _ ->
+ manager.getSizingCalculator().editSavedSettings {
+ if(it.currentMode == KeyboardMode.OneHanded
+ && it.oneHandedDirection == OneHandedDirection.Left) {
+ it.copy(
+ currentMode = KeyboardMode.Regular
+ )
+ } else {
+ it.copy(
+ oneHandedDirection = OneHandedDirection.Left,
+ currentMode = KeyboardMode.OneHanded
+ )
+ }
+ }
+ },
+ windowImpl = null,
+)
+
+val RightHandedKeyboardAction = Action(
+ icon = R.drawable.arrow_right,
+ name = R.string.right_handed_keyboard_action_title,
+ simplePressImpl = { manager, _ ->
+ manager.getSizingCalculator().editSavedSettings {
+ if(it.currentMode == KeyboardMode.OneHanded
+ && it.oneHandedDirection == OneHandedDirection.Right) {
+ it.copy(
+ currentMode = KeyboardMode.Regular
+ )
+ } else {
+ it.copy(
+ oneHandedDirection = OneHandedDirection.Right,
+ currentMode = KeyboardMode.OneHanded
+ )
+ }
+ }
+ },
+ windowImpl = null,
+)
+
+val SplitKeyboardAction = Action(
+ icon = R.drawable.arrow_down,
+ name = R.string.split_keyboard_action_title,
+ simplePressImpl = { manager, _ ->
+ manager.getSizingCalculator().editSavedSettings {
+ if(it.currentMode == KeyboardMode.Split) {
+ it.copy(
+ currentMode = KeyboardMode.Regular
+ )
+ } else {
+ it.copy(
+ currentMode = KeyboardMode.Split
+ )
+ }
+ }
+ },
+ windowImpl = null,
+)
+
+val FloatingKeyboardAction = Action(
+ icon = R.drawable.arrow_up,
+ name = R.string.floating_keyboard_action_title,
+ simplePressImpl = { manager, _ ->
+ manager.getSizingCalculator().editSavedSettings {
+ if(it.currentMode == KeyboardMode.Floating) {
+ it.copy(
+ currentMode = KeyboardMode.Regular
+ )
+ } else {
+ it.copy(
+ currentMode = KeyboardMode.Floating
+ )
+ }
+ }
+ },
+ windowImpl = null,
+)
diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/Registry.kt b/java/src/org/futo/inputmethod/latin/uix/actions/Registry.kt
index 216a63da7..dc11c4a1b 100644
--- a/java/src/org/futo/inputmethod/latin/uix/actions/Registry.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/actions/Registry.kt
@@ -31,7 +31,11 @@ val AllActionsMap = mapOf(
"copy" to CopyAction,
"select_all" to SelectAllAction,
"more" to MoreActionsAction,
- "bugs" to BugViewerAction
+ "bugs" to BugViewerAction,
+ "onehanded_left" to LeftHandedKeyboardAction,
+ "onehanded_right" to RightHandedKeyboardAction,
+ "split_keyboard" to SplitKeyboardAction,
+ "floating_keyboard" to FloatingKeyboardAction
)
val ActionToId = AllActionsMap.entries.associate { it.value to it.key }
diff --git a/java/src/org/futo/inputmethod/v2keyboard/KeyboardLayoutSet.kt b/java/src/org/futo/inputmethod/v2keyboard/KeyboardLayoutSet.kt
index 0616082df..97c180a6d 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/KeyboardLayoutSet.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/KeyboardLayoutSet.kt
@@ -76,16 +76,12 @@ fun getPrimaryLayoutOverride(editorInfo: EditorInfo?): String? {
}
data class KeyboardLayoutSetV2Params(
- val width: Int,
- val height: Int,
- val padding: Rect,
+ val computedSize: ComputedKeyboardSize,
val keyboardLayoutSet: String,
val locale: Locale,
val editorInfo: EditorInfo?,
val numberRow: Boolean,
val gap: Float = 4.0f,
- val useSplitLayout: Boolean,
- val splitLayoutWidth: Int,
val bottomActionKey: Int?,
val longPressKeySettings: LongPressKeySettings? = null
)
@@ -184,14 +180,15 @@ class KeyboardLayoutSetV2 internal constructor(
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 height = params.computedSize.getHeight()
+
+ private val padding = params.computedSize.getPadding()
+
+ private val widthMinusPadding = params.computedSize.getTotalKeyboardWidth()
+ private val heightMinusPadding = height - padding.top - padding.bottom
private val singularRowHeight: Double
- get() = heightMinusPadding?.let { it / 4.0 } ?: run {
- (ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) *
- keyboardHeightMultiplier
- }
+ get() = heightMinusPadding / 4.0
fun getKeyboard(element: KeyboardLayoutElement): Keyboard {
@@ -221,10 +218,8 @@ class KeyboardLayoutSetV2 internal constructor(
}
val layoutParams = LayoutParams(
+ size = params.computedSize,
gap = params.gap.dp,
- useSplitLayout = params.useSplitLayout,
- splitLayoutWidth = params.splitLayoutWidth,
- padding = params.padding,
standardRowHeight = singularRowHeight,
element = element
)
diff --git a/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt b/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt
index 15e0d48c3..3c5415e64 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt
@@ -2,12 +2,27 @@ 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.datastore.preferences.core.stringPreferencesKey
import androidx.window.layout.FoldingFeature
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.descriptors.element
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.encoding.CompositeDecoder
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.encoding.decodeStructure
+import kotlinx.serialization.encoding.encodeStructure
+import kotlinx.serialization.json.Json
import org.futo.inputmethod.latin.FoldStateProvider
+import org.futo.inputmethod.latin.LatinIME
import org.futo.inputmethod.latin.uix.SettingsKey
+import org.futo.inputmethod.latin.uix.UixManager
import org.futo.inputmethod.latin.uix.getSettingBlocking
+import org.futo.inputmethod.latin.uix.setSettingBlocking
import org.futo.inputmethod.latin.utils.ResourceUtils
import kotlin.math.roundToInt
@@ -17,13 +32,139 @@ interface KeyboardSizeStateProvider {
sealed class ComputedKeyboardSize()
-class RegularKeyboardSize(val height: Int, val padding: Rect) : ComputedKeyboardSize()
+class RegularKeyboardSize(val height: Int, val width: Int, val padding: Rect) : ComputedKeyboardSize()
-class SplitKeyboardSize(val height: Int, val padding: Rect, val splitLayoutWidth: Int) : ComputedKeyboardSize()
+class SplitKeyboardSize(val height: Int, val width: 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 OneHandedDirection {
+ Left,
+ Right
+}
+class OneHandedKeyboardSize(val height: Int, val width: Int, val padding: Rect, val layoutWidth: Int, val direction: OneHandedDirection): ComputedKeyboardSize()
+
+class FloatingKeyboardSize(
+ val bottomOrigin: Pair,
+ val width: Int,
+ val height: Int,
+ val decorationPadding: Rect
+): ComputedKeyboardSize()
+
+fun ComputedKeyboardSize.getHeight(): Int = when(this) {
+ is FloatingKeyboardSize -> height
+ is OneHandedKeyboardSize -> height
+ is RegularKeyboardSize -> height
+ is SplitKeyboardSize -> height
+}
+
+fun ComputedKeyboardSize.getWidth(): Int = when(this) {
+ is FloatingKeyboardSize -> width
+ is OneHandedKeyboardSize -> width
+ is RegularKeyboardSize -> width
+ is SplitKeyboardSize -> width
+}
+
+fun ComputedKeyboardSize.getPadding(): Rect = when(this) {
+ is FloatingKeyboardSize -> decorationPadding
+ is OneHandedKeyboardSize -> padding
+ is RegularKeyboardSize -> padding
+ is SplitKeyboardSize -> padding
+}
+
+fun ComputedKeyboardSize.getTotalKeyboardWidth(): Int = when(this) {
+ is FloatingKeyboardSize -> width - decorationPadding.left - decorationPadding.right
+ is OneHandedKeyboardSize -> layoutWidth
+ is RegularKeyboardSize -> width - padding.left - padding.right
+ is SplitKeyboardSize -> width - padding.left - padding.right
+}
+
+enum class KeyboardMode {
+ Regular,
+ Split,
+ OneHanded,
+ Floating
+}
+
+
+
+@Serializer(forClass = Rect::class)
+object RectSerializer : KSerializer {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Rect") {
+ element("left")
+ element("top")
+ element("right")
+ element("bottom")
+ }
+
+ override fun serialize(encoder: Encoder, value: Rect) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.left)
+ encodeIntElement(descriptor, 1, value.top)
+ encodeIntElement(descriptor, 2, value.right)
+ encodeIntElement(descriptor, 3, value.bottom)
+ }
+ }
+
+ override fun deserialize(decoder: Decoder): Rect {
+ return decoder.decodeStructure(descriptor) {
+ var left = 0
+ var top = 0
+ var right = 0
+ var bottom = 0
+
+ while (true) {
+ when (val index = decodeElementIndex(descriptor)) {
+ 0 -> left = decodeIntElement(descriptor, 0)
+ 1 -> top = decodeIntElement(descriptor, 1)
+ 2 -> right = decodeIntElement(descriptor, 2)
+ 3 -> bottom = decodeIntElement(descriptor, 3)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+
+ Rect(left, top, right, bottom)
+ }
+ }
+}
+
+
+
+@Serializable
+data class SavedKeyboardSizingSettings(
+ val currentMode: KeyboardMode,
+ val heightMultiplier: Float,
+ val paddingDp: @Serializable(RectSerializer::class) Rect,
+
+ // Split
+ val splitWidthFraction: Float,
+
+ // One handed, values with respect to left handed mode
+ // left = padding
+ // right = width + padding
+ // bottom = padding for bottom
+ val oneHandedRectDp: @Serializable(RectSerializer::class) Rect,
+ val oneHandedDirection: OneHandedDirection,
+
+ // Floating
+ val floatingBottomCenterOriginDp: Pair, // relative to bottom left of screen, .second is Y up
+ val floatingWidthDp: Float,
+ val floatingHeightDp: Float
+) {
+ fun toJsonString(): String =
+ Json.encodeToString(this)
+
+ companion object {
+ @JvmStatic
+ fun fromJsonString(s: String): SavedKeyboardSizingSettings? =
+ try {
+ Json.decodeFromString(s)
+ } catch (e: Exception) {
+ //e.printStackTrace()
+ null
+ }
+ }
+}
enum class KeyboardSizeSettingKind {
Portrait,
@@ -31,117 +172,231 @@ enum class KeyboardSizeSettingKind {
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 DefaultKeyboardSettings = mapOf(
+ KeyboardSizeSettingKind.Portrait to SavedKeyboardSizingSettings(
+ currentMode = KeyboardMode.Regular,
+ heightMultiplier = 1.0f,
+ paddingDp = Rect(2, 4, 2, 10),
+ splitWidthFraction = 4.0f / 5.0f,
+ oneHandedDirection = OneHandedDirection.Right,
+ oneHandedRectDp = Rect(4, 4, 364, 30),
+ floatingBottomCenterOriginDp = Pair(0.0f, 0.0f),
+ floatingHeightDp = 240.0f,
+ floatingWidthDp = 360.0f
+ ),
+
+ KeyboardSizeSettingKind.Landscape to SavedKeyboardSizingSettings(
+ currentMode = KeyboardMode.Split,
+ heightMultiplier = 0.9f,
+ paddingDp = Rect(8, 2, 8, 2),
+ splitWidthFraction = 3.0f / 5.0f,
+ oneHandedDirection = OneHandedDirection.Right,
+ oneHandedRectDp = Rect(4, 4, 364, 30),
+ floatingBottomCenterOriginDp = Pair(0.0f, 0.0f),
+ floatingHeightDp = 240.0f,
+ floatingWidthDp = 360.0f
+ ),
+
+ KeyboardSizeSettingKind.FoldableInnerDisplay to SavedKeyboardSizingSettings(
+ currentMode = KeyboardMode.Split,
+ heightMultiplier = 0.67f,
+ paddingDp = Rect(44, 4, 44, 8),
+ splitWidthFraction = 3.0f / 5.0f,
+ oneHandedDirection = OneHandedDirection.Right,
+ oneHandedRectDp = Rect(4, 4, 364, 30),
+ floatingBottomCenterOriginDp = Pair(0.0f, 0.0f),
+ floatingHeightDp = 240.0f,
+ floatingWidthDp = 360.0f
+ ),
)
-val KeyboardHeightSettings = mapOf(
+val KeyboardSettings = mapOf(
KeyboardSizeSettingKind.Portrait to SettingsKey(
- floatPreferencesKey("keyboardHeightMultiplier"), 1.0f),
+ stringPreferencesKey("keyboard_settings_portrait"), ""),
KeyboardSizeSettingKind.Landscape to SettingsKey(
- floatPreferencesKey("keyboard_height_landscape"), 0.9f),
+ stringPreferencesKey("keyboard_settings_landscape"), ""),
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
- floatPreferencesKey("keyboard_height_fold"), 0.67f),
+ stringPreferencesKey("keyboard_settings_fold"), ""),
)
-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),
-)
+class KeyboardSizingCalculator(val context: Context, val uixManager: UixManager) {
+ val sizeStateProvider = context as KeyboardSizeStateProvider
+ val foldStateProvider = context as FoldStateProvider
-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),
-)
+ private fun dp(v: Number): Int =
+ (v.toFloat() * context.resources.displayMetrics.density).toInt()
+
+ private fun dp(v: Rect): Rect =
+ Rect(dp(v.left), dp(v.top), dp(v.right), dp(v.bottom))
+
+ private fun limitFloating(rectPx: Rect): Rect {
+ val width = rectPx.width()
+ val height = rectPx.height()
+
+ val minWidth = dp(160)
+ val minHeight = dp(160)
+
+ if(width < minWidth) {
+ val delta = minWidth - width
+ rectPx.left -= delta / 2
+ rectPx.right += delta / 2
+ }
+
+ if(height < minHeight) {
+ val delta = minHeight - height
+ rectPx.top -= delta
+ rectPx.bottom += delta
+ }
+
+ val maxWidth = context.resources.displayMetrics.widthPixels * 2 / 3
+ val maxHeight = context.resources.displayMetrics.heightPixels * 2 / 3
+
+ if(width > maxWidth) {
+ val delta = width - maxWidth
+ rectPx.left += delta / 2
+ rectPx.right -= delta / 2
+ }
+
+ if(height > maxHeight) {
+ val delta = height - maxHeight
+ rectPx.top += delta / 2
+ rectPx.bottom -= delta / 2
+ }
-class KeyboardSizingCalculator(val context: Context) {
- private val sizeStateProvider = context as KeyboardSizeStateProvider
- private val foldStateProvider = context as FoldStateProvider
+ val originX = rectPx.left
+ val originY = rectPx.top
- private fun isSplitKeyboard(mode: KeyboardSizeSettingKind): Boolean =
- context.getSettingBlocking(SplitKeyboardSettings[mode]!!)
+ if(originX < 0){
+ rectPx.left -= originX
+ rectPx.right -= originX
+ }
- private fun heightMultiplier(mode: KeyboardSizeSettingKind): Float =
- context.getSettingBlocking(KeyboardHeightSettings[mode]!!)
+ if(originY < 0) {
+ rectPx.top -= originY
+ rectPx.bottom -= originY
+ }
- private fun bottomOffsetPx(mode: KeyboardSizeSettingKind): Int =
- (context.getSettingBlocking(KeyboardOffsetSettings[mode]!!) * context.resources.displayMetrics.density).toInt()
+ if(rectPx.right > context.resources.displayMetrics.widthPixels) {
+ val delta = rectPx.right - context.resources.displayMetrics.widthPixels
+ rectPx.right -= delta
+ rectPx.left -= delta
+ }
+ if(rectPx.bottom < 0) {
+ val delta = rectPx.bottom
+ rectPx.top -= delta
+ rectPx.bottom -= delta
+ }
- private fun sideInsetPx(mode: KeyboardSizeSettingKind): Int =
- (context.getSettingBlocking(KeyboardSideInsetSettings[mode]!!) * context.resources.displayMetrics.density).toInt()
+ return rectPx
+ }
- 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 getSavedSettings(): SavedKeyboardSizingSettings =
+ SavedKeyboardSizingSettings.fromJsonString(context.getSettingBlocking(
+ KeyboardSettings[sizeStateProvider.currentSizeState]!!
+ )) ?: DefaultKeyboardSettings[sizeStateProvider.currentSizeState]!!
+
+ fun editSavedSettings(transform: (SavedKeyboardSizingSettings) -> SavedKeyboardSizingSettings) {
+ val sizeState = sizeStateProvider.currentSizeState
+
+ val savedSettings = SavedKeyboardSizingSettings.fromJsonString(context.getSettingBlocking(
+ KeyboardSettings[sizeState]!!
+ )) ?: DefaultKeyboardSettings[sizeState]!!
+
+ val transformed = transform(savedSettings)
+
+ if(transformed != savedSettings) {
+ context.setSettingBlocking(KeyboardSettings[sizeState]!!.key, transformed.toJsonString())
+ }
+ }
fun calculate(layoutName: String, isNumberRowActive: Boolean): ComputedKeyboardSize {
+ val savedSettings = getSavedSettings()
+
val layout = LayoutManager.getLayout(context, layoutName)
val effectiveRowCount = layout.effectiveRows.size
- val configuration = context.resources.configuration
val displayMetrics = context.resources.displayMetrics
+ println("Display metrics ${displayMetrics.widthPixels / displayMetrics.density}")
- 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 singularRowHeight = (ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) *
+ savedSettings.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
+ val window = (context as LatinIME).window.window
+ val width = ResourceUtils.getDefaultKeyboardWidth(window, context.resources)
+
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(
+ height = displayMetrics.heightPixels / 2 - (displayMetrics.density * 80.0f).toInt(),
+ width = width,
+ padding = Rect(
(displayMetrics.density * 44.0f).roundToInt(),
- (displayMetrics.density * 20.0f).roundToInt(),
+ (displayMetrics.density * 50.0f).roundToInt(),
(displayMetrics.density * 44.0f).roundToInt(),
(displayMetrics.density * 12.0f).roundToInt(),
),
- displayMetrics.widthPixels * 3 / 5
+ splitLayoutWidth = displayMetrics.widthPixels * 3 / 5
)
- isSplit -> SplitKeyboardSize(
- recommendedHeight.roundToInt(),
- Rect(sideInset, topPadding, sideInset, bottomOffset),
- displayMetrics.widthPixels * 3 / 5)
+ savedSettings.currentMode == KeyboardMode.Split ->
+ SplitKeyboardSize(
+ height = recommendedHeight.roundToInt(),
+ width = width,
+ padding = dp(savedSettings.paddingDp),
+ splitLayoutWidth = (displayMetrics.widthPixels * savedSettings.splitWidthFraction).toInt()
+ )
- else -> RegularKeyboardSize(
- recommendedHeight.roundToInt(),
- Rect(sideInset, topPadding, sideInset, bottomOffset),
- )
+ savedSettings.currentMode == KeyboardMode.OneHanded ->
+ OneHandedKeyboardSize(
+ height = recommendedHeight.roundToInt(),
+ width = width,
+ padding = dp(savedSettings.oneHandedRectDp).let { rect ->
+ when(savedSettings.oneHandedDirection) {
+ OneHandedDirection.Left -> Rect(rect.left, rect.top, rect.left, rect.bottom)
+ OneHandedDirection.Right -> Rect(rect.left, rect.top, rect.left, rect.bottom)
+ }
+ },
+ layoutWidth = dp(savedSettings.oneHandedRectDp.width()).coerceAtMost(displayMetrics.widthPixels * 9 / 10),
+ direction = savedSettings.oneHandedDirection
+ )
+
+ savedSettings.currentMode == KeyboardMode.Floating -> {
+ val singularRowHeightFloat = dp(savedSettings.floatingHeightDp) / 4.0f
+ val recommendedHeightFloat = singularRowHeightFloat * numRows
+ FloatingKeyboardSize(
+ bottomOrigin = Pair(
+ dp(savedSettings.floatingBottomCenterOriginDp.first),
+ dp(savedSettings.floatingBottomCenterOriginDp.second)
+ ),
+ width = dp(savedSettings.floatingWidthDp),
+ height = recommendedHeightFloat.toInt(),
+ decorationPadding = dp(
+ Rect(
+ 8,
+ 8,
+ 8,
+ 8
+ )
+ )
+ )
+ }
+
+ else ->
+ RegularKeyboardSize(
+ height = recommendedHeight.roundToInt(),
+ width = width,
+ padding = dp(savedSettings.paddingDp)
+ )
}
}
@@ -155,4 +410,14 @@ class KeyboardSizingCalculator(val context: Context) {
return (minDp / 100.0f).coerceIn(3.0f, 6.0f)
}
+
+ fun calculateSuggestionBarHeightDp(): Float {
+ return 40.0f
+ }
+
+ fun calculateTotalActionBarHeightPx(): Int =
+ when {
+ uixManager.actionsExpanded -> dp(2 * calculateSuggestionBarHeightDp())
+ else -> dp(calculateSuggestionBarHeightDp())
+ }
}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt b/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt
index 18de08aea..094e24adc 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt
@@ -81,10 +81,8 @@ data class LayoutRow(
)
data class LayoutParams(
+ val size: ComputedKeyboardSize,
val gap: Dp,
- val useSplitLayout: Boolean,
- val splitLayoutWidth: Int,
- val padding: Rect,
val standardRowHeight: Double,
val element: KeyboardLayoutElement,
)
@@ -131,8 +129,6 @@ data class LayoutEngine(
}
private fun computeRowHeight(): Double {
- //val normalKeyboardHeight = ((rowHeight.value + verticalGap.value) * density) * 3
-
val normalKeyboardHeight = totalRowHeight
// divide by total row height
@@ -140,19 +136,29 @@ data class LayoutEngine(
BottomRowHeightMode.Fixed -> ((normalKeyboardHeight - layoutParams.standardRowHeight) / rows.filter { !it.isBottomRow }.sumOf { it.rowHeight })
BottomRowHeightMode.Flexible -> (normalKeyboardHeight) / rows.sumOf { it.rowHeight }
}
- //return ((normalKeyboardHeight - bottomRowHeightPx) / rows.filter { !it.isBottomRow }.sumOf { it.rowHeight })
- //return (normalKeyboardHeight) / rows.sumOf { it.rowHeight }
}
- private val isSplitLayout = layoutParams.useSplitLayout
+ private val isSplitLayout = layoutParams.size is SplitKeyboardSize
+ private val isOneHandedLayout = layoutParams.size is OneHandedKeyboardSize
private val layoutWidth = if(isSplitLayout) {
- layoutParams.splitLayoutWidth
+ (layoutParams.size as SplitKeyboardSize).splitLayoutWidth
+ } else if(isOneHandedLayout) {
+ (layoutParams.size as OneHandedKeyboardSize).layoutWidth
} else {
params.mId.mWidth
}
- private val unsplitLayoutWidth = params.mId.mWidth
+ private val unsplitLayoutWidth = if(isSplitLayout) {
+ params.mId.mWidth
+ } else {
+ layoutWidth
+ }
+
+ // TODO: Remove
+ private val padding = Rect(0, 0, 0, 0)
+ private val xOffset = 0
+
private val minimumBottomFunctionalKeyWidth = (layoutWidth * keyboard.minimumBottomRowFunctionalKeyWidth)
private val regularKeyWidth = computeRegularKeyWidth()
@@ -551,10 +557,10 @@ data class LayoutEngine(
}
private fun addRowAlignLeft(row: List, y: Int, height: Int)
- = addRow(row, 0.0f + layoutParams.padding.left, y, height)
+ = addRow(row, 0.0f + padding.left + xOffset, y, height)
private fun addRowAlignRight(row: List, y: Int, height: Int) {
- val startingOffset = params.mId.mWidth - row.sumOf { it.widthPx.toDouble() }.toFloat() + layoutParams.padding.left
+ val startingOffset = params.mId.mWidth - row.sumOf { it.widthPx.toDouble() }.toFloat() + padding.left
addRow(row, startingOffset, y, height)
}
@@ -570,7 +576,7 @@ data class LayoutEngine(
}
private fun addKeys(rows: List): Int {
- var currentY = 0.0f + layoutParams.padding.top
+ var currentY = 0.0f + padding.top
rows.forEach { row ->
addRow(row, currentY.toInt())
currentY += row.height
@@ -588,14 +594,14 @@ data class LayoutEngine(
val rows = computeRows(this.rows)
- val totalKeyboardHeight = addKeys(rows).let { totalRowHeight.roundToInt() } + layoutParams.padding.top + layoutParams.padding.bottom
+ val totalKeyboardHeight = addKeys(rows).let { totalRowHeight.roundToInt() } + padding.top + padding.bottom
params.mOccupiedHeight = totalKeyboardHeight - verticalGapPx.roundToInt()
- params.mOccupiedWidth = params.mId.mWidth + layoutParams.padding.left + layoutParams.padding.right
- params.mTopPadding = 0//layoutParams.padding.top
- params.mBottomPadding = 0//layoutParams.padding.bottom
- params.mLeftPadding = 0//layoutParams.padding.left
- params.mRightPadding = 0//layoutParams.padding.right
+ params.mOccupiedWidth = params.mId.mWidth + padding.left + padding.right
+ params.mTopPadding = 0
+ params.mBottomPadding = 0
+ params.mLeftPadding = 0
+ params.mRightPadding = 0
params.mBaseWidth = params.mOccupiedWidth
params.mDefaultKeyWidth = regularKeyWidth.roundToInt()
diff --git a/java/src/org/futo/inputmethod/v2keyboard/LayoutManager.kt b/java/src/org/futo/inputmethod/v2keyboard/LayoutManager.kt
index 2c4b085d5..9960a07bd 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/LayoutManager.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/LayoutManager.kt
@@ -33,7 +33,7 @@ object LayoutManager {
private fun getAllLayoutPaths(assetManager: AssetManager): List {
return listFilesRecursively(assetManager, "layouts").filter {
- (it.endsWith(".yml") || it.endsWith(".yaml")) && it != "mapping.yaml"
+ (it.endsWith(".yml") || it.endsWith(".yaml")) && it != "layouts/mapping.yaml"
}
}