diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml
index 6627fea9f4..bda2db7a6d 100644
--- a/java/res/values/strings-uix.xml
+++ b/java/res/values/strings-uix.xml
@@ -440,6 +440,13 @@
Swipe Typing (alpha)
Allow swiping from key to key to write words.
+ Swipe left to delete word
+ Swipe right for space
+ Swipe down to accept middle prediction
+ Swipe diagonal down to accept side predictions
+ Swipe diagonal down right to accept right-side prediction, diagonal down left to accept left-side prediction
+ Swipe up to undo
+ Swipe Gestures
Emoji Suggestions
Suggest emojis while you\'re typing
Vibration
@@ -470,6 +477,8 @@
Configure additional layouts in the languages screen
Show action/suggestions bar
Show the bar containing suggestions. Recommended to keep enabled
+ Compact suggestion bar
+ Reduce the height of the suggestion bar and remove buttons from it
Inline autofill
Display password manager autofill and auto-reply suggestions (provided by app) in suggestion bar
Quick period key
diff --git a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
index 948fba78d3..a6be04242e 100644
--- a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
@@ -106,8 +106,16 @@ public interface KeyboardActionListener {
public void onUpWithDeletePointerActive();
public void onUpWithPointerActive();
public void onSwipeLanguage(int direction);
+ public void onSwipeAction(int direction);
public void onMovingCursorLockEvent(boolean canMoveCursor);
+ public static final int SWIPE_ACTION_LEFT = -1;
+ public static final int SWIPE_ACTION_RIGHT = 1;
+ public static final int SWIPE_ACTION_UP = 2;
+ public static final int SWIPE_ACTION_DOWN = 3;
+ public static final int SWIPE_ACTION_DOWN_LEFT = 4;
+ public static final int SWIPE_ACTION_DOWN_RIGHT = 5;
+
public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
public static class Adapter implements KeyboardActionListener {
@@ -144,6 +152,8 @@ public void onUpWithPointerActive() {}
@Override
public void onSwipeLanguage(int direction) {}
@Override
+ public void onSwipeAction(int direction) {}
+ @Override
public void onMovingCursorLockEvent(boolean canMoveCursor) {}
}
}
diff --git a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
index e09c182dc5..c775a8ffe8 100644
--- a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
@@ -150,6 +150,10 @@ public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
private boolean mStartedOnFastLongPress;
private boolean mCursorMoved = false;
private boolean mSpacebarLongPressed = false;
+ private boolean mSwipeActionTriggered = false;
+
+ private static final int sSwipeActionStep =
+ (int)(18.0 * Resources.getSystem().getDisplayMetrics().density);
// true if keyboard layout has been changed.
private boolean mKeyboardLayoutHasBeenChanged;
@@ -745,8 +749,12 @@ private void onDownEventInternal(final int x, final int y, final long eventTime)
mStartTime = System.currentTimeMillis();
mStartedOnFastLongPress = key.isFastLongPress();
mSpacebarLongPressed = false;
+ mSwipeActionTriggered = false;
- mIsSlidingCursor = key.getCode() == Constants.CODE_DELETE || key.getCode() == Constants.CODE_SPACE;
+ final boolean swipeActionsMode = !Settings.getInstance().getCurrent().mGestureInputEnabled;
+ mIsSlidingCursor = key.getCode() == Constants.CODE_DELETE
+ || key.getCode() == Constants.CODE_SPACE
+ || swipeActionsMode;
mIsFlickingKey = !mIsSlidingCursor && key.getHasFlick();
mFlickDirection = key.flickDirection(0, 0);
mCurrentKey = key;
@@ -954,6 +962,72 @@ private void onMoveEventInternal(final int x, final int y, final long eventTime)
final SettingsValues settingsValues = Settings.getInstance().getCurrent();
+ // Swipe actions (when gesture typing is off)
+ // Skip spacebar — it has its own swipe gestures (cursor movement, language switch)
+ if (mIsSlidingCursor && oldKey != null && !mSwipeActionTriggered
+ && !settingsValues.mGestureInputEnabled
+ && oldKey.getCode() != Constants.CODE_SPACE
+ && oldKey.getCode() != Constants.CODE_DELETE) {
+ final int dx = x - mStartX;
+ final int dy = y - mStartY;
+ final long swipeDistSq = (long)dx * dx + (long)dy * dy;
+ final long thresholdSq = (long)sSwipeActionStep * sSwipeActionStep;
+ final int swipeIgnoreTime = settingsValues.mKeyLongpressTimeout
+ / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+
+ if (swipeDistSq >= thresholdSq
+ && mStartTime + swipeIgnoreTime < System.currentTimeMillis()) {
+ final int absDx = Math.abs(dx);
+ final int absDy = Math.abs(dy);
+ int direction = 0;
+ boolean enabled = false;
+
+ if (absDx > 2 * absDy) {
+ // Horizontal swipe
+ if (dx < 0) {
+ direction = KeyboardActionListener.SWIPE_ACTION_LEFT;
+ enabled = settingsValues.mSwipeLeftDelete;
+ } else {
+ direction = KeyboardActionListener.SWIPE_ACTION_RIGHT;
+ enabled = settingsValues.mSwipeRightSpace;
+ }
+ } else if (absDy > 2 * absDx) {
+ // Vertical swipe
+ if (dy < 0) {
+ direction = KeyboardActionListener.SWIPE_ACTION_UP;
+ enabled = settingsValues.mSwipeUpUndo;
+ } else {
+ direction = KeyboardActionListener.SWIPE_ACTION_DOWN;
+ enabled = settingsValues.mSwipeDownPrediction;
+ }
+ } else if (dy > 0) {
+ // Diagonal down
+ if (dx < 0) {
+ direction = KeyboardActionListener.SWIPE_ACTION_DOWN_LEFT;
+ enabled = settingsValues.mSwipeDownLRPrediction;
+ } else {
+ direction = KeyboardActionListener.SWIPE_ACTION_DOWN_RIGHT;
+ enabled = settingsValues.mSwipeDownLRPrediction;
+ }
+ }
+
+ if (enabled && direction != 0) {
+ mSwipeActionTriggered = true;
+ sTimerProxy.cancelKeyTimersOf(this);
+ sListener.onSwipeAction(direction);
+ mLastX = x;
+ mLastY = y;
+ return;
+ }
+ }
+ }
+
+ if (mSwipeActionTriggered) {
+ mLastX = x;
+ mLastY = y;
+ return;
+ }
+
if (mIsSlidingCursor && oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) {
int pointerStep = sPointerStep;
if(settingsValues.mSpacebarMode == Settings.SPACEBAR_MODE_SWIPE_LANGUAGE && !mSpacebarLongPressed) {
@@ -1100,6 +1174,10 @@ private void onUpEventInternal(final int x, final int y, final long eventTime) {
sListener.onUpWithPointerActive();
}
+ if (mSwipeActionTriggered) {
+ return;
+ }
+
if(mIsFlickingKey && currentKey != null) {
final Key flickedKey = currentKey.flick(x - mStartX, y - mStartY);
detectAndSendKey(flickedKey, mKeyX, mKeyY, eventTime);
diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
index 49bd237883..89a32ab4e7 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
+++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
@@ -47,6 +47,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodSubtype;
import androidx.annotation.NonNull;
@@ -648,6 +649,41 @@ public void onSwipeLanguage(int direction) {
Subtypes.INSTANCE.switchToNextLanguage(mInputMethodService, direction);
}
+ @Override
+ public void onSwipeAction(int direction) {
+ if (direction == KeyboardActionListener.SWIPE_ACTION_LEFT) {
+ // Delete word before cursor
+ sendCtrlKeyEvent(KeyEvent.KEYCODE_DEL);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_RIGHT) {
+ // Insert space
+ onCodeInput(Constants.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_UP) {
+ // Undo
+ sendCtrlKeyEvent(KeyEvent.KEYCODE_Z);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_DOWN) {
+ // Accept center prediction
+ getLatinIME().getUixManager().pickSuggestionAtVisualPosition(1);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_DOWN_LEFT) {
+ // Accept left prediction
+ getLatinIME().getUixManager().pickSuggestionAtVisualPosition(0);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_DOWN_RIGHT) {
+ // Accept right prediction
+ getLatinIME().getUixManager().pickSuggestionAtVisualPosition(2);
+ }
+ }
+
+ private void sendCtrlKeyEvent(int keyCode) {
+ final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+ if (ic != null) {
+ final long now = android.os.SystemClock.uptimeMillis();
+ ic.sendKeyEvent(new KeyEvent(
+ now, now, KeyEvent.ACTION_DOWN, keyCode, 0, KeyEvent.META_CTRL_ON));
+ ic.sendKeyEvent(new KeyEvent(
+ now, now, KeyEvent.ACTION_UP, keyCode, 0, KeyEvent.META_CTRL_ON));
+ }
+ }
+
@Override
public void onMovingCursorLockEvent(boolean canMoveCursor) {
if(canMoveCursor) {
diff --git a/java/src/org/futo/inputmethod/latin/settings/Settings.java b/java/src/org/futo/inputmethod/latin/settings/Settings.java
index 61ac200881..f3f60b2c01 100644
--- a/java/src/org/futo/inputmethod/latin/settings/Settings.java
+++ b/java/src/org/futo/inputmethod/latin/settings/Settings.java
@@ -143,6 +143,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_USE_WESTERN_NUMERALS = "pref_use_western_numerals";
+ public static final String PREF_SWIPE_LEFT_DELETE = "swipe_left_delete";
+ public static final String PREF_SWIPE_RIGHT_SPACE = "swipe_right_space";
+ public static final String PREF_SWIPE_DOWN_PREDICTION = "swipe_down_prediction";
+ public static final String PREF_SWIPE_DOWN_LR_PREDICTION = "swipe_down_lr_prediction";
+ public static final String PREF_SWIPE_UP_UNDO = "swipe_up_undo";
+
public static final int DEFAULT_ALT_SPACES_MODE = SPACES_MODE_ALL;
// Emoji
diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
index 8c25837adb..b8f859c7d1 100644
--- a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
@@ -107,6 +107,12 @@ public class SettingsValues {
public final int mNumberRowMode;
public final int mAltSpacesMode;
+ public final boolean mSwipeLeftDelete;
+ public final boolean mSwipeRightSpace;
+ public final boolean mSwipeDownPrediction;
+ public final boolean mSwipeDownLRPrediction;
+ public final boolean mSwipeUpUndo;
+
// From the input box
@Nonnull
public final InputAttributes mInputAttributes;
@@ -202,6 +208,12 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
: Settings.NUMBER_ROW_MODE_DEFAULT;
mAltSpacesMode = prefs.getInt(Settings.PREF_ALT_SPACES_MODE, Settings.DEFAULT_ALT_SPACES_MODE);
+ mSwipeLeftDelete = prefs.getBoolean(Settings.PREF_SWIPE_LEFT_DELETE, false);
+ mSwipeRightSpace = prefs.getBoolean(Settings.PREF_SWIPE_RIGHT_SPACE, false);
+ mSwipeDownPrediction = prefs.getBoolean(Settings.PREF_SWIPE_DOWN_PREDICTION, false);
+ mSwipeDownLRPrediction = prefs.getBoolean(Settings.PREF_SWIPE_DOWN_LR_PREDICTION, false);
+ mSwipeUpUndo = prefs.getBoolean(Settings.PREF_SWIPE_UP_UNDO, false);
+
mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI
&& prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true);
// Compute other readable settings
diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
index 0d981c1e17..d755eed96d 100644
--- a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
@@ -49,6 +49,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableFloatState
@@ -161,6 +162,17 @@ import kotlin.math.roundToInt
*/
val ActionBarHeight = 40.dp
+val CompactActionBarHeight = 26.dp
+
+val SuggestionBarCompactSetting = SettingsKey(
+ booleanPreferencesKey("suggestion_bar_compact"),
+ false
+)
+
+val LocalSuggestionBarCompact = staticCompositionLocalOf { false }
+
+val currentActionBarHeight: Dp
+ @Composable get() = if (LocalSuggestionBarCompact.current) CompactActionBarHeight else ActionBarHeight
val ActionBarScrollIndexSetting = SettingsKey(
intPreferencesKey("action_bar_scroll_index"),
@@ -196,7 +208,6 @@ val suggestionStylePrimary = TextStyle(
fontSize = 18.sp,
lineHeight = 26.sp,
letterSpacing = 0.5.sp,
- //textAlign = TextAlign.Center
)
val suggestionStyleAlternative = TextStyle(
@@ -205,7 +216,22 @@ val suggestionStyleAlternative = TextStyle(
fontSize = 18.sp,
lineHeight = 26.sp,
letterSpacing = 0.5.sp,
- //textAlign = TextAlign.Center
+)
+
+val suggestionStylePrimaryCompact = TextStyle(
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 18.sp,
+ letterSpacing = 0.5.sp,
+)
+
+val suggestionStyleAlternativeCompact = TextStyle(
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 18.sp,
+ letterSpacing = 0.5.sp,
)
val suggestionStyleCandidateDescription = TextStyle(
@@ -315,9 +341,12 @@ fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean,
else -> Modifier
}
- val textStyle = when(isAutocorrect) {
- true -> suggestionStylePrimary
- false -> suggestionStyleAlternative
+ val compact = LocalSuggestionBarCompact.current
+ val textStyle = when {
+ isAutocorrect && compact -> suggestionStylePrimaryCompact
+ isAutocorrect -> suggestionStylePrimary
+ compact -> suggestionStyleAlternativeCompact
+ else -> suggestionStyleAlternative
}.copy(color = color).withCustomFont()
Box(
@@ -787,13 +816,15 @@ fun ActionBar(
val view = LocalView.current
val context = LocalContext.current
+ val compact = LocalSuggestionBarCompact.current
+ val barHeight = if (compact) CompactActionBarHeight else ActionBarHeight
val oldActionBar = useDataStore(OldStyleActionsBar)
- val useDoubleHeight = isActionsExpanded && oldActionBar.value == false
+ val useDoubleHeight = isActionsExpanded && oldActionBar.value == false && !compact
Column(Modifier
.height(
- ActionBarHeight * (if (useDoubleHeight) 2 else 1).let {
+ barHeight * (if (useDoubleHeight) 2 else 1).let {
if(needToUseExpandableSuggestionUi) {
it - 1
} else {
@@ -805,7 +836,7 @@ fun ActionBar(
testTag = "ActionBar"
testTagsAsResourceId = true
}) {
- if(isActionsExpanded && !oldActionBar.value) {
+ if(isActionsExpanded && !oldActionBar.value && !compact) {
ActionSep()
Surface(
@@ -827,30 +858,33 @@ fun ActionBar(
.weight(1.0f), color = actionBarColor()
) {
Row(Modifier.safeKeyboardPadding()) {
- ExpandActionsButton(isActionsExpanded) {
- toggleActionsExpanded()
+ if (!compact) {
+ ExpandActionsButton(isActionsExpanded) {
+ toggleActionsExpanded()
- keyboardManagerForAction?.performHapticAndAudioFeedback(
- Constants.CODE_TAB,
- view
- )
+ keyboardManagerForAction?.performHapticAndAudioFeedback(
+ Constants.CODE_TAB,
+ view
+ )
+ }
}
- if(oldActionBar.value && isActionsExpanded) {
+ if(oldActionBar.value && isActionsExpanded && !compact) {
Box(modifier = Modifier
.weight(1.0f)
.fillMaxHeight()) {
ActionItems(onActionActivated, onActionAltActivated)
}
} else {
- if (importantNotice != null) {
+ if (importantNotice != null && !compact) {
ImportantNoticeView(importantNotice)
} else {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& inlineSuggestions.isNotEmpty()
+ && !compact
) {
InlineSuggestions(inlineSuggestions)
- } else if(quickClipState != null) {
+ } else if(quickClipState != null && !compact) {
QuickClipView(quickClipState, onQuickClipDismiss)
} else if (words != null) {
SuggestionItems(
@@ -873,7 +907,7 @@ fun ActionBar(
Spacer(modifier = Modifier.weight(1.0f))
}
- if(inlineSuggestions.isEmpty()) {
+ if(inlineSuggestions.isEmpty() && !compact) {
PinnedActionItems(onActionActivated, onActionAltActivated)
}
}
@@ -892,7 +926,7 @@ fun ActionWindowBar(
onBack: () -> Unit,
onExpand: () -> Unit
) {
- Column(Modifier.height(ActionBarHeight)) {
+ Column(Modifier.height(currentActionBarHeight)) {
ActionSep()
Surface(
modifier = Modifier
@@ -955,7 +989,7 @@ fun CollapsibleSuggestionsBar(
words: SuggestedWords?,
suggestionStripListener: SuggestionStripViewListener,
) {
- Column(Modifier.height(ActionBarHeight)) {
+ Column(Modifier.height(currentActionBarHeight)) {
ActionSep()
Surface(
modifier = Modifier
@@ -1215,7 +1249,7 @@ private fun RowScope.InlineCandidates(
}
}
itemsIndexed(wordList) { i, it ->
- CandidateItem(Modifier.height(ActionBarHeight), it,
+ CandidateItem(Modifier.height(currentActionBarHeight), it,
listener = suggestionStripListener,
last = i == wordList.size-1,
width = with(LocalDensity.current) {
@@ -1335,7 +1369,7 @@ fun BoxScope.ActionBarWithExpandableCandidates(
if(canShowSuggest) {
Surface(
- Modifier.fillMaxWidth().padding(0.dp, ActionBarHeight, 0.dp, 0.dp)
+ Modifier.fillMaxWidth().padding(0.dp, currentActionBarHeight, 0.dp, 0.dp)
.heightIn(max = with(density) { keyboardOffset?.intValue?.toDp() ?: 0.dp })
.safeKeyboardPadding()
) {
@@ -1343,7 +1377,7 @@ fun BoxScope.ActionBarWithExpandableCandidates(
listToRender ?: emptyList(),
itemMeasurer = { measureWord(density, widths, it) }
) { allocatedWidth, item, isLast ->
- CandidateItem(Modifier.height(ActionBarHeight), item, listener = suggestionStripListener, width=with(density) { allocatedWidth.toDp() }, last=isLast)
+ CandidateItem(Modifier.height(currentActionBarHeight), item, listener = suggestionStripListener, width=with(density) { allocatedWidth.toDp() }, last=isLast)
}
}
}
@@ -1353,7 +1387,7 @@ fun BoxScope.ActionBarWithExpandableCandidates(
testTag = "ActionBar"
testTagsAsResourceId = true
}
- .height(ActionBarHeight)
+ .height(currentActionBarHeight)
.align(Alignment.TopCenter)
) {
ActionSep()
diff --git a/java/src/org/futo/inputmethod/latin/uix/QuickClip.kt b/java/src/org/futo/inputmethod/latin/uix/QuickClip.kt
index aeb88e8e23..625850e544 100644
--- a/java/src/org/futo/inputmethod/latin/uix/QuickClip.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/QuickClip.kt
@@ -322,7 +322,7 @@ object QuickClip {
@Preview
@Composable
private fun PreviewQuickClips() {
- Row(Modifier.height(ActionBarHeight)) {
+ Row(Modifier.height(currentActionBarHeight)) {
QuickClipView(QuickClipState(
texts = listOf(QuickClipItem(
kind = QuickClipKind.FullString,
diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
index 831f0ff540..976d59caa6 100644
--- a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
@@ -132,6 +132,7 @@ import org.futo.inputmethod.latin.uix.settings.DataStoreCacheProvider
import org.futo.inputmethod.latin.uix.settings.pages.ActionBarDisplayedSetting
import org.futo.inputmethod.latin.uix.settings.pages.InlineAutofillSetting
import org.futo.inputmethod.latin.uix.settings.useDataStore
+import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
import org.futo.inputmethod.latin.uix.theme.KeyboardSurfaceShaderBackground
import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.theme.UixThemeAuto
@@ -227,12 +228,13 @@ fun BoxScope.KeyboardBackground(
shader != null -> KeyboardSurfaceShaderBackground(shader, modifier = Modifier.matchParentSize())
image != null && rect != null -> {
val navbarHeight = navBarHeight()
+ val actionBarHeightDp = currentActionBarHeight
Canvas(Modifier.matchParentSize()) {
drawRect(colorScheme.keyboardSurface)
val fixedWidth = computedSize?.width?.toFloat() ?: size.width
val fixedHeight = when {
- (computedSize != null) -> computedSize.height + ActionBarHeight.toPx() + navbarHeight.toPx()
+ (computedSize != null) -> computedSize.height + actionBarHeightDp.toPx() + navbarHeight.toPx()
else -> size.height
}
@@ -777,7 +779,7 @@ class UixManager(private val latinIME: LatinIME) {
currWindowActionWindow.value?.fixedWindowHeight ?: ((latinIME
.getInputViewHeight()
.toFloat() / heightDiv.toFloat()).toDp() +
- if (actionsExpanded) ActionBarHeight else 0.dp)
+ if (actionsExpanded) currentActionBarHeight else 0.dp)
})
.safeKeyboardPadding()
) {
@@ -1099,7 +1101,7 @@ class UixManager(private val latinIME: LatinIME) {
Column(modifier = Modifier
.matchParentSize()
.absolutePadding(
- top = if (isActionsExpanded.value) ActionBarHeight else 0.dp
+ top = if (isActionsExpanded.value) currentActionBarHeight else 0.dp
), horizontalAlignment = when(size.direction) {
// Aligned opposite of the keyboard
OneHandedDirection.Left -> Alignment.End
@@ -1185,11 +1187,13 @@ class UixManager(private val latinIME: LatinIME) {
private fun ProvidersAndWrapper(content: @Composable () -> Unit) {
UixThemeWrapper(latinIME.colorScheme) {
DataStoreCacheProvider {
+ val compactSuggestionBar = useDataStoreValue(SuggestionBarCompactSetting)
CompositionLocalProvider(
LocalManager provides keyboardManagerForAction,
LocalThemeProvider provides latinIME.getDrawableProvider(),
LocalLayoutDirection provides LayoutDirection.Ltr,
- LocalFoldingState provides foldingOptions.value
+ LocalFoldingState provides foldingOptions.value,
+ LocalSuggestionBarCompact provides compactSuggestionBar
) {
Box(Modifier
.fillMaxSize()
@@ -1428,6 +1432,57 @@ class UixManager(private val latinIME: LatinIME) {
}
}
+ fun getSuggestionAtVisualPosition(position: Int): SuggestedWordInfo? {
+ val words = suggestedWords.value ?: return null
+ val layout = makeSuggestionLayout(words, null)
+
+ if (layout.isGestureBatch || (layout.emojiMatches.isEmpty() && layout.presentableSuggestions.size <= 1)) {
+ return if (position == 1) layout.presentableSuggestions.firstOrNull() else null
+ }
+
+ if (layout.autocorrectMatch != null) {
+ var supplementalIndex = 0
+ val left = if (layout.emojiMatches.isEmpty()) {
+ layout.sortedMatches.getOrNull(supplementalIndex++)
+ } else {
+ layout.emojiMatches.getOrNull(0)
+ }
+ val center = layout.autocorrectMatch
+ val right = if (layout.verbatimWord != null && layout.verbatimWord.mWord != layout.autocorrectMatch.mWord) {
+ layout.verbatimWord
+ } else {
+ layout.sortedMatches.getOrNull(supplementalIndex)
+ }
+ return when (position) {
+ 0 -> left
+ 1 -> center
+ 2 -> right
+ else -> null
+ }
+ }
+
+ // No autocorrect
+ var supplementalIndex = 1
+ val left = if (layout.emojiMatches.isEmpty()) {
+ layout.sortedMatches.getOrNull(supplementalIndex++)
+ } else {
+ layout.emojiMatches.getOrNull(0)
+ }
+ val center = layout.sortedMatches.getOrNull(0)
+ val right = layout.sortedMatches.getOrNull(supplementalIndex)
+ return when (position) {
+ 0 -> left
+ 1 -> center
+ 2 -> right
+ else -> null
+ }
+ }
+
+ fun pickSuggestionAtVisualPosition(position: Int) {
+ val suggestion = getSuggestionAtVisualPosition(position) ?: return
+ latinIME.latinIMELegacy.pickSuggestionManually(suggestion)
+ }
+
fun requestForgetWord(suggestedWordInfo: SuggestedWords.SuggestedWordInfo) {
keyboardManagerForAction.requestDialog(
latinIME.getString(R.string.keyboard_suggest_blacklist_body, suggestedWordInfo.mWord),
diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt
index 3526a44ed9..499485a1a9 100644
--- a/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/actions/EmojiAction.kt
@@ -100,6 +100,7 @@ import org.futo.inputmethod.latin.uix.EmojiTracker.getRecentEmojis
import org.futo.inputmethod.latin.uix.EmojiTracker.resetRecentEmojis
import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji
import org.futo.inputmethod.latin.uix.LocalKeyboardScheme
+import org.futo.inputmethod.latin.uix.LocalSuggestionBarCompact
import org.futo.inputmethod.latin.uix.PersistentActionState
import org.futo.inputmethod.latin.uix.actions.emoji.EmojiItem
import org.futo.inputmethod.latin.uix.actions.emoji.EmojiView
@@ -1011,6 +1012,11 @@ val EmojiAction = Action(
@Composable
override fun WindowTitleBar(rowScope: RowScope) {
val context = LocalContext.current
+ val compact = LocalSuggestionBarCompact.current
+ if(compact) {
+ super.WindowTitleBar(rowScope)
+ return
+ }
if(searching.value) {
with(rowScope) {
Surface(
diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt b/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt
index 5375ea9d95..8670b8d7fb 100644
--- a/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/actions/KeyboardSizingActions.kt
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.Action
import org.futo.inputmethod.latin.uix.ActionBarHeight
+import org.futo.inputmethod.latin.uix.currentActionBarHeight
import org.futo.inputmethod.latin.uix.ActionWindow
import org.futo.inputmethod.latin.uix.CloseResult
import org.futo.inputmethod.latin.uix.TutorialMode
@@ -92,7 +93,7 @@ val KeyboardModeAction = Action(
override fun WindowContents(keyboardShown: Boolean) {
val currMode = sizeCalculator.getSavedSettings().currentMode
Column {
- Row(Modifier.height(ActionBarHeight)) {
+ Row(Modifier.height(currentActionBarHeight)) {
// Hide the back button in the resize tutorial
if(manager.getTutorialMode() != TutorialMode.ResizerTutorial) {
IconButton(onClick = {
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
index de8fea13c6..044b5d7643 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
@@ -96,6 +96,7 @@ import org.futo.inputmethod.latin.settings.toLongPressKeyLayoutItems
import org.futo.inputmethod.latin.uix.AndroidTextInput
import org.futo.inputmethod.latin.uix.BasicThemeProvider
import org.futo.inputmethod.latin.uix.KeyHintsSetting
+import org.futo.inputmethod.latin.uix.SuggestionBarCompactSetting
import org.futo.inputmethod.latin.uix.LocalKeyboardScheme
import org.futo.inputmethod.latin.uix.SHOW_EMOJI_SUGGESTIONS
import org.futo.inputmethod.latin.uix.SettingsKey
@@ -107,6 +108,7 @@ import org.futo.inputmethod.latin.uix.settings.DropDownPickerSettingItem
import org.futo.inputmethod.latin.uix.settings.LocalSharedPrefsCache
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.PrimarySettingToggleDataStoreItem
+import org.futo.inputmethod.latin.uix.settings.SettingToggleRaw
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.SettingItem
@@ -783,6 +785,13 @@ val KeyboardSettingsMenu = UserSettingsMenu(
Icon(painterResource(id = R.drawable.more_horizontal), contentDescription = null)
}
),
+ userSettingToggleDataStore(
+ title = R.string.keyboard_settings_compact_suggestion_bar,
+ subtitle = R.string.keyboard_settings_compact_suggestion_bar_subtitle,
+ setting = SuggestionBarCompactSetting,
+ ).copy(visibilityCheck = {
+ useDataStore(ActionBarDisplayedSetting).value
+ }),
userSettingToggleDataStore(
title = R.string.keyboard_settings_inline_autofill,
subtitle = R.string.keyboard_settings_inline_autofill_subtitle,
@@ -807,16 +816,81 @@ val TypingSettingsMenu = UserSettingsMenu(
AutoSpacesSetting()
}
),
- userSettingToggleSharedPrefs(
- title = R.string.typing_settings_swipe,
+ UserSetting(
+ name = R.string.typing_settings_swipe,
subtitle = R.string.typing_settings_swipe_subtitle,
- key = Settings.PREF_GESTURE_INPUT,
- default = {true},
- icon = {
- Icon(painterResource(id = R.drawable.swipe_icon), contentDescription = null,
- tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f))
- }
- ),
+ ) {
+ val gestureInput = useSharedPrefsBool(Settings.PREF_GESTURE_INPUT, true)
+ val leftDelete = useSharedPrefsBool(Settings.PREF_SWIPE_LEFT_DELETE, false)
+ val rightSpace = useSharedPrefsBool(Settings.PREF_SWIPE_RIGHT_SPACE, false)
+ val downPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_PREDICTION, false)
+ val downLrPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_LR_PREDICTION, false)
+ val upUndo = useSharedPrefsBool(Settings.PREF_SWIPE_UP_UNDO, false)
+
+ SettingToggleRaw(
+ title = stringResource(R.string.typing_settings_swipe),
+ subtitle = stringResource(R.string.typing_settings_swipe_subtitle),
+ enabled = gestureInput.value,
+ setValue = { newValue ->
+ gestureInput.setValue(newValue)
+ if (newValue) {
+ leftDelete.setValue(false)
+ rightSpace.setValue(false)
+ downPrediction.setValue(false)
+ downLrPrediction.setValue(false)
+ upUndo.setValue(false)
+ }
+ },
+ icon = {
+ Icon(painterResource(id = R.drawable.swipe_icon), contentDescription = null,
+ tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f))
+ }
+ )
+ },
+
+ UserSetting(name = R.string.swipe_gestures_title) {
+ val gestureInputOn = useSharedPrefsBool(Settings.PREF_GESTURE_INPUT, true)
+ val enabled = !gestureInputOn.value
+
+ val leftDelete = useSharedPrefsBool(Settings.PREF_SWIPE_LEFT_DELETE, false)
+ val rightSpace = useSharedPrefsBool(Settings.PREF_SWIPE_RIGHT_SPACE, false)
+ val downPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_PREDICTION, false)
+ val downLrPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_LR_PREDICTION, false)
+ val upUndo = useSharedPrefsBool(Settings.PREF_SWIPE_UP_UNDO, false)
+
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_left_delete),
+ enabled = leftDelete.value,
+ disabled = !enabled,
+ setValue = { leftDelete.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_right_space),
+ enabled = rightSpace.value,
+ disabled = !enabled,
+ setValue = { rightSpace.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_down_prediction),
+ enabled = downPrediction.value,
+ disabled = !enabled,
+ setValue = { downPrediction.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_down_lr_prediction),
+ subtitle = stringResource(R.string.swipe_gesture_down_lr_prediction_subtitle),
+ enabled = downLrPrediction.value,
+ disabled = !enabled,
+ setValue = { downLrPrediction.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_up_undo),
+ enabled = upUndo.value,
+ disabled = !enabled,
+ setValue = { upUndo.setValue(it) }
+ )
+ },
+
userSettingToggleDataStore(
title = R.string.typing_settings_suggest_emojis,
subtitle = R.string.typing_settings_suggest_emojis_subtitle,
diff --git a/java/src/org/futo/inputmethod/v2keyboard/Keyboard.kt b/java/src/org/futo/inputmethod/v2keyboard/Keyboard.kt
index 2624629bf5..e5c05dd719 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/Keyboard.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/Keyboard.kt
@@ -293,7 +293,16 @@ data class Keyboard(
val autoShift: Boolean = true,
val subKeyboards: Map = emptyMap(),
- val imeHint: String? = null
+ val imeHint: String? = null,
+
+ /**
+ * (optional) Whether keyboard rows use staggered (center-gapped) alignment.
+ * When true (default), rows with fewer keys than the widest row are centered with
+ * gaps on either side, matching the traditional staggered keyboard layout.
+ * When false, rows are left-aligned with no centering gaps, producing an
+ * ortholinear/grid-style layout.
+ */
+ val staggered: Boolean = true
//val element: KeyboardElement = KeyboardElement.Alphabet,
diff --git a/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt b/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt
index 6aa363ff41..1c39f9683d 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/KeyboardSizingCalculator.kt
@@ -26,6 +26,7 @@ import org.futo.inputmethod.latin.FoldStateProvider
import org.futo.inputmethod.latin.LatinIME
import org.futo.inputmethod.latin.settings.SettingsValues
import org.futo.inputmethod.latin.uix.OldStyleActionsBar
+import org.futo.inputmethod.latin.uix.SuggestionBarCompactSetting
import org.futo.inputmethod.latin.uix.SettingsKey
import org.futo.inputmethod.latin.uix.UixManager
import org.futo.inputmethod.latin.uix.getSetting
@@ -553,7 +554,8 @@ class KeyboardSizingCalculator(val context: Context, val uixManager: UixManager)
}
fun calculateSuggestionBarHeightDp(): Float {
- return 40.0f
+ val compact = context.getSetting(SuggestionBarCompactSetting) == true
+ return if (compact) 26.0f else 40.0f
}
fun calculateTotalActionBarHeightPx(): Int =
diff --git a/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt b/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt
index f4519e35f3..19f2731bde 100644
--- a/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt
+++ b/java/src/org/futo/inputmethod/v2keyboard/LayoutEngine.kt
@@ -360,7 +360,15 @@ data class LayoutEngine(
val totalRowWidth = computedRow.sumOf { it.widthPx.toDouble() }.toFloat()
val rowLayoutWidth = if(splittable) { layoutWidth } else { unsplitLayoutWidth }
- val entries = mergeDuplicates(computedRow.addGap(rowLayoutWidth - totalRowWidth))
+ val entries = if(keyboard.staggered) {
+ mergeDuplicates(computedRow.addGap(rowLayoutWidth - totalRowWidth))
+ } else {
+ // Stretch keys to fill the entire row width (ortholinear/grid layout)
+ val extraPerKey = (rowLayoutWidth - totalRowWidth) / computedRow.size
+ computedRow.map { key ->
+ LayoutEntry.Key(key.data, key.widthPx + extraPerKey)
+ }
+ }
return LayoutRow(
entries = entries,