Point libswipetype submodule to our Gitea fork with absolute-coordinate DTW
Check generated files / Layout list (push) Has been cancelled
Check generated files / Layout warnings (push) Has been cancelled
Check generated files / Compose key data (push) Has been cancelled
Make Apk CI / Build-Apk (push) Has been cancelled

submodule vendor/libswipetype now points to
http://10.0.1.49:3010/harley/libswipetype.git (commit e4aef2f)
instead of upstream github.com/libswipetype/libswipetype.

Key change: removed [0,1] bounding-box normalization from both
PathProcessor and IdealPathGenerator. Paths now stay in original dp
coordinates so DTW measures actual keyboard distance between keys.
This commit is contained in:
2026-07-07 03:41:21 -04:00
parent 292788aafa
commit 55a7bd2643
8 changed files with 144 additions and 77 deletions
+1 -1
View File
@@ -3,4 +3,4 @@
url = https://github.com/Julow/cdict
[submodule "vendor/libswipetype"]
path = vendor/libswipetype
url = https://github.com/libswipetype/libswipetype
url = http://10.0.1.49:3010/harley/libswipetype.git
Binary file not shown.
+1 -1
View File
@@ -151,7 +151,7 @@ public final class Config
// The option value uses an unnamed scale where the baseline is around 25.
float dpi_ratio = Math.max(dm.xdpi, dm.ydpi) / Math.min(dm.xdpi, dm.ydpi);
float swipe_scaling = Math.min(dm.widthPixels, dm.heightPixels) / 10.f * dpi_ratio;
float swipe_dist_value = Float.valueOf(_prefs.getString("swipe_dist", "15"));
float swipe_dist_value = Float.valueOf(_prefs.getString("swipe_dist", "25"));
swipe_dist_px = swipe_dist_value / 25.f * swipe_scaling;
float slider_sensitivity = Float.valueOf(_prefs.getString("slider_sensitivity", "30")) / 100.f;
slide_step_px = slider_sensitivity * swipe_scaling;
+1 -1
View File
@@ -134,7 +134,7 @@ public class Keyboard2 extends InputMethodService
Config.initGlobalConfig(prefs, getResources(),
_foldStateTracker.isUnfolded(), _dictionaries);
_config = Config.globalConfig();
_swipeAdapter = new UnexpectedKeyboardSwipeAdapter(this, null, _config);
_swipeAdapter = new UnexpectedKeyboardSwipeAdapter(this);
_swipeAdapter.init(new UnexpectedKeyboardSwipeAdapter.CandidatesCallback() {
public void onSwipeCandidates(String[] words, float[] scores) {
if (words.length > 0) {
+88 -15
View File
@@ -17,9 +17,12 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import dev.dettmer.swipetype.android.KeyboardLayoutDescriptor;
public class Keyboard2View extends View
implements View.OnTouchListener, Pointers.IPointerEventHandler
{
@@ -39,6 +42,9 @@ public class Keyboard2View extends View
private int[] _glideKeys = new int[32];
private int _glideKeyCount = 0;
private boolean _inGlide = false;
/** Trail points for the swipe gesture (pixels). */
private List<float[]> _trailPoints = new ArrayList<>();
private Paint _trailPaint;
private Pointers.Modifiers _mods;
@@ -74,6 +80,13 @@ public class Keyboard2View extends View
_theme = new Theme(getContext(), attrs);
_config = Config.globalConfig();
_pointers = new Pointers(this, _config);
_trailPaint = new Paint();
_trailPaint.setColor(0x8044AAFF);
_trailPaint.setStyle(Paint.Style.STROKE);
_trailPaint.setStrokeWidth(6f);
_trailPaint.setStrokeCap(Paint.Cap.ROUND);
_trailPaint.setStrokeJoin(Paint.Join.ROUND);
_trailPaint.setAntiAlias(true);
refresh_navigation_bar(context);
setOnTouchListener(this);
int layout_id = (attrs == null) ? 0 :
@@ -218,12 +231,17 @@ public class Keyboard2View extends View
_pointers.onTouchDown(tx, ty, event.getPointerId(p), key);
// Reset glide tracking
_glideKeyCount = 0;
_glideKeys[0] = -1;
_inGlide = false;
_glideLastKey = -1;
_trailPoints.clear();
if (_swipeAdapter != null) _swipeAdapter.cancelGesture();
if (key != null && key.keys[0] != null
&& key.keys[0].getKind() == KeyValue.Kind.Char)
{
_glideLastKey = key.keys[0].getChar();
_trailPoints.add(new float[]{tx, ty});
}
break;
case MotionEvent.ACTION_MOVE:
for (p = 0; p < event.getPointerCount(); p++)
@@ -231,33 +249,44 @@ public class Keyboard2View extends View
float mx = event.getX(p);
float my = event.getY(p);
_pointers.onTouchMove(mx, my, event.getPointerId(p));
// Track key under finger for glide detection
if (_swipeAdapter != null && !_inGlide)
// Collect trail + detect glide in one pass
if (_swipeAdapter != null)
{
KeyboardData.Key k = getKeyAtPosition(mx, my);
if (k != null && k.keys[0] != null
&& k.keys[0].getKind() == KeyValue.Kind.Char)
{
int cp = k.keys[0].getChar();
if (cp != _glideLastKey)
_trailPoints.add(new float[]{mx, my});
invalidate();
if (!_inGlide)
{
_glideLastKey = cp;
boolean seen = false;
for (int i = 0; i < _glideKeyCount; i++)
if (_glideKeys[i] == cp) { seen = true; break; }
if (!seen && _glideKeyCount < 32)
_glideKeys[_glideKeyCount++] = cp;
if (_glideKeyCount >= 3)
int cp = k.keys[0].getChar();
if (cp != _glideLastKey)
{
_inGlide = true;
_swipeAdapter.startGesture();
_glideLastKey = cp;
boolean seen = false;
for (int i = 0; i < _glideKeyCount; i++)
if (_glideKeys[i] == cp) { seen = true; break; }
if (!seen && _glideKeyCount < 32)
_glideKeys[_glideKeyCount++] = cp;
if (_glideKeyCount >= 2)
{
_inGlide = true;
_swipeAdapter.startGesture();
_pointers.cancelPointer(event.getPointerId(p));
}
}
}
}
}
// Feed points during glide
// Feed points during glide (convert to dp for the engine)
if (_inGlide && _swipeAdapter != null)
_swipeAdapter.addPoint(mx, my, System.currentTimeMillis());
{
float density = getResources().getDisplayMetrics().density;
_swipeAdapter.addPoint(mx / density, my / density, System.currentTimeMillis());
_trailPoints.add(new float[]{mx, my});
invalidate(); // trigger redraw to show trail
}
}
break;
case MotionEvent.ACTION_UP:
@@ -265,10 +294,14 @@ public class Keyboard2View extends View
if (_inGlide && _swipeAdapter != null)
_swipeAdapter.finishGesture();
_inGlide = false;
_trailPoints.clear();
invalidate();
_pointers.onTouchUp(event.getPointerId(event.getActionIndex()));
break;
case MotionEvent.ACTION_CANCEL:
_inGlide = false;
_trailPoints.clear();
invalidate();
_pointers.onTouchCancel();
break;
default:
@@ -339,6 +372,37 @@ public class Keyboard2View extends View
(int)(_tc.row_height * _keyboard.keysHeight
+ _config.marginTop + _marginBottom);
setMeasuredDimension(width, height);
// Pass real key positions to the swipe adapter
if (_swipeAdapter != null && _keyboard != null)
{
float density = getResources().getDisplayMetrics().density;
List<KeyboardLayoutDescriptor.KeyInfo> keys = new ArrayList<>();
float yy = _tc.margin_top / density;
for (KeyboardData.Row row : _keyboard.rows)
{
yy += row.shift * _tc.row_height / density;
float xx = _marginLeft / density;
float keyH = (row.height * _tc.row_height - _tc.vertical_margin) / density;
for (KeyboardData.Key k : row.keys)
{
xx += k.shift * _keyWidth / density;
float keyW = (_keyWidth * k.width - _tc.horizontal_margin) / density;
if (k.keys != null && k.keys.length > 0 && k.keys[0] != null
&& k.keys[0].getKind() == KeyValue.Kind.Char)
{
char c = k.keys[0].getChar();
keys.add(new KeyboardLayoutDescriptor.KeyInfo(
String.valueOf(c), (int)c,
xx + keyW/2, yy + keyH/2, keyW, keyH));
}
xx += (k.width * _keyWidth) / density;
}
yy += row.height * _tc.row_height / density;
}
_swipeAdapter.setKeyLayout(keys,
(_keyboard.keysWidth * _keyWidth + _marginLeft + _tc.margin_left) / density,
yy);
}
}
Rect _cached_exclusion_rect = new Rect();
@@ -430,6 +494,15 @@ public class Keyboard2View extends View
}
y += row.height * _tc.row_height;
}
// Draw swipe gesture trail
if (_trailPoints != null && _trailPoints.size() >= 2)
{
android.graphics.Path trail = new android.graphics.Path();
trail.moveTo(_trailPoints.get(0)[0], _trailPoints.get(0)[1]);
for (int i = 1; i < _trailPoints.size(); i++)
trail.lineTo(_trailPoints.get(i)[0], _trailPoints.get(i)[1]);
canvas.drawPath(trail, _trailPaint);
}
}
@Override
+16
View File
@@ -195,6 +195,22 @@ public final class Pointers implements Handler.Callback
_handler.onPointerFlagsChanged(true);
}
/** Cancel a single pointer by its ID — stops long-press and removes it. */
public void cancelPointer(int pointerId)
{
for (int i = _ptrs.size() - 1; i >= 0; i--)
{
Pointer ptr = _ptrs.get(i);
if (ptr.pointerId == pointerId && !ptr.hasFlagsAny(FLAG_P_LATCHED))
{
_longpress_handler.removeMessages(ptr.timeoutWhat);
_ptrs.remove(i);
break;
}
}
_handler.onPointerFlagsChanged(false);
}
/* Whether an other pointer is down on a non-special key. */
private boolean isOtherPointerDown()
{
@@ -14,41 +14,42 @@ import dev.dettmer.swipetype.android.SwipeTypeCandidate;
import dev.dettmer.swipetype.android.SwipeTypeEngine;
import dev.dettmer.swipetype.android.SwipeTypeError;
/**
* Adapter connecting Unexpected Keyboard to the libswipetype gesture engine.
*
* Collects touch points during a glide gesture, sends them to SwipeTypeEngine
* on lift, and delivers candidates back to the keyboard for display.
*/
public class UnexpectedKeyboardSwipeAdapter implements SwipeTypeAdapter
{
private static final String TAG = "SwipeAdapter";
private SwipeTypeEngine _engine;
private Context _context;
private KeyboardData _keyboardData;
private Config _config;
/** Collected touch points for the current gesture. */
private List<GesturePoint> _gesturePoints = new ArrayList<>();
private boolean _gestureActive = false;
/** Callback to deliver candidates to. */
private CandidatesCallback _candidatesCallback;
/** Key positions (dp) set by the view after layout. */
private List<KeyInfo> _cachedKeys = null;
private float _layoutWidthDp = 0;
private float _layoutHeightDp = 0;
public interface CandidatesCallback
{
void onSwipeCandidates(String[] words, float[] scores);
}
public UnexpectedKeyboardSwipeAdapter(Context context, KeyboardData kbd, Config config)
public UnexpectedKeyboardSwipeAdapter(Context context)
{
_context = context;
_keyboardData = kbd;
_config = config;
}
/** Initialize the engine and load the dictionary. Call once on startup. */
/** Called by the view after layout with all key positions in dp. */
public void setKeyLayout(List<KeyInfo> keys, float widthDp, float heightDp)
{
_cachedKeys = keys;
_layoutWidthDp = widthDp;
_layoutHeightDp = heightDp;
if (_engine != null && _engine.isInitialized())
_engine.notifyLayoutChanged();
}
public void init(CandidatesCallback callback)
{
_candidatesCallback = callback;
@@ -61,78 +62,58 @@ public class UnexpectedKeyboardSwipeAdapter implements SwipeTypeAdapter
{
int resId = _context.getResources().getIdentifier(
"en_us_sample", "raw", _context.getPackageName());
if (resId == 0)
{
Log.w(TAG, "Dictionary resource not found");
return;
}
if (resId == 0) { Log.w(TAG, "Dictionary resource not found"); return; }
try (InputStream is = _context.getResources().openRawResource(resId))
{
boolean ok = _engine.loadDictionary("en-US", is);
Log.i(TAG, "loadDictionary: " + ok);
}
catch (Exception e)
{
Log.e(TAG, "Failed to load dictionary: " + e.getMessage());
}
catch (Exception e) { Log.e(TAG, "Failed: " + e.getMessage()); }
}
/** Start a new gesture. Call on initial swipe motion. */
public void startGesture() { _gesturePoints.clear(); _gestureActive = true; }
/** Add a touch point. */
public void addPoint(float x, float y, long timestamp)
{
if (_gestureActive)
_gesturePoints.add(new GesturePoint(x, y, timestamp));
if (_gestureActive) _gesturePoints.add(new GesturePoint(x, y, timestamp));
}
/** Finish the gesture and run recognition. */
public void finishGesture()
{
_gestureActive = false;
if (_gesturePoints.size() < 3) return;
if (_gesturePoints.size() < 3) { _gesturePoints.clear(); return; }
_engine.processGesture(_gesturePoints);
_gesturePoints.clear();
}
/** Cancel the current gesture. */
public void cancelGesture() { _gestureActive = false; _gesturePoints.clear(); }
public boolean isGestureActive() { return _gestureActive; }
// ===== SwipeTypeAdapter implementation =====
// ===== SwipeTypeAdapter =====
@Override
public void onInit(SwipeTypeEngine engine)
{
Log.i(TAG, "Engine initialized");
}
public void onInit(SwipeTypeEngine engine) { Log.i(TAG, "Engine initialized"); }
@Override
public KeyboardLayoutDescriptor getKeyboardLayout()
{
// Stub: returns a minimal layout for testing
List<KeyInfo> keys = new ArrayList<>();
float density = _context.getResources().getDisplayMetrics().density;
// QWERTY row positions (approximate centers in dp at 24dp key width)
if (_cachedKeys != null && !_cachedKeys.isEmpty())
return new KeyboardLayoutDescriptor("en-US", _cachedKeys,
_layoutWidthDp, _layoutHeightDp);
// Fallback — should not be reached once the view reports layout
List<KeyInfo> stub = new ArrayList<>();
float d = _context.getResources().getDisplayMetrics().density;
String[] rows = {"qwertyuiop", "asdfghjkl", "zxcvbnm"};
float y = 0;
for (String row : rows)
{
for (String r : rows) {
float x = 0;
for (int i = 0; i < row.length(); i++)
{
char c = row.charAt(i);
keys.add(new KeyInfo(String.valueOf(c), (int)c,
x + 12f / density, y + 12f / density,
24f / density, 24f / density));
x += 24f / density;
for (int i = 0; i < r.length(); i++) {
char c = r.charAt(i);
stub.add(new KeyInfo(String.valueOf(c), (int)c, x+12f/d, y+12f/d, 24f/d, 24f/d));
x += 24f/d;
}
y += 24f / density;
y += 24f/d;
}
return new KeyboardLayoutDescriptor("en-US", keys,
rows[0].length() * 24f / density, rows.length * 24f / density);
return new KeyboardLayoutDescriptor("en-US", stub, 240f/d, 72f/d);
}
@Override
@@ -150,10 +131,7 @@ public class UnexpectedKeyboardSwipeAdapter implements SwipeTypeAdapter
}
@Override
public void onError(SwipeTypeError error)
{
Log.e(TAG, "Error: " + error.name());
}
public void onError(SwipeTypeError error) { Log.e(TAG, "Error: " + error.name()); }
public void shutdown()
{