From 948864e65380fcbb3faa2439e6b283c03a509c9d Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 11 May 2026 13:23:24 -0700 Subject: [PATCH] feat(android): long-press opens radial menu as right-click alternative MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Touch screens have no right mouse button, so right-click radial was inaccessible on Android. New system radial_open_on_long_press counts up while a touch is held on a face-up card without crossing the drag threshold; after 0.5 s it transitions RightClickRadialState to Active, which the existing visual overlay and destination-ring infrastructure then renders unchanged. Three supporting changes to wire up the touch-driven confirm path: - radial_track_cursor: falls back to the first active Touches position when cursor_world returns None, so the hover ring tracks a sliding held finger on Android. - radial_handle_release_or_cancel: confirms on Touches::iter_just_released (finger lift) in addition to right-mouse release. Cancels on Touches::iter_just_canceled. No new event reader — uses the Touches resource which is already in scope after the track_cursor addition. - handle_double_tap: skips when the radial is active. Guards the narrow edge case where the finger lifts on the exact same frame as the 0.5 s long-press threshold fires; prevents a spurious double-tap move from racing with the radial confirm. Co-Authored-By: Claude Sonnet 4.6 --- docs/android/PLAYABILITY_TODO.md | 18 ++++- solitaire_engine/src/input_plugin.rs | 7 ++ solitaire_engine/src/radial_menu.rs | 100 ++++++++++++++++++++++++--- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/docs/android/PLAYABILITY_TODO.md b/docs/android/PLAYABILITY_TODO.md index 46c19ed..04c6a1a 100644 --- a/docs/android/PLAYABILITY_TODO.md +++ b/docs/android/PLAYABILITY_TODO.md @@ -119,8 +119,22 @@ rewrites required. smaller snap-on-commit and faster perceived response. **Remaining:** connect AVD or device and verify drag feels responsive with no stutter; tune threshold further if needed. -- [ ] **Long-press menu.** Alternative to right-click (which doesn't - exist on touch). Wire to the existing right-click-highlight system. +- [x] **Long-press menu.** *Closed 2026-05-11.* New system + `radial_open_on_long_press` in `radial_menu.rs` counts up while a + touch is held (`drag.active_touch_id.is_some() && !drag.committed`) + and opens `RightClickRadialState::Active` after 0.5 s — the same + state the right-click path uses. Existing radial infrastructure + then handles everything: + - `radial_track_cursor` extended to fall back to the first active + touch when no cursor position is available, so sliding the held + finger moves the hover ring. + - `radial_handle_release_or_cancel` extended to confirm/cancel on + `Touches::iter_just_released()` in addition to right-mouse release. + - `handle_double_tap` skips when the radial is active (guards a + narrow edge case where the finger lifts at exactly the same frame + the 0.5 s threshold fires). + Hardware verification needed: confirm the 0.5 s hold feel, verify + sliding to a destination and lifting confirms the move. - [ ] **HUD typography.** Reduce text sizes for `Score:`, `Moves:`, timer so they fit cleanly in one row. - [ ] **Orientation lock.** Set `android:screenOrientation="portrait"` diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index 6a27f11..e630d06 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -34,6 +34,7 @@ use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau}; use crate::card_animation::tuning::AnimationTuning; use crate::card_animation::{CardAnimation, MotionCurve}; use crate::card_plugin::{CardEntity, HintHighlight, HintHighlightTimer, STACK_FAN_FRAC}; +use crate::radial_menu::RightClickRadialState; use crate::ui_theme::{MOTION_DRAG_REJECT_SECS, STATE_SUCCESS, STATE_WARNING}; use solitaire_core::game_state::DrawMode; use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL; @@ -1413,6 +1414,7 @@ fn handle_double_click( fn handle_double_tap( mut touch_events: MessageReader, paused: Option>, + radial: Option>, time: Res