diff --git a/docs/android/PLAYABILITY_TODO.md b/docs/android/PLAYABILITY_TODO.md index 70a9d28..46c19ed 100644 --- a/docs/android/PLAYABILITY_TODO.md +++ b/docs/android/PLAYABILITY_TODO.md @@ -107,9 +107,18 @@ rewrites required. ## P2 — Polish -- [ ] **Drag responsiveness on touch.** Bevy default touch-to-mouse - mapping can lag; confirm drag start threshold isn't too high for a - finger. +- [x] **Drag responsiveness on touch.** *Closed 2026-05-11.* + Two code-side improvements shipped; final feel confirmation still needs + hardware: + 1. `start_drag` (mouse path) now bails out when a touch is just-pressed + (`Touches::iter_just_pressed()`), ensuring `touch_start_drag` always + owns the drag state on touch-screen devices — including Bevy/Winit + versions that simulate `MouseButton::Left` from the primary touch. + 2. Mobile drag commit threshold lowered 10 px → 8 px, matching Android's + `ViewConfiguration.getScaledTouchSlop()` spec. Smaller threshold → + 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. - [ ] **HUD typography.** Reduce text sizes for `Score:`, `Moves:`, diff --git a/solitaire_engine/src/card_animation/tuning.rs b/solitaire_engine/src/card_animation/tuning.rs index 254ba0f..45883cd 100644 --- a/solitaire_engine/src/card_animation/tuning.rs +++ b/solitaire_engine/src/card_animation/tuning.rs @@ -114,7 +114,7 @@ impl AnimationTuning { platform: InputPlatform::Touch, duration_scale: 0.75, overshoot_scale: 0.5, - drag_threshold_px: 10.0, + drag_threshold_px: 8.0, // Android ViewConfiguration.getScaledTouchSlop() drag_scale: 1.12, hover_scale: 1.0, // no hover affordance on touch hover_lerp_speed: 20.0, diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index eb25ff2..6a27f11 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -519,8 +519,10 @@ fn handle_touch_stock_tap( /// Begins a mouse drag: records the press position and the cards that would be /// dragged. Cards are **not** elevated yet — that happens in [`follow_drag`] /// once the drag threshold is crossed. +#[allow(clippy::too_many_arguments)] fn start_drag( buttons: Res>, + touches: Option>, paused: Option>, windows: Query<&Window, With>, cameras: Query<(&Camera, &GlobalTransform)>, @@ -535,6 +537,15 @@ fn start_drag( if !buttons.just_pressed(MouseButton::Left) || !drag.is_idle() { return; } + // On platforms where Winit simulates a MouseButton::Left press from the + // first touch, this guard ensures touch_start_drag (which runs after this + // system) claims the drag state instead of the mouse path. Without it the + // card is tracked via cursor_world (updated from the simulated mouse + // position) rather than the Touches resource, which can be one frame + // behind the actual finger position on Android. + if touches.as_ref().is_some_and(|t| t.iter_just_pressed().next().is_some()) { + return; + } let Some(layout) = layout else { return }; let Some(world) = cursor_world(&windows, &cameras) else { return };