Update keyboard sizing and add floating mode

This commit is contained in:
Aleksandras Kostarevas 2024-09-27 00:23:03 +03:00
parent 32b27b84c0
commit 3d8233be92
13 changed files with 804 additions and 275 deletions

View File

@ -16,6 +16,11 @@
<string name="more_actions_action_title">All Actions</string>
<string name="bug_viewer_action_title">Bug Viewer</string>
<string name="left_handed_keyboard_action_title">Left-Handed Keyboard</string>
<string name="right_handed_keyboard_action_title">Right-Handed Keyboard</string>
<string name="split_keyboard_action_title">Split Keyboard</string>
<string name="floating_keyboard_action_title">Floating Keyboard</string>
<string name="action_kind_action_key">Action Key</string>
<string name="action_kind_pinned_key">Pinned Action(s)</string>
<string name="action_kind_favorites">Favorite Actions</string>

View File

@ -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)
);

View File

@ -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.

View File

@ -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<ComputedKeyboardSize?> = 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, String?> =
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

View File

@ -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 {

View File

@ -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<Keyboard?>(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
)
)

View File

@ -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<KeyboardManagerForAction> {
@ -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<Action, PersistentActionState?> = 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()
}

View File

@ -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,
)

View File

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

View File

@ -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
)

View File

@ -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<Int, Int>,
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<Rect> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Rect") {
element<Int>("left")
element<Int>("top")
element<Int>("right")
element<Int>("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<Float, Float>, // 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())
}
}

View File

@ -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<LayoutEntry>, 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<LayoutEntry>, 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<LayoutRow>): 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()

View File

@ -33,7 +33,7 @@ object LayoutManager {
private fun getAllLayoutPaths(assetManager: AssetManager): List<String> {
return listFilesRecursively(assetManager, "layouts").filter {
(it.endsWith(".yml") || it.endsWith(".yaml")) && it != "mapping.yaml"
(it.endsWith(".yml") || it.endsWith(".yaml")) && it != "layouts/mapping.yaml"
}
}