Skip to content
CleverKeys Wiki
planning v1.4.0+

Full Trackpad Mode — Design Specification

Status: Planning. This document is a design proposal in response to GitHub issue #143. It is NOT implemented yet. Linked from docs/ROADMAP.md for tracking.

Motivation

The existing TrackPoint Mode (FLAG_P_TRACKPOINT_MODE) converts the area around a single navigation key into a joystick when the user holds that key. Issue #143 reports that this is “inconvenient due to space restraint” — the user wants to use their thumb across the FULL keyboard surface for finer cursor control, not just the area of one key.

This spec describes a FULL_TRACKPAD_MODE that:

  • Activates on command (mapped to a short-swipe via Per-Key Actions, OR a dedicated key event)
  • Suppresses all normal key input
  • Treats the entire keyboard panel as a single touch surface for cursor movement
  • Exits via a clearly-distinguishable gesture so the user never gets stuck

The legacy per-key TrackPoint stays in place; this is an additive mode for users who want the wider gesture area.

User Flow

[normal typing]
     |
     | user invokes full_trackpad_mode command
     | (mapped to any short-swipe direction or key tap)

┌────────────────────────────────────────────────────┐
│  FULL TRACKPAD MODE                                │
│  - All key labels dimmed to 40% alpha              │
│  - Brief 2-second toast: "Quick-tap anywhere to exit" │
│  - Any finger drag → cursor moves with speed scaling │
│  - Quick tap (< 200ms, < 10px) → exit mode         │
│  - Two-finger drag → scroll (Phase 2)              │
└────────────────────────────────────────────────────┘
     |
     | user quick-taps

[normal typing resumed]

Activation

A new command full_trackpad_mode registered in CommandRegistry.kt under Category.CURSOR. Users assign it to:

  • A short-swipe direction on any key (via Per-Key Actions → Action Type: Command → “Toggle Full Trackpad Mode”)
  • A dedicated layout key via command:'full_trackpad_mode' in the layout XML
  • A future quick-setting tile (out of scope for v1)

Programmatic activation also exposed for accessibility tools.

Touch Handling

Keyboard2View.onTouch(v, event) (currently dispatching to _pointers.onTouchDown/Move/Up/Cancel) gains a check at the top:

override fun onTouch(v: View?, event: MotionEvent?): Boolean {
    if (_fullTrackpadActive) {
        return handleFullTrackpadTouch(event ?: return false)
    }
    // ... existing dispatch ...
}

handleFullTrackpadTouch is a new method whose state machine:

EventBehavior
ACTION_DOWNRecord originX = event.x, originY = event.y, downTimeMs = event.eventTime. Reset maxDisplacement = 0.
ACTION_MOVEdx = event.x - originX, dy = event.y - originY. If |dx| or |dy| exceeds TRACKPOINT_DEAD_ZONE, emit cursor-movement keys via _handler.onPointerDown/Up using the same axis-independent + delay-scaling formula as Pointers.handleTrackPointRepeat(). Track maxDisplacement = max(maxDisplacement, hypot(dx, dy)).
ACTION_UPIf (eventTime - downTimeMs < 200ms) && (maxDisplacement < 10px), treat as exit-tap: _fullTrackpadActive = false, refresh visual state. Otherwise no action.
ACTION_CANCELNo state change; mode stays active.

The cursor-movement math is borrowed directly from Pointers.kt:1083-1157:

val maxDistance = (height * 0.5f).coerceAtLeast(1f)
val normalized = (displacement / maxDistance).coerceIn(0f, 1f)
val repeatDelay = TRACKPOINT_MAX_DELAY -
    (normalized * (TRACKPOINT_MAX_DELAY - TRACKPOINT_MIN_DELAY))

So speed scales 0 → max from dead zone to keyboard edge.

Visual Treatment

The keyboard panel dims its key labels to 40% alpha when _fullTrackpadActive is true. Implementation:

  • Keyboard2View.onDraw() checks the flag and applies paint.alpha = 0.4 * 255 to text glyphs (NOT background) when the mode is active.
  • A brief toast or in-IME overlay (per ime-visual-feedback.md — toasts are invisible behind the IME panel, so use an in-view fadeable TextView) shows: “TRACKPAD MODE — quick-tap anywhere to exit”.
  • The overlay fades after 2 seconds. The dimming persists until exit.

Optional phase-2 additions:

  • Crosshair indicator at the touch point
  • Subtle border accent in the active color

Exit

Primary: quick-tap anywhere. Defined as ACTION_UP - ACTION_DOWN < 200ms AND maxDisplacement < 10px. This is the user’s gesture for “I’m done.”

Backup: a small ”✕” icon shown in the top-right of the keyboard while mode is active (touchable). Phase-2 polish.

Hard exit: any ACTION_CANCEL from system (e.g., onFinishInputView() fires) automatically exits the mode so the user doesn’t get stuck.

Configuration

New Config.kt keys (all in the “Gestures” section):

KeyDefaultRangePurpose
full_trackpad_mode_enabledtrueboolMaster toggle; if false, the full_trackpad_mode command is a no-op
full_trackpad_speed_multiplier1.0f0.5-3.0Scales the cursor-movement rate; user-tunable
full_trackpad_dim_keystrueboolWhether to dim key labels in mode (some users prefer to see keys)
full_trackpad_show_hinttrueboolWhether to show the 2s exit hint overlay (suppressible after user knows the gesture)

All readable via SafePreferences per the project pattern. Settings UI section under Gestures or Cursor Navigation.

Out of Scope (Phase 2)

  • Two-finger drag for scrolling (PgUp/PgDn or smooth scroll keys)
  • Two-finger tap for selection-toggle (Shift+arrow mode)
  • Pinch-to-zoom (rare in text fields, low value)
  • Configurable activation gestures beyond command assignment

File Touch List (when implemented)

FileChange
src/main/kotlin/tribixbite/cleverkeys/customization/CommandRegistry.ktAdd Command("full_trackpad_mode", "Toggle Full Trackpad Mode", ...)
src/main/kotlin/tribixbite/cleverkeys/customization/CustomShortSwipeExecutor.ktAdd "full_trackpad_mode" branch in executeCommandByName()
src/main/kotlin/tribixbite/cleverkeys/Keyboard2View.ktAdd _fullTrackpadActive flag, handleFullTrackpadTouch() method, onTouch() interception, onDraw() dimming
src/main/kotlin/tribixbite/cleverkeys/Config.ktAdd 4 new pref keys to Defaults + Config fields + refresh() reads
src/main/kotlin/tribixbite/cleverkeys/SettingsActivity.ktNew section under Gestures with the 4 toggles
src/main/kotlin/tribixbite/cleverkeys/backup/SettingsValidation.ktClassify the 4 new keys
src/main/kotlin/tribixbite/cleverkeys/backup/SettingsDefaults.ktAdd defaults to SETTINGS_DEFAULTS map
src/test/kotlin/tribixbite/cleverkeys/FullTrackpadModeTest.ktNew pure-JVM tests for the touch math (dead zone, speed scaling, quick-tap exit)
src/androidTest/kotlin/tribixbite/cleverkeys/FullTrackpadIntegrationTest.ktOptional ew-cli test that simulates touch sequences and asserts cursor key events fire
docs/wiki/gestures/full-trackpad-mode.mdUser guide (write at implementation time)
docs/wiki/specs/gestures/full-trackpad-mode-spec.mdThis file — update status: implemented and refresh implementation details at implementation time

Estimated effort: ~250 LOC main + ~80 LOC tests + ~150 LOC docs. Single-session implementation if UX decisions are pre-made.

UX Decisions To Confirm Before Implementation

These need owner sign-off (no autonomous choice):

  1. Exit gesture choice: quick-tap (proposed) vs. two-finger tap vs. long-press-and-hold a corner vs. dedicated visible ”✕” button. Each has tradeoffs:
    • Quick-tap: discoverable but conflicts with potential “tap-to-cursor-position” semantics.
    • Two-finger tap: rare false-positive but requires two hands.
    • Long-press corner: invisible, easy to miss.
    • Visible ✕: discoverable, takes screen space.
  2. Should tap-down-then-release without movement also place the cursor at that position? This is what a real laptop trackpad does (tap = click). If yes, the “quick-tap exits” gesture needs a different signature (e.g., two consecutive quick-taps).
  3. What’s the activation default? Should there be a default key mapping (e.g., long-press space) or strictly require user opt-in via Per-Key Actions?
  4. Visual indicator strength: full overlay vs. just label dimming vs. nothing. Some users may find the overlay distracting.