From 3650788dc59d59f8dce23742f1dba335df7c1723 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 18 May 2026 17:09:54 -0700 Subject: [PATCH] fix(engine): prevent stock-tap from toggling HUD on Android Every draw-from-stock tap was also firing the HUD auto-hide toggle because the stock pile is not an ActionButton and toggle_hud_on_tap had no way to know the tap was consumed by game logic. Add GameInputConsumedResource(bool): handle_touch_stock_tap sets it on TouchPhase::Started when a draw fires; toggle_hud_on_tap checks and clears it on TouchPhase::Ended, treating it as equivalent to started_on_button so the HUD stays put. Co-Authored-By: Claude Sonnet 4.6 --- solitaire_engine/src/hud_plugin.rs | 13 +++++++++++-- solitaire_engine/src/input_plugin.rs | 5 ++++- solitaire_engine/src/resources.rs | 7 +++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/solitaire_engine/src/hud_plugin.rs b/solitaire_engine/src/hud_plugin.rs index a55fdb7..48dff13 100644 --- a/solitaire_engine/src/hud_plugin.rs +++ b/solitaire_engine/src/hud_plugin.rs @@ -45,7 +45,7 @@ use crate::layout::LayoutSystem; use crate::pause_plugin::PausedResource; use crate::resources::GameStateResource; #[cfg(target_os = "android")] -use crate::resources::DragState; +use crate::resources::{DragState, GameInputConsumedResource}; use crate::selection_plugin::SelectionState; use crate::time_attack_plugin::TimeAttackResource; use crate::ui_focus::{FocusGroup, Focusable}; @@ -2492,6 +2492,7 @@ fn toggle_hud_on_tap( mut tracker: ResMut, mut hud_vis: ResMut, buttons: Query<&Interaction, With>, + mut game_consumed: ResMut, ) { use bevy::input::touch::TouchPhase; if !scrims.is_empty() || paused.is_some_and(|p| p.0) { @@ -2502,6 +2503,7 @@ fn toggle_hud_on_tap( for _ in touch_events.read() {} tracker.start_pos = None; tracker.started_on_button = false; + game_consumed.0 = false; return; } for event in touch_events.read() { @@ -2515,7 +2517,13 @@ fn toggle_hud_on_tap( buttons.iter().any(|i| *i != Interaction::None); } TouchPhase::Ended if drag.is_idle() => { - let on_button = tracker.started_on_button; + // Also treat taps where game logic consumed the touch (e.g. + // drawing from stock) as "on button" so they don't toggle + // the HUD. The flag is set on TouchPhase::Started by the + // input system that consumed the tap and must be cleared here + // regardless of whether we toggle. + let on_button = tracker.started_on_button || game_consumed.0; + game_consumed.0 = false; if let Some(start) = tracker.start_pos.take() { if !on_button && (event.position - start).length() < HUD_TAP_SLOP_PX { *hud_vis = match *hud_vis { @@ -2532,6 +2540,7 @@ fn toggle_hud_on_tap( TouchPhase::Canceled => { tracker.start_pos = None; tracker.started_on_button = false; + game_consumed.0 = false; } _ => {} } diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index 159c3e8..45c0eb7 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -47,7 +47,7 @@ use crate::game_plugin::{ConfirmNewGameScreen, GameMutation, RestorePromptScreen use crate::pause_plugin::PausedResource; use crate::progress_plugin::ProgressResource; use crate::layout::{Layout, LayoutResource}; -use crate::resources::{DragState, GameStateResource, HintCycleIndex}; +use crate::resources::{DragState, GameInputConsumedResource, GameStateResource, HintCycleIndex}; use crate::selection_plugin::SelectionState; use crate::time_attack_plugin::TimeAttackResource; @@ -95,6 +95,7 @@ impl Plugin for InputPlugin { app.init_resource::() .init_resource::() .init_resource::() + .init_resource::() .add_message::() .add_message::() .add_message::() @@ -501,6 +502,7 @@ fn handle_touch_stock_tap( layout: Option>, drag: Res, mut draw: MessageWriter, + mut game_consumed: ResMut, ) { if paused.is_some_and(|p| p.0) { return; @@ -522,6 +524,7 @@ fn handle_touch_stock_tap( }; if point_in_rect(world, stock_pos, layout.0.card_size) { draw.write(DrawRequestEvent); + game_consumed.0 = true; break; // one draw per tap frame } } diff --git a/solitaire_engine/src/resources.rs b/solitaire_engine/src/resources.rs index e11c138..88eaf06 100644 --- a/solitaire_engine/src/resources.rs +++ b/solitaire_engine/src/resources.rs @@ -114,6 +114,13 @@ pub struct HintCycleIndex(pub usize); #[derive(Resource, Debug, Clone, Default)] pub struct SettingsScrollPos(pub f32); +/// Set to `true` by an input system when a touch tap is consumed by game logic +/// (e.g. drawing from stock). `toggle_hud_on_tap` checks this flag on +/// `TouchPhase::Ended` and skips the HUD visibility toggle when set, then +/// resets it to `false` so subsequent taps behave normally. +#[derive(Resource, Debug, Clone, Default)] +pub struct GameInputConsumedResource(pub bool); + /// Shared Tokio runtime used by all async-task closures that need HTTP I/O. /// /// Bevy's `AsyncComputeTaskPool` uses `async-executor` (not Tokio), so spawned