Compare commits

...

3 Commits

Author SHA1 Message Date
Jules Aguillon
506912be60 Fix other modifiers not applied
This adds a "GESTURE" modifier in KeyValue that materialize the gesture
into [Modifiers] and allow it to be applied at the same time as the
other modifiers, in the right order.

This moves the key modifying code back into Pointer to allow more
modifier manipulations.
2024-05-25 02:19:55 +02:00
Jules Aguillon
d03afb6e8f Apply Shift then fallback to Fn
This changes both the round trip and the clockwise circle gestures to
first apply Shift and if that didn't change the key, fallback to Fn.

This allows to have letters in the corner of keys while being able to
shift them. Clockwise circle modify keys the same way as round trip for
the center key.

The anti-clockwise gesture now do nothing. It's intended to make it
configurable per-layout.
2024-05-25 01:20:15 +02:00
Jules Aguillon
8021a626c5 Circle and round trip gestures
This implements clockwise/anticlockwise circle and round trip gestures
inspired by Messagease.

The circle gestures start after a small threshold to avoid making the
regular swipe too hard to aim.

The gestures do:

- clockwise circle: Shift variant of the center symbol
- anticlockwise circle: Fn variant of the center symbol
- round trip: Fn variant of the targetted side symbol

The Gesture class now keep track of what the pointer is doing while it
moves on a key. It replaces the 'selected_direction' integer.
2024-05-23 21:05:05 +02:00
4 changed files with 274 additions and 42 deletions

View File

@ -0,0 +1,141 @@
package juloo.keyboard2;
public final class Gesture
{
/** The pointer direction that caused the last state change.
Integer from 0 to 15 (included). */
int current_dir;
State state;
public Gesture(int starting_direction)
{
current_dir = starting_direction;
state = State.Swiped;
}
enum State
{
Cancelled,
Swiped,
Rotating_clockwise,
Rotating_anticlockwise,
Ended_swipe,
Ended_center,
Ended_clockwise,
Ended_anticlockwise
}
enum Name
{
None,
Swipe,
Roundtrip,
Circle,
Anticircle
}
/** Angle to travel before a rotation gesture starts. A threshold too low
would be too easy to reach while doing back and forth gestures, as the
quadrants are very small. In the same unit as [current_dir] */
static final int ROTATION_THRESHOLD = 2;
/** Return the currently recognized gesture. Return [null] if no gesture is
recognized. Might change everytime [changed_direction] return [true]. */
public Name get_gesture()
{
switch (state)
{
case Cancelled:
return Name.None;
case Swiped:
case Ended_swipe:
return Name.Swipe;
case Ended_center:
return Name.Roundtrip;
case Rotating_clockwise:
case Ended_clockwise:
return Name.Circle;
case Rotating_anticlockwise:
case Ended_anticlockwise:
return Name.Anticircle;
}
return Name.None; // Unreachable
}
public boolean is_in_progress()
{
switch (state)
{
case Swiped:
case Rotating_clockwise:
case Rotating_anticlockwise:
return true;
}
return false;
}
public int current_direction() { return current_dir; }
/** The pointer changed direction. Return [true] if the gesture changed
state and [get_gesture] return a different value. */
public boolean changed_direction(int direction)
{
int d = dir_diff(current_dir, direction);
boolean clockwise = d > 0;
switch (state)
{
case Swiped:
if (Math.abs(d) < ROTATION_THRESHOLD)
return false;
// Start a rotation
state = (clockwise) ?
State.Rotating_clockwise : State.Rotating_anticlockwise;
current_dir = direction;
return true;
// Check that rotation is not reversing
case Rotating_clockwise:
case Rotating_anticlockwise:
current_dir = direction;
if ((state == State.Rotating_clockwise) == clockwise)
return false;
state = State.Cancelled;
return true;
}
return false;
}
/** Return [true] if [get_gesture] will return a different value. */
public boolean moved_to_center()
{
switch (state)
{
case Swiped: state = State.Ended_center; return true;
case Rotating_clockwise: state = State.Ended_clockwise; return false;
case Rotating_anticlockwise: state = State.Ended_anticlockwise; return false;
}
return false;
}
/** Will not change the gesture state. */
public void pointer_up()
{
switch (state)
{
case Swiped: state = State.Ended_swipe; break;
case Rotating_clockwise: state = State.Ended_clockwise; break;
case Rotating_anticlockwise: state = State.Ended_anticlockwise; break;
}
}
static int dir_diff(int d1, int d2)
{
final int n = 16;
// Shortest-path in modulo arithmetic
if (d1 == d2)
return 0;
int left = (d1 - d2 + n) % n;
int right = (d2 - d1 + n) % n;
return (left < right) ? -left : right;
}
}

View File

@ -33,7 +33,6 @@ public final class KeyModifier
if (r == null) if (r == null)
{ {
r = k; r = k;
/* Order: Fn, Shift, accents */
for (int i = 0; i < n_mods; i++) for (int i = 0; i < n_mods; i++)
r = modify(r, mods.get(i)); r = modify(r, mods.get(i));
ks.put(mods, r); ks.put(mods, r);
@ -70,6 +69,7 @@ public final class KeyModifier
case ALT: case ALT:
case META: return turn_into_keyevent(k); case META: return turn_into_keyevent(k);
case FN: return apply_fn(k); case FN: return apply_fn(k);
case GESTURE: return apply_gesture(k);
case SHIFT: return apply_shift(k); case SHIFT: return apply_shift(k);
case GRAVE: return apply_map_char(k, map_char_grave); case GRAVE: return apply_map_char(k, map_char_grave);
case AIGU: return apply_map_char(k, map_char_aigu); case AIGU: return apply_map_char(k, map_char_aigu);
@ -139,11 +139,10 @@ public final class KeyModifier
case Char: case Char:
char kc = k.getChar(); char kc = k.getChar();
String modified = map.apply(kc); String modified = map.apply(kc);
if (modified == null) if (modified != null)
return k;
return KeyValue.makeStringKey(modified, k.getFlags()); return KeyValue.makeStringKey(modified, k.getFlags());
default: return k;
} }
return k;
} }
private static KeyValue apply_shift(KeyValue k) private static KeyValue apply_shift(KeyValue k)
@ -482,6 +481,15 @@ public final class KeyModifier
return k.withKeyevent(e); return k.withKeyevent(e);
} }
/** Modify a key affected by a round-trip or a clockwise circle gesture. */
private static KeyValue apply_gesture(KeyValue k)
{
KeyValue shifted = apply_shift(k);
if (shifted == null || shifted.equals(k))
return apply_fn(k);
return shifted;
}
/* Lookup the cache entry for a key. Create it needed. */ /* Lookup the cache entry for a key. Create it needed. */
private static HashMap<Pointers.Modifiers, KeyValue> cacheEntry(KeyValue k) private static HashMap<Pointers.Modifiers, KeyValue> cacheEntry(KeyValue k)
{ {

View File

@ -27,6 +27,7 @@ public final class KeyValue implements Comparable<KeyValue>
public static enum Modifier public static enum Modifier
{ {
SHIFT, SHIFT,
GESTURE,
CTRL, CTRL,
ALT, ALT,
META, META,
@ -54,8 +55,8 @@ public final class KeyValue implements Comparable<KeyValue>
ARROW_RIGHT, ARROW_RIGHT,
BREVE, BREVE,
BAR, BAR,
FN, // Must be placed last to be applied first FN,
} } // Last is be applied first
public static enum Editing public static enum Editing
{ {
@ -404,6 +405,12 @@ public final class KeyValue implements Comparable<KeyValue>
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT); return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
} }
/** Make a modifier key for passing to [KeyModifier]. */
public static KeyValue makeInternalModifier(Modifier mod)
{
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
}
public static KeyValue getKeyByName(String name) public static KeyValue getKeyByName(String name)
{ {
switch (name) switch (name)

View File

@ -143,6 +143,12 @@ public final class Pointers implements Handler.Callback
return; return;
} }
stopKeyRepeat(ptr); stopKeyRepeat(ptr);
KeyValue ptr_value = ptr.value;
if (ptr.gesture != null && ptr.gesture.is_in_progress())
{
// A gesture was in progress
ptr.gesture.pointer_up();
}
Pointer latched = getLatched(ptr); Pointer latched = getLatched(ptr);
if (latched != null) // Already latched if (latched != null) // Already latched
{ {
@ -152,7 +158,7 @@ public final class Pointers implements Handler.Callback
else // Otherwise, unlatch else // Otherwise, unlatch
{ {
removePtr(latched); removePtr(latched);
_handler.onPointerUp(ptr.value, ptr.modifiers); _handler.onPointerUp(ptr_value, ptr.modifiers);
} }
} }
else if ((ptr.flags & FLAG_P_LATCHABLE) != 0) else if ((ptr.flags & FLAG_P_LATCHABLE) != 0)
@ -168,7 +174,7 @@ public final class Pointers implements Handler.Callback
{ {
clearLatched(); clearLatched();
removePtr(ptr); removePtr(ptr);
_handler.onPointerUp(ptr.value, ptr.modifiers); _handler.onPointerUp(ptr_value, ptr.modifiers);
} }
} }
@ -217,18 +223,15 @@ public final class Pointers implements Handler.Callback
return k.keys[DIRECTION_TO_INDEX[direction]]; return k.keys[DIRECTION_TO_INDEX[direction]];
} }
/* /**
* Get the KeyValue at the given direction. In case of swipe (direction != * Get the key nearest to [direction] that is not key0. Take care
* null), get the nearest KeyValue that is not key0. * of applying [_handler.modifyKey] to the selected key in the same
* Take care of applying [_handler.onPointerSwipe] to the selected key, this * operation to be sure to treat removed keys correctly.
* must be done at the same time to be sure to treat removed keys correctly. * Return [null] if no key could be found in the given direction or
* Return [null] if no key could be found in the given direction or if the * if the selected key didn't change.
* selected key didn't change.
*/ */
private KeyValue getNearestKeyAtDirection(Pointer ptr, Integer direction) private KeyValue getNearestKeyAtDirection(Pointer ptr, int direction)
{ {
if (direction == null)
return _handler.modifyKey(ptr.key.keys[0], ptr.modifiers);
KeyValue k; KeyValue k;
// [i] is [0, -1, 1, -2, 2, ...] // [i] is [0, -1, 1, -2, 2, ...]
for (int i = 0; i > -4; i = (~i>>31) - i) for (int i = 0; i > -4; i = (~i>>31) - i)
@ -261,37 +264,59 @@ public final class Pointers implements Handler.Callback
float dy = y - ptr.downY; float dy = y - ptr.downY;
float dist = Math.abs(dx) + Math.abs(dy); float dist = Math.abs(dx) + Math.abs(dy);
Integer direction;
if (dist < _config.swipe_dist_px) if (dist < _config.swipe_dist_px)
{ {
direction = null; // Pointer is still on the center.
if (ptr.gesture == null || !ptr.gesture.is_in_progress())
return;
// Gesture ended
ptr.gesture.moved_to_center();
ptr.value = apply_gesture(ptr, ptr.gesture.get_gesture());
ptr.flags = 0;
} }
else else
{ { // Pointer is on a quadrant.
// See [getKeyAtDirection()] for the meaning. The starting point on the // See [getKeyAtDirection()] for the meaning. The starting point on the
// circle is the top direction. // circle is the top direction.
double a = Math.atan2(dy, dx) + Math.PI; double a = Math.atan2(dy, dx) + Math.PI;
// a is between 0 and 2pi, 0 is pointing to the left // a is between 0 and 2pi, 0 is pointing to the left
// add 12 to align 0 to the top // add 12 to align 0 to the top
direction = ((int)(a * 8 / Math.PI) + 12) % 16; int direction = ((int)(a * 8 / Math.PI) + 12) % 16;
} if (ptr.gesture == null)
{ // Gesture starts
if (direction != ptr.selected_direction) ptr.gesture = new Gesture(direction);
{ KeyValue new_value = getNearestKeyAtDirection(ptr, direction);
ptr.selected_direction = direction; if (new_value != null)
KeyValue newValue = getNearestKeyAtDirection(ptr, direction); { // Pointer is swiping into a side key.
if (newValue != null && !newValue.equals(ptr.value))
{ ptr.value = new_value;
ptr.value = newValue; ptr.flags = pointer_flags_of_kv(new_value);
ptr.flags = pointer_flags_of_kv(newValue);
// Sliding mode is entered when key5 or key6 is down on a slider key. // Sliding mode is entered when key5 or key6 is down on a slider key.
if (ptr.key.slider && if (ptr.key.slider &&
(newValue.equals(ptr.key.getKeyValue(5)) (new_value.equals(ptr.key.getKeyValue(5))
|| newValue.equals(ptr.key.getKeyValue(6)))) || new_value.equals(ptr.key.getKeyValue(6))))
{ {
startSliding(ptr, x); startSliding(ptr, x);
} }
_handler.onPointerDown(newValue, true); _handler.onPointerDown(new_value, true);
}
}
else if (ptr.gesture.changed_direction(direction))
{ // Gesture changed state
if (!ptr.gesture.is_in_progress())
{ // Gesture ended
stopKeyRepeat(ptr);
_handler.onPointerFlagsChanged(true);
}
else
{
ptr.value = apply_gesture(ptr, ptr.gesture.get_gesture());
restartKeyRepeat(ptr);
ptr.flags = 0; // Special behaviors are ignored during a gesture.
}
} }
} }
} }
@ -395,6 +420,12 @@ public final class Pointers implements Handler.Callback
} }
} }
private void restartKeyRepeat(Pointer ptr)
{
stopKeyRepeat(ptr);
startKeyRepeat(ptr);
}
/** A pointer is repeating. Returns [true] if repeat should continue. */ /** A pointer is repeating. Returns [true] if repeat should continue. */
private boolean handleKeyRepeat(Pointer ptr) private boolean handleKeyRepeat(Pointer ptr)
{ {
@ -447,14 +478,51 @@ public final class Pointers implements Handler.Callback
return flags; return flags;
} }
// Gestures
/** Apply a gesture to the current key. */
KeyValue apply_gesture(Pointer ptr, Gesture.Name gesture)
{
switch (gesture)
{
case None:
return ptr.value;
case Swipe:
return ptr.value;
case Roundtrip:
return
modify_key_with_extra_modifier(
ptr,
getNearestKeyAtDirection(ptr, ptr.gesture.current_direction()),
KeyValue.Modifier.GESTURE);
case Circle:
return
modify_key_with_extra_modifier(ptr, ptr.key.keys[0],
KeyValue.Modifier.GESTURE);
case Anticircle:
return _handler.modifyKey(ptr.key.keys[0], ptr.modifiers);
}
return ptr.value; // Unreachable
}
KeyValue modify_key_with_extra_modifier(Pointer ptr, KeyValue kv,
KeyValue.Modifier extra_mod)
{
return
_handler.modifyKey(kv,
ptr.modifiers.with_extra_mod(KeyValue.makeInternalModifier(extra_mod)));
}
// Pointers
private static final class Pointer private static final class Pointer
{ {
/** -1 when latched. */ /** -1 when latched. */
public int pointerId; public int pointerId;
/** The Key pressed by this Pointer */ /** The Key pressed by this Pointer */
public final KeyboardData.Key key; public final KeyboardData.Key key;
/** Current direction. [null] means not swiping. */ /** Gesture state, see [Gesture]. [null] means the pointer has not moved out of the center region. */
public Integer selected_direction; public Gesture gesture;
/** Selected value with [modifiers] applied. */ /** Selected value with [modifiers] applied. */
public KeyValue value; public KeyValue value;
public float downX; public float downX;
@ -472,7 +540,7 @@ public final class Pointers implements Handler.Callback
{ {
pointerId = p; pointerId = p;
key = k; key = k;
selected_direction = null; gesture = null;
value = v; value = v;
downX = x; downX = x;
downY = y; downY = y;
@ -602,6 +670,14 @@ public final class Pointers implements Handler.Callback
return false; return false;
} }
/** Return a copy of this object with an extra modifier added. */
public Modifiers with_extra_mod(KeyValue m)
{
KeyValue[] newmods = Arrays.copyOf(_mods, _size + 1);
newmods[_size] = m;
return ofArray(newmods, newmods.length);
}
/** Returns the activated modifiers that are not in [m2]. */ /** Returns the activated modifiers that are not in [m2]. */
public Iterator<KeyValue> diff(Modifiers m2) public Iterator<KeyValue> diff(Modifiers m2)
{ {