diff --git a/res/xml/bottom_row.xml b/res/xml/bottom_row.xml
index fd3de1c..153293b 100644
--- a/res/xml/bottom_row.xml
+++ b/res/xml/bottom_row.xml
@@ -1,8 +1,10 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java
index ea04112..0c2281f 100644
--- a/srcs/juloo.keyboard2/KeyValue.java
+++ b/srcs/juloo.keyboard2/KeyValue.java
@@ -758,6 +758,7 @@ public final class KeyValue implements Comparable
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
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
return vertical;
}
+ public boolean isJoystick() {
+ return this == Cursor_joystick;
+ }
+
@Override
public String toString() { return symbol; }
diff --git a/srcs/juloo.keyboard2/Pointers.java b/srcs/juloo.keyboard2/Pointers.java
index c7feedc..e005414 100644
--- a/srcs/juloo.keyboard2/Pointers.java
+++ b/srcs/juloo.keyboard2/Pointers.java
@@ -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
diff --git a/srcs/special_font/001.svg b/srcs/special_font/001.svg
index 176f5ce..ecb8f45 100644
--- a/srcs/special_font/001.svg
+++ b/srcs/special_font/001.svg
@@ -1,7 +1,4 @@
-
-
-
+
+
-
+