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" } }