Split layout in landscape mode (#1334)

* Automatically split layouts in landscape

This inserts empty space in the center of layouts when in landscape
orientation. The keys are smaller and pushed to the edges of the screen.
This way, the fingers don't have to reach the center of the screen,
which is inconvenient in landscape orientation.

* Duplicate keys that are on the middle

Keys that are almost aligned on the middle of the screen are duplicated
on both sides. These keys are most probably hit with both thumbs.
This is especially useful for large keys like the space bar on the
bottom row.

* Split the bottom row

* Add an option to control layout splitting
This commit is contained in:
Jules Aguillon
2026-06-23 21:33:30 +02:00
committed by GitHub
parent c6a568a74b
commit 21633f22fd
7 changed files with 123 additions and 11 deletions
+8
View File
@@ -142,4 +142,12 @@
<item>hide</item>
<item>show</item>
</string-array>
<string-array name="pref_split_layout_entries">
<item>@string/pref_split_layout_landscape</item>
<item>@string/pref_split_layout_never</item>
</string-array>
<string-array name="pref_split_layout_values">
<item>landscape</item>
<item>never</item>
</string-array>
</resources>
+3
View File
@@ -165,4 +165,7 @@
<string name="pref_physical_keyboard_behavior">When a physical keyboard is connected</string>
<string name="pref_physical_keyboard_behavior_hide">Hide everything</string>
<string name="pref_physical_keyboard_behavior_show">Show everything</string>
<string name="pref_split_layout">Split layout</string>
<string name="pref_split_layout_never">Never</string>
<string name="pref_split_layout_landscape">In landscape orientation</string>
</resources>
+1
View File
@@ -11,6 +11,7 @@
<ListPreference android:key="number_row" android:title="@string/pref_number_row_title" android:summary="%s" android:defaultValue="no_number_row" android:entries="@array/pref_show_number_row_entries" android:entryValues="@array/pref_show_number_row_values"/>
<ListPreference android:key="show_numpad" android:title="@string/pref_show_numpad_title" android:summary="%s" android:defaultValue="1" android:entries="@array/pref_show_numpad_entries" android:entryValues="@array/pref_show_numpad_values"/>
<ListPreference android:key="numpad_layout" android:title="@string/pref_numpad_layout" android:summary="%s" android:defaultValue="high_first" android:entries="@array/pref_numpad_layout_entries" android:entryValues="@array/pref_numpad_layout_values"/>
<ListPreference android:key="split_layout" android:title="@string/pref_split_layout" android:summary="%s" android:defaultValue="landscape" android:entries="@array/pref_split_layout_entries" android:entryValues="@array/pref_split_layout_values"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_category_suggestions">
<CheckBoxPreference android:key="suggestions" android:title="@string/pref_suggestions_title" android:summary="@string/pref_suggestions_summary" android:defaultValue="true"/>
+4
View File
@@ -95,6 +95,8 @@ public final class Config
[get_current_layout()] and [set_current_layout()]. */
int current_layout_narrow;
int current_layout_wide;
/** Whether to automatically split the layout. */
public boolean split_layout;
private Config(SharedPreferences prefs, Resources res,
Boolean foldableUnfolded, Dictionaries dicts)
@@ -132,10 +134,12 @@ public final class Config
show_numpad = true;
keyboardHeightPercent = _prefs.getInt(foldable_unfolded ? "keyboard_height_landscape_unfolded" : "keyboard_height_landscape", 50);
characterSizeScale = 1.25f;
split_layout = _prefs.getString("split_layout", "landscape").equals("landscape");
}
else
{
keyboardHeightPercent = _prefs.getInt(foldable_unfolded ? "keyboard_height_unfolded" : "keyboard_height", 35);
split_layout = false;
}
layouts = LayoutsPreference.load_from_preferences(res, _prefs);
inverse_numpad = _prefs.getString("numpad_layout", "default").equals("low_first");
+29 -11
View File
@@ -43,7 +43,7 @@ public final class KeyboardData
ArrayList<Row> rows_ = new ArrayList<Row>();
for (Row r : rows)
rows_.add(r.mapKeys(f));
return new KeyboardData(this, rows_);
return with_rows(rows_);
}
/** Add keys from the given iterator into the keyboard. Preferred position is
@@ -61,7 +61,7 @@ public final class KeyboardData
}
for (KeyValue kv : unplaced_keys)
add_key_to_preferred_pos(rows, kv, PreferredPos.ANYWHERE);
return new KeyboardData(this, rows);
return with_rows(rows);
}
/** Place a key on the keyboard according to its preferred position. Mutates
@@ -141,9 +141,9 @@ public final class KeyboardData
keys.add(nps.get(i));
}
}
extendedRows.add(new Row(keys, row.height, row.shift));
extendedRows.add(row.with_keys(keys));
}
return new KeyboardData(this, extendedRows);
return with_rows(extendedRows);
}
/** Insert the given row at the given indice. The row is scaled so that the
@@ -152,7 +152,7 @@ public final class KeyboardData
{
ArrayList<Row> rows_ = new ArrayList<Row>(this.rows);
rows_.add(i, row.updateWidth(keysWidth));
return new KeyboardData(this, rows_);
return with_rows(rows_);
}
public Key findKeyWithValue(KeyValue kv)
@@ -304,10 +304,11 @@ public final class KeyboardData
}
/** Copies the fields of a keyboard, with rows changed. */
protected KeyboardData(KeyboardData src, List<Row> rows)
public KeyboardData with_rows(List<Row> rows_)
{
this(rows, compute_max_width(rows), src.modmap, src.script,
src.numpad_script, src.name, src.bottom_row, src.embedded_number_row, src.locale_extra_keys);
return new KeyboardData(rows_, compute_max_width(rows_), modmap, script,
numpad_script, name, bottom_row, embedded_number_row,
locale_extra_keys);
}
public static class Row
@@ -347,7 +348,14 @@ public final class KeyboardData
public Row copy()
{
return new Row(new ArrayList<Key>(keys), height, shift);
return with_keys(new ArrayList<Key>(keys));
}
/** Copy the row with the keys changed. The list [keys] is not copied and
should be immutable. */
public Row with_keys(List<Key> keys)
{
return new Row(keys, height, shift);
}
public void getKeys(Map<KeyValue, KeyPos> dst, int row)
@@ -368,7 +376,7 @@ public final class KeyboardData
ArrayList<Key> keys_ = new ArrayList<Key>();
for (Key k : keys)
keys_.add(f.apply(k));
return new Row(keys_, height, shift);
return with_keys(keys_);
}
/** Change the width of every keys so that the row is 's' units wide. */
@@ -533,9 +541,19 @@ public final class KeyboardData
return new Key(ks, anticircle, flags, width, shift, indication, role);
}
public Key withWidth(float w)
{
return withWidthAndShift(w, shift);
}
public Key withShift(float s)
{
return new Key(keys, anticircle, keysflags, width, s, indication, role);
return withWidthAndShift(width, s);
}
public Key withWidthAndShift(float w, float s)
{
return new Key(keys, anticircle, keysflags, w, s, indication, role);
}
public boolean hasValue(KeyValue kv)
@@ -0,0 +1,73 @@
package juloo.keyboard2;
import java.util.ArrayList;
import java.util.List;
public final class LayoutLandscapeModifier
{
/** Width added to the layout, in the key width unit of the layout. */
public static final int ADDED_WIDTH = 5;
public static KeyboardData transform_to_landscape(KeyboardData kw)
{
ArrayList<KeyboardData.Row> new_rows = new ArrayList<KeyboardData.Row>();
for (KeyboardData.Row r : kw.rows)
new_rows.add(split_row(r));
return kw.with_rows(new_rows);
}
public static KeyboardData.Row transform_number_row(KeyboardData.Row r)
{
return split_row(r);
}
static KeyboardData.Row split_row(KeyboardData.Row r)
{
if (r.keys.size() < 2)
return r;
// Split the row at the key that overlaps that mid region. If the mid
// region is entirely covered by one key, it is duplicated.
float mid_start = r.keysWidth / 2 - 0.25f;
float mid_end = mid_start + 0.5f;
float off = 0f;
int i = 0;
int end = r.keys.size() - 1; // Exclude the last key to force a split
for (; true; i++)
{
KeyboardData.Key k = r.keys.get(i);
off += k.shift + k.width;
if (off > mid_start || i == end)
{
if (off > mid_end)
return duplicate_at_index(r, i, off);
return split_at_index(r, i + 1);
}
}
}
/** Insert [ADDED_WIDTH] empty space before the key at index [i]. */
static KeyboardData.Row split_at_index(KeyboardData.Row r, int i)
{
List<KeyboardData.Key> new_keys = new ArrayList<KeyboardData.Key>(r.keys);
KeyboardData.Key k = new_keys.get(i);
new_keys.set(i, k.withShift(k.shift + ADDED_WIDTH));
return r.with_keys(new_keys);
}
/** Duplicate the key at index [i] and insert [ADDED_WIDTH] empty space in
between. */
static KeyboardData.Row duplicate_at_index(KeyboardData.Row r, int i, float off)
{
List<KeyboardData.Key> new_keys = new ArrayList<KeyboardData.Key>(r.keys);
KeyboardData.Key k = new_keys.get(i);
// Reduce the size of the duplicated keys if they would add more than 1 to
// the width.
float k_width = (k.width + 1.f) / 2;
// Adjust the size on each sides when the key is not totally centered.
float mid_d = Math.min(off - (r.keysWidth + k.width) / 2, k_width - 1);
new_keys.add(i + 1, k.withWidthAndShift(k_width + mid_d, ADDED_WIDTH - 1));
new_keys.set(i, k.withWidth(k_width - mid_d));
return r.with_keys(new_keys);
}
}
+5
View File
@@ -46,11 +46,16 @@ public final class LayoutModifier
else if (globalConfig.add_number_row && !kw.embedded_number_row) // The numpad removes the number row
{
added_number_row = modify_number_row(globalConfig.number_row_symbols ? number_row_symbols : number_row_no_symbols, kw);
if (globalConfig.split_layout)
added_number_row = LayoutLandscapeModifier.transform_number_row(added_number_row);
remove_keys.addAll(added_number_row.getKeys(0).keySet());
}
// Add the bottom row before computing the extra keys
if (kw.bottom_row)
kw = kw.insert_row(bottom_row, kw.rows.size());
// Split the layout in landscape orientation
if (globalConfig.split_layout)
kw = LayoutLandscapeModifier.transform_to_landscape(kw);
// Compose keys to add to the layout
// 'extra_keys_keyset' reflects changes made to 'extra_keys'
Set<KeyValue> extra_keys_keyset = extra_keys.keySet();