Add 2D joypad cursor slider + custom bottom row layout

This commit is contained in:
2026-07-07 01:38:43 -04:00
parent 9c70f1113e
commit 4a05bf54d5
4 changed files with 152 additions and 15 deletions
+7 -5
View File
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<row height="0.95">
<key width="1.7" role="action" key0="ctrl" key1="loc switch_greekmath" key2="loc meta" key3="loc switch_clipboard" key4="switch_numeric"/>
<key width="1.1" role="action" key0="fn" key1="loc alt" key2="loc change_method" key3="switch_emoji" key4="config"/>
<key width="4.4" role="space_bar" key0="space" key7="switch_forward" key8="switch_backward" key5="cursor_left" key6="cursor_right"/>
<key width="1.1" role="action" key0="loc compose" key7="up" key6="right" key5="left" key8="down" key1="loc home" key2="loc page_up" key3="loc end" key4="loc page_down"/>
<key width="1.7" role="action" key0="enter" key1="loc voice_typing" key2="action"/>
<key width="1.7" role="action" key0="shift" key1="loc capslock"/>
<key width="1.1" role="action" key0="fn"/>
<key width="1.1" role="action" key0="ctrl" key1="loc meta"/>
<key width="1.1" role="action" key0="alt"/>
<key width="4.2" role="space_bar" key0="space" key7="switch_forward" key8="switch_backward" key5="cursor_left" key6="cursor_right"/>
<key width="2.0" role="action" key0="cursor_joystick"/>
<key width="1.5" role="action" key0="enter" key1="loc voice_typing" key2="action"/>
</row>
+7 -1
View File
@@ -758,6 +758,7 @@ public final class KeyValue implements Comparable<KeyValue>
case "cursor_right": return sliderKey(Slider.Cursor_right, 1);
case "cursor_up": return sliderKey(Slider.Cursor_up, 1);
case "cursor_down": return sliderKey(Slider.Cursor_down, 1);
case "cursor_joystick": return sliderKey(Slider.Cursor_joystick, 1);
case "selection_cancel": return editingKey("Esc", Editing.SELECTION_CANCEL, FLAG_SMALLER_FONT | FLAG_SPECIAL);
case "selection_cursor_left": return sliderKey(Slider.Selection_cursor_left, -1); // Move the left side of the selection
case "selection_cursor_right": return sliderKey(Slider.Selection_cursor_right, 1);
@@ -870,7 +871,8 @@ public final class KeyValue implements Comparable<KeyValue>
Cursor_up(0xE005, true),
Cursor_down(0xE007, true),
Selection_cursor_left(0xE008, false),
Selection_cursor_right(0xE006, false);
Selection_cursor_right(0xE006, false),
Cursor_joystick(0xE001, false);
final String symbol;
final boolean vertical;
@@ -885,6 +887,10 @@ public final class KeyValue implements Comparable<KeyValue>
return vertical;
}
public boolean isJoystick() {
return this == Cursor_joystick;
}
@Override
public String toString() { return symbol; }
+135 -3
View File
@@ -143,7 +143,10 @@ public final class Pointers implements Handler.Callback
if (ptr.hasFlagsAny(FLAG_P_SLIDING))
{
clearLatched();
ptr.sliding.onTouchUp(ptr);
if (ptr.joypad != null)
ptr.joypad.onTouchUp(ptr);
else
ptr.sliding.onTouchUp(ptr);
return;
}
stopLongPress(ptr);
@@ -267,7 +270,14 @@ public final class Pointers implements Handler.Callback
return;
if (ptr.hasFlagsAny(FLAG_P_SLIDING))
{
ptr.sliding.onTouchMove(ptr, x, y);
if (ptr.joypad != null)
{
ptr.joypad.onTouchMove(ptr, x, y);
}
else
{
ptr.sliding.onTouchMove(ptr, x, y);
}
return;
}
@@ -472,7 +482,15 @@ public final class Pointers implements Handler.Callback
int diry = dy < 0 ? -r : r;
stopLongPress(ptr);
ptr.flags |= FLAG_P_SLIDING;
ptr.sliding = new Sliding(x, y, dirx, diry, kv.getSlider());
KeyValue.Slider s = kv.getSlider();
if (s.isJoystick())
{
ptr.joypad = new Joypad(x, y, s);
}
else
{
ptr.sliding = new Sliding(x, y, dirx, diry, s);
}
}
/** Return the [FLAG_P_*] flags that correspond to pressing [kv]. */
@@ -556,6 +574,8 @@ public final class Pointers implements Handler.Callback
public int timeoutWhat;
/** [null] when not in sliding mode. */
public Sliding sliding;
/** [null] when not in joypad mode. */
public Joypad joypad;
public Pointer(int p, KeyboardData.Key k, KeyValue v, float x, float y, Modifiers m, int f)
{
@@ -569,6 +589,7 @@ public final class Pointers implements Handler.Callback
flags = f;
timeoutWhat = -1;
sliding = null;
joypad = null;
}
public boolean hasFlagsAny(int has)
@@ -672,6 +693,117 @@ public final class Pointers implements Handler.Callback
}
}
/** Joypad: a 2D slider that tracks finger position in both x and y axes.
Movement is divided into 4 directional zones (up/down/left/right).
The dominant axis determines which cursor key is fired.
Direction can change without lifting the finger. */
public final class Joypad
{
/** Accumulated distance along each axis. */
float dx_acc = 0.f;
float dy_acc = 0.f;
/** Smoothed speed (same formula as Sliding). */
float speed = 0.5f;
/** Coordinate of the last move. */
float last_x;
float last_y;
/** [System.currentTimeMillis()] at the time of the last move. -1 = not started. */
long last_move_ms = -1;
/** The slider this joypad delegates to. */
KeyValue.Slider slider;
static final float SPEED_SMOOTHING = 0.7f;
static final float SPEED_MAX = 4.f;
static final float SPEED_VERTICAL_MULT = 0.5f;
static final float SPEED_WORD_MULT = 0.25f;
public Joypad(float x, float y, KeyValue.Slider s)
{
last_x = x;
last_y = y;
slider = s;
}
public void onTouchMove(Pointer ptr, float x, float y)
{
float travelled = Math.abs(x - last_x) + Math.abs(y - last_y);
if (last_move_ms == -1)
{
if (travelled < (_config.swipe_dist_px + _config.slide_step_px))
return;
last_move_ms = System.currentTimeMillis();
}
float current_speed = speed / _config.slide_step_px;
if (ptr.modifiers.has(KeyValue.Modifier.CTRL))
current_speed *= SPEED_WORD_MULT;
// Track both axes independently
dx_acc += (x - last_x) * current_speed;
dy_acc += (y - last_y) * current_speed * SPEED_VERTICAL_MULT;
update_speed(travelled, x, y);
// Determine dominant direction and fire the appropriate event
// Use the axis with the larger absolute accumulated displacement
int dx = (int)dx_acc;
int dy = (int)dy_acc;
if (dx != 0 || dy != 0)
{
if (Math.abs(dx) >= Math.abs(dy))
{
// Horizontal movement wins
if (dx > 0)
{
dx_acc -= dx;
_handler.onPointerHold(
KeyValue.sliderKey(KeyValue.Slider.Cursor_right, dx), ptr.modifiers);
}
else
{
dx_acc -= dx;
_handler.onPointerHold(
KeyValue.sliderKey(KeyValue.Slider.Cursor_left, -dx), ptr.modifiers);
}
}
else
{
// Vertical movement wins
if (dy > 0)
{
dy_acc -= dy;
_handler.onPointerHold(
KeyValue.sliderKey(KeyValue.Slider.Cursor_down, dy), ptr.modifiers);
}
else
{
dy_acc -= dy;
_handler.onPointerHold(
KeyValue.sliderKey(KeyValue.Slider.Cursor_up, -dy), ptr.modifiers);
}
}
}
}
public void onTouchUp(Pointer ptr)
{
removePtr(ptr);
_handler.onPointerFlagsChanged(false);
}
void update_speed(float travelled, float x, float y)
{
long now = System.currentTimeMillis();
float instant_speed = Math.min(SPEED_MAX,
travelled / (float)(now - last_move_ms) + 1.f);
speed = speed + (instant_speed - speed) * SPEED_SMOOTHING;
last_move_ms = now;
last_x = x;
last_y = y;
}
}
/** Represent modifiers currently activated.
Sorted in the order they should be evaluated. */
public static final class Modifiers
+3 -6
View File
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- IcoMoon-Free 227-smile2 CC BY 4.0 -->
<!-- Generated by IcoMoon.io -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- materialdesignicons.com arrow-expand-all → 4-directional joystick -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#000000" d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM11 4c0.552 0 1 0.448 1 1s-0.448 1-1 1-1-0.448-1-1 0.448-1 1-1zM5 4c0.552 0 1 0.448 1 1s-0.448 1-1 1-1-0.448-1-1 0.448-1 1-1zM8 13c-1.82 0-3.413-0.973-4.288-2.427l1.286-0.772c0.612 1.018 1.727 1.699 3.002 1.699s2.389-0.681 3.002-1.699l1.286 0.772c-0.874 1.454-2.467 2.427-4.288 2.427z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M9.5,13.09L10.91,14.5L6.41,19H10V21H3V14H5V17.59L9.5,13.09M10.91,9.5L9.5,10.91L5,6.41V10H3V3H10V5H6.41L10.91,9.5M14.5,13.09L19,17.59V14H21V21H14V19H17.59L13.09,14.5L14.5,13.09M13.09,9.5L17.59,5H14V3H21V10H19V6.41L14.5,10.91L13.09,9.5Z" /></svg>

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 566 B