Compare commits

...

6 Commits

Author SHA1 Message Date
Jules Aguillon
1de7872881 Improve selection mode when selection ends cross
Emptying the selection with the spacebar slider puts the selection mode
in an unexpected state. This checks that this doesn't happen.

The selection ends can now cross each other, reversing the selection
order. Special care is made to make sure that the sliders go in the
expected direction.

Pointers is changed to send a `onPointerDown` for the first slider event
instead of a `onPointerHold`. This event is detected in
`KeyEventHandler` and the selection ends are reordered if needed when
sliding again.
2025-06-16 00:17:14 +02:00
Jules Aguillon
f9b844e988 Increase the clipboard history size to 6
Some checks failed
Make Apk CI / Build-Apk (push) Has been cancelled
Check translations / check-translations (push) Has been cancelled
Check layouts / check_layout.output (push) Has been cancelled
Check layouts / Generated files (push) Has been cancelled
2025-06-14 11:12:21 +02:00
Jules Aguillon
6f0244a29e Add funding.json
Some checks failed
Make Apk CI / Build-Apk (push) Has been cancelled
Check translations / check-translations (push) Has been cancelled
Check layouts / check_layout.output (push) Has been cancelled
Check layouts / Generated files (push) Has been cancelled
2025-06-04 02:08:31 +02:00
Jules Aguillon
2a50a4a129 Fix high keyboard height making it overflows
Some checks failed
Make Apk CI / Build-Apk (push) Has been cancelled
Check translations / check-translations (push) Has been cancelled
Check layouts / check_layout.output (push) Has been cancelled
Check layouts / Generated files (push) Has been cancelled
The calculation for the size of each rows now avoid making the keyboard
bigger than the screen if the Keyboard Height option is unusually high.

The height calculation with the default settings is changed slightly, it
is now assuming that a layout is 3.95 rows high instead of 4. This is
because the bottom row is usually 0.95% the size of other rows.
2025-05-30 20:07:26 +02:00
Jules Aguillon
9ab099175d Fix label size when the keyboard is unusually high
The label size is computed relatively to the width of the keyboard when
the height is larger than 3/2 of the width. This ensures that the labels
have a reasonable size when the keyboard height increases.
2025-05-30 20:07:26 +02:00
Jules Aguillon
0445c310ad Settings: Remove the upper limit in keyboard height
This can be used on phones that have several screens.
2025-05-30 20:07:26 +02:00
8 changed files with 159 additions and 33 deletions

91
funding.json Normal file
View File

@@ -0,0 +1,91 @@
{
"version": "v1.0.0",
"entity": {
"type": "individual",
"role": "owner",
"name": "Julow",
"email": "jules@j3s.fr",
"description": "Open source developer and maintainer of Unexpected Keyboard.",
"webpageUrl": {
"url": "https://github.com/Julow"
}
},
"projects": [
{
"guid": "unexpected-keyboard",
"name": "Unexpected Keyboard",
"description": "Lightweight and privacy-conscious virtual keyboard for Android.",
"webpageUrl": {
"url": "https://github.com/Julow/Unexpected-Keyboard/"
},
"repositoryUrl": {
"url": "https://github.com/Julow/Unexpected-Keyboard/"
},
"licenses": [
"spdx:GPL-3.0",
"spdx:CC0-1.0"
],
"tags": [
"android",
"mobile",
"privacy",
"productivity",
"programming",
"user-experience"
]
}
],
"funding": {
"channels": [
{
"guid": "liberapay",
"type": "other",
"address": "https://liberapay.com/Julow/",
"description": "Recurring donations for funding Unexpected Keyboard."
},
{
"guid": "github-sponsors",
"type": "other",
"address": "https://github.com/sponsors/Julow",
"description": "Recurring donations for funding Unexpected Keyboard."
},
{
"guid": "paypal",
"type": "other",
"address": "https://paypal.me/JulesAguillon",
"description": "One-time donations for funding Unexpected-keyboard."
}
],
"plans": [
{
"guid": "fund-maintenance",
"status": "active",
"name": "Fund developer time",
"description": "Help the maintainers spend time on Unexpected Keyboard",
"amount": 0,
"currency": "EUR",
"frequency": "monthly",
"channels": [
"liberapay",
"github-sponsors",
"paypal"
]
},
{
"guid": "one-time-contribution",
"status": "active",
"name": "Fund developer time",
"description": "Help the maintainers spend time on Unexpected Keyboard",
"amount": 0,
"currency": "EUR",
"frequency": "one-time",
"channels": [
"liberapay",
"github-sponsors",
"paypal"
]
}
],
"history": []
}
}

View File

@@ -40,7 +40,7 @@
<juloo.keyboard2.prefs.IntSlideBarPreference android:key="margin_bottom_landscape_unfolded" android:title="@string/pref_landscape_unfolded" android:summary="%sdp" android:defaultValue="3" min="0" max="100"/> <juloo.keyboard2.prefs.IntSlideBarPreference android:key="margin_bottom_landscape_unfolded" android:title="@string/pref_landscape_unfolded" android:summary="%sdp" android:defaultValue="3" min="0" max="100"/>
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen android:title="@string/pref_keyboard_height_title"> <PreferenceScreen android:title="@string/pref_keyboard_height_title">
<juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height" android:title="@string/pref_portrait" android:summary="%s%%" android:defaultValue="35" min="10" max="50"/> <juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height" android:title="@string/pref_portrait" android:summary="%s%%" android:defaultValue="35" min="10" max="100"/>
<juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height_landscape" android:title="@string/pref_landscape" android:summary="%s%%" android:defaultValue="50" min="20" max="65"/> <juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height_landscape" android:title="@string/pref_landscape" android:summary="%s%%" android:defaultValue="50" min="20" max="65"/>
<juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height_unfolded" android:title="@string/pref_portrait_unfolded" android:summary="%s%%" android:defaultValue="35" min="10" max="50"/> <juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height_unfolded" android:title="@string/pref_portrait_unfolded" android:summary="%s%%" android:defaultValue="35" min="10" max="50"/>
<juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height_landscape_unfolded" android:title="@string/pref_landscape_unfolded" android:summary="%s%%" android:defaultValue="50" min="20" max="65"/> <juloo.keyboard2.prefs.IntSlideBarPreference android:key="keyboard_height_landscape_unfolded" android:title="@string/pref_landscape_unfolded" android:summary="%s%%" android:defaultValue="50" min="20" max="65"/>

View File

@@ -49,7 +49,7 @@ public final class ClipboardHistoryService
/** The maximum size limits the amount of user data stored in memory but also /** The maximum size limits the amount of user data stored in memory but also
gives a sense to the user that the history is not persisted and can be gives a sense to the user that the history is not persisted and can be
forgotten as soon as the app stops. */ forgotten as soon as the app stops. */
public static final int MAX_HISTORY_SIZE = 3; public static final int MAX_HISTORY_SIZE = 6;
/** Time in ms until history entries expire. */ /** Time in ms until history entries expire. */
public static final long HISTORY_TTL_MS = 5 * 60 * 1000; public static final long HISTORY_TTL_MS = 5 * 60 * 1000;

View File

@@ -42,7 +42,8 @@ public final class Config
public long longPressInterval; public long longPressInterval;
public boolean keyrepeat_enabled; public boolean keyrepeat_enabled;
public float margin_bottom; public float margin_bottom;
public float keyHeight; public int keyboardHeightPercent;
public int screenHeightPixels;
public float horizontal_margin; public float horizontal_margin;
public float key_vertical_margin; public float key_vertical_margin;
public float key_horizontal_margin; public float key_horizontal_margin;
@@ -110,9 +111,6 @@ public final class Config
orientation_landscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; orientation_landscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
foldable_unfolded = foldableUnfolded; foldable_unfolded = foldableUnfolded;
// The height of the keyboard is relative to the height of the screen.
// This is the height of the keyboard if it have 4 rows.
int keyboardHeightPercent;
float characterSizeScale = 1.f; float characterSizeScale = 1.f;
String show_numpad_s = _prefs.getString("show_numpad", "never"); String show_numpad_s = _prefs.getString("show_numpad", "never");
show_numpad = "always".equals(show_numpad_s); show_numpad = "always".equals(show_numpad_s);
@@ -159,9 +157,7 @@ public final class Config
borderConfig = _prefs.getBoolean("border_config", false); borderConfig = _prefs.getBoolean("border_config", false);
customBorderRadius = _prefs.getInt("custom_border_radius", 0) / 100.f; customBorderRadius = _prefs.getInt("custom_border_radius", 0) / 100.f;
customBorderLineWidth = get_dip_pref(dm, "custom_border_line_width", 0); customBorderLineWidth = get_dip_pref(dm, "custom_border_line_width", 0);
// Do not substract key_vertical_margin from keyHeight because this is done screenHeightPixels = dm.heightPixels;
// during rendering.
keyHeight = dm.heightPixels * keyboardHeightPercent / 100 / 4;
horizontal_margin = horizontal_margin =
get_dip_pref_oriented(dm, "horizontal_margin", 3, 28); get_dip_pref_oriented(dm, "horizontal_margin", 3, 28);
double_tap_lock_shift = _prefs.getBoolean("lock_double_tap", false); double_tap_lock_shift = _prefs.getBoolean("lock_double_tap", false);

View File

@@ -78,6 +78,11 @@ public final class KeyEventHandler
case Compose_pending: case Compose_pending:
_autocap.stop(); _autocap.stop();
break; break;
case Slider:
// Don't wait for the next key_up and move the cursor right away. This
// is called after the trigger distance have been travelled.
handle_slider(key.getSlider(), key.getSliderRepeat(), true);
break;
default: break; default: break;
} }
} }
@@ -99,7 +104,7 @@ public final class KeyEventHandler
case Modifier: break; case Modifier: break;
case Editing: handle_editing_key(key.getEditing()); break; case Editing: handle_editing_key(key.getEditing()); break;
case Compose_pending: _recv.set_compose_pending(true); break; case Compose_pending: _recv.set_compose_pending(true); break;
case Slider: handle_slider(key.getSlider(), key.getSliderRepeat()); break; case Slider: handle_slider(key.getSlider(), key.getSliderRepeat(), false); break;
case Macro: evaluate_macro(key.getMacro()); break; case Macro: evaluate_macro(key.getMacro()); break;
} }
update_meta_state(old_mods); update_meta_state(old_mods);
@@ -261,7 +266,7 @@ public final class KeyEventHandler
} }
/** [r] might be negative, in which case the direction is reversed. */ /** [r] might be negative, in which case the direction is reversed. */
void handle_slider(KeyValue.Slider s, int r) void handle_slider(KeyValue.Slider s, int r, boolean key_down)
{ {
switch (s) switch (s)
{ {
@@ -269,8 +274,8 @@ public final class KeyEventHandler
case Cursor_right: move_cursor(r); break; case Cursor_right: move_cursor(r); break;
case Cursor_up: move_cursor_vertical(-r); break; case Cursor_up: move_cursor_vertical(-r); break;
case Cursor_down: move_cursor_vertical(r); break; case Cursor_down: move_cursor_vertical(r); break;
case Selection_cursor_left: move_cursor_sel(r, true); break; case Selection_cursor_left: move_cursor_sel(r, true, key_down); break;
case Selection_cursor_right: move_cursor_sel(r, false); break; case Selection_cursor_right: move_cursor_sel(r, false, key_down); break;
} }
} }
@@ -310,7 +315,7 @@ public final class KeyEventHandler
/** Move one of the two side of a selection. If [sel_left] is true, the left /** Move one of the two side of a selection. If [sel_left] is true, the left
position is moved, otherwise the right position is moved. */ position is moved, otherwise the right position is moved. */
void move_cursor_sel(int d, boolean sel_left) void move_cursor_sel(int d, boolean sel_left, boolean key_down)
{ {
InputConnection conn = _recv.getCurrentInputConnection(); InputConnection conn = _recv.getCurrentInputConnection();
if (conn == null) if (conn == null)
@@ -320,10 +325,23 @@ public final class KeyEventHandler
{ {
int sel_start = et.selectionStart; int sel_start = et.selectionStart;
int sel_end = et.selectionEnd; int sel_end = et.selectionEnd;
if (sel_left == (sel_start <= sel_end)) // Reorder the selection when the slider has just been pressed. The
sel_start += d; // selection might have been reversed if one end crossed the other end
else // with a previous slider.
sel_end += d; if (key_down && sel_start > sel_end)
{
sel_start = et.selectionEnd;
sel_end = et.selectionStart;
}
do
{
if (sel_left)
sel_start += d;
else
sel_end += d;
// Move the cursor twice if moving it once would make the selection
// empty and stop selection mode.
} while (sel_start == sel_end);
if (conn.setSelection(sel_start, sel_end)) if (conn.setSelection(sel_start, sel_end))
return; // Fallback to sending key events if [setSelection] failed return; // Fallback to sending key events if [setSelection] failed
} }

View File

@@ -42,6 +42,8 @@ public class Keyboard2View extends View
private Config _config; private Config _config;
private float _keyWidth; private float _keyWidth;
private float _mainLabelSize;
private float _subLabelSize;
private float _marginRight; private float _marginRight;
private float _marginLeft; private float _marginLeft;
private float _marginBottom; private float _marginBottom;
@@ -227,7 +229,7 @@ public class Keyboard2View extends View
return null; return null;
for (KeyboardData.Row row : _keyboard.rows) for (KeyboardData.Row row : _keyboard.rows)
{ {
y += (row.shift + row.height) * _config.keyHeight; y += (row.shift + row.height) * _tc.row_height;
if (ty < y) if (ty < y)
return row; return row;
} }
@@ -307,9 +309,19 @@ public class Keyboard2View extends View
_marginRight = Math.max(_config.horizontal_margin, insets_right); _marginRight = Math.max(_config.horizontal_margin, insets_right);
_marginBottom = _config.margin_bottom + insets_bottom; _marginBottom = _config.margin_bottom + insets_bottom;
_keyWidth = (width - _marginLeft - _marginRight) / _keyboard.keysWidth; _keyWidth = (width - _marginLeft - _marginRight) / _keyboard.keysWidth;
_tc = new Theme.Computed(_theme, _config, _keyWidth); _tc = new Theme.Computed(_theme, _config, _keyWidth, _keyboard);
// Compute the size of labels based on the width or the height of keys. The
// margin around keys is taken into account. Keys normal aspect ratio is
// assumed to be 3/2. It's generally more, the width computation is useful
// when the keyboard is unusually high.
float labelBaseSize = Math.min(
_tc.row_height - _tc.vertical_margin,
_keyWidth * 3/2 - _tc.horizontal_margin
) * _config.characterSize;
_mainLabelSize = labelBaseSize * _config.labelTextSize;
_subLabelSize = labelBaseSize * _config.sublabelTextSize;
int height = int height =
(int)(_config.keyHeight * _keyboard.keysHeight (int)(_tc.row_height * _keyboard.keysHeight
+ _config.marginTop + _marginBottom); + _config.marginTop + _marginBottom);
setMeasuredDimension(width, height); setMeasuredDimension(width, height);
} }
@@ -352,9 +364,9 @@ public class Keyboard2View extends View
float y = _tc.margin_top; float y = _tc.margin_top;
for (KeyboardData.Row row : _keyboard.rows) for (KeyboardData.Row row : _keyboard.rows)
{ {
y += row.shift * _config.keyHeight; y += row.shift * _tc.row_height;
float x = _marginLeft + _tc.margin_left; float x = _marginLeft + _tc.margin_left;
float keyH = row.height * _config.keyHeight - _tc.vertical_margin; float keyH = row.height * _tc.row_height - _tc.vertical_margin;
for (KeyboardData.Key k : row.keys) for (KeyboardData.Key k : row.keys)
{ {
x += k.shift * _keyWidth; x += k.shift * _keyWidth;
@@ -372,7 +384,7 @@ public class Keyboard2View extends View
drawIndication(canvas, k, x, y, keyW, keyH, _tc); drawIndication(canvas, k, x, y, keyW, keyH, _tc);
x += _keyWidth * k.width; x += _keyWidth * k.width;
} }
y += row.height * _config.keyHeight; y += row.height * _tc.row_height;
} }
} }
@@ -440,7 +452,7 @@ public class Keyboard2View extends View
kv = modifyKey(kv, _mods); kv = modifyKey(kv, _mods);
if (kv == null) if (kv == null)
return; return;
float textSize = scaleTextSize(kv, _config.labelTextSize, keyH); float textSize = scaleTextSize(kv, true);
Paint p = tc.label_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, false), textSize); Paint p = tc.label_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, false), textSize);
canvas.drawText(kv.getString(), x, (keyH - p.ascent() - p.descent()) / 2f + y, p); canvas.drawText(kv.getString(), x, (keyH - p.ascent() - p.descent()) / 2f + y, p);
} }
@@ -454,7 +466,7 @@ public class Keyboard2View extends View
kv = modifyKey(kv, _mods); kv = modifyKey(kv, _mods);
if (kv == null) if (kv == null)
return; return;
float textSize = scaleTextSize(kv, _config.sublabelTextSize, keyH); float textSize = scaleTextSize(kv, false);
Paint p = tc.sublabel_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, true), textSize, a); Paint p = tc.sublabel_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, true), textSize, a);
float subPadding = _config.keyPadding; float subPadding = _config.keyPadding;
if (v == Vertical.CENTER) if (v == Vertical.CENTER)
@@ -479,14 +491,15 @@ public class Keyboard2View extends View
if (k.indication == null || k.indication.equals("")) if (k.indication == null || k.indication.equals(""))
return; return;
Paint p = tc.indication_paint; Paint p = tc.indication_paint;
p.setTextSize(keyH * _config.sublabelTextSize * _config.characterSize); p.setTextSize(_subLabelSize);
canvas.drawText(k.indication, 0, k.indication.length(), canvas.drawText(k.indication, 0, k.indication.length(),
x + keyW / 2f, (keyH - p.ascent() - p.descent()) * 4/5 + y, p); x + keyW / 2f, (keyH - p.ascent() - p.descent()) * 4/5 + y, p);
} }
private float scaleTextSize(KeyValue k, float rel_size, float keyH) private float scaleTextSize(KeyValue k, boolean main_label)
{ {
float smaller_font = k.hasFlagsAny(KeyValue.FLAG_SMALLER_FONT) ? 0.75f : 1.f; float smaller_font = k.hasFlagsAny(KeyValue.FLAG_SMALLER_FONT) ? 0.75f : 1.f;
return keyH * rel_size * smaller_font * _config.characterSize; float label_size = main_label ? _mainLabelSize : _subLabelSize;
return label_size * smaller_font;
} }
} }

View File

@@ -306,7 +306,6 @@ public final class Pointers implements Handler.Callback
// Start sliding mode // Start sliding mode
if (new_value.getKind() == KeyValue.Kind.Slider) if (new_value.getKind() == KeyValue.Kind.Slider)
startSliding(ptr, x, y, dx, dy, new_value); startSliding(ptr, x, y, dx, dy, new_value);
_handler.onPointerDown(new_value, true);
} }
} }
@@ -469,7 +468,7 @@ public final class Pointers implements Handler.Callback
stopLongPress(ptr); stopLongPress(ptr);
ptr.flags |= FLAG_P_SLIDING; ptr.flags |= FLAG_P_SLIDING;
ptr.sliding = new Sliding(x, y, dirx, diry, kv.getSlider()); ptr.sliding = new Sliding(x, y, dirx, diry, kv.getSlider());
_handler.onPointerHold(kv, ptr.modifiers); _handler.onPointerDown(kv, true);
} }
/** Return the [FLAG_P_*] flags that correspond to pressing [kv]. */ /** Return the [FLAG_P_*] flags that correspond to pressing [kv]. */

View File

@@ -94,14 +94,23 @@ public class Theme
public final float horizontal_margin; public final float horizontal_margin;
public final float margin_top; public final float margin_top;
public final float margin_left; public final float margin_left;
public final float row_height;
public final Paint indication_paint; public final Paint indication_paint;
public final Key key; public final Key key;
public final Key key_activated; public final Key key_activated;
public Computed(Theme theme, Config config, float keyWidth) public Computed(Theme theme, Config config, float keyWidth, KeyboardData layout)
{ {
vertical_margin = config.key_vertical_margin * config.keyHeight; // Rows height is proportional to the keyboard height, meaning it doesn't
// change for layouts with more or less rows. 3.95 is the usual height of
// a layout in KeyboardData unit. The keyboard will be higher if the
// layout has more rows and smaller if it has less because rows stay the
// same height.
row_height = Math.min(
config.screenHeightPixels * config.keyboardHeightPercent / 100 / 3.95f,
config.screenHeightPixels / layout.keysHeight);
vertical_margin = config.key_vertical_margin * row_height;
horizontal_margin = config.key_horizontal_margin * keyWidth; horizontal_margin = config.key_horizontal_margin * keyWidth;
// Add half of the key margin on the left and on the top as it's also // Add half of the key margin on the left and on the top as it's also
// added on the right and on the bottom of every keys. // added on the right and on the bottom of every keys.