feat(android): tap-to-toggle HUD visibility (A1)

On Android, a short tap on the empty game area (not on a card) toggles
the HUD band, info column, and action bar between Visible and Hidden.
Layout recomputes with band_h=0 when hidden so cards fill the full
screen. Any modal open restores the HUD to Visible automatically.

- hud_plugin: HudVisibility resource, HudBand/HudColumn/HudActionBar
  markers, apply_hud_visibility (fires synthetic WindowResized),
  restore_hud_on_modal, and Android-only toggle_hud_on_tap +
  HudTapTracker (15 px slop, skips card taps via DragState.is_idle())
- layout: compute_layout gains hud_visible: bool; passes band_h=0.0
  when hidden; all callers updated
- input_plugin: TouchDragSet (AfterStartDrag / BeforeEndDrag) public
  system-set anchors for cross-plugin ordering
- table_plugin: setup_table + on_window_resized read HudVisibility and
  pass hud_visible to compute_layout
- Desktop behaviour is unchanged (HudVisibility always Visible, tap
  system is #[cfg(target_os = "android")] gated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-12 22:46:36 -07:00
parent 918d83420b
commit 24ab25b0b7
8 changed files with 218 additions and 68 deletions
+7 -2
View File
@@ -10,6 +10,7 @@ use solitaire_core::card::Suit;
use solitaire_core::pile::PileType;
use crate::events::{HintVisualEvent, StateChangedEvent};
use crate::hud_plugin::HudVisibility;
use crate::layout::{compute_layout, Layout, LayoutResource, LayoutSystem};
use crate::safe_area::SafeAreaInsets;
use crate::resources::GameStateResource;
@@ -149,6 +150,7 @@ fn setup_table(
settings: Option<Res<SettingsResource>>,
bg_images: Option<Res<BackgroundImageSet>>,
safe_area: Option<Res<SafeAreaInsets>>,
hud_vis: Option<Res<HudVisibility>>,
) {
// Only spawn a camera if one does not already exist (e.g. a parent app
// may have added one in tests). Use the felt-green clear colour so the
@@ -179,7 +181,8 @@ fn setup_table(
let insets = safe_area.as_deref().copied().unwrap_or_default();
let safe_area_top = insets.top / scale;
let safe_area_bottom = insets.bottom / scale;
let layout = compute_layout(window_size, safe_area_top, safe_area_bottom);
let hud_visible = hud_vis.as_deref().copied().unwrap_or_default() == HudVisibility::Visible;
let layout = compute_layout(window_size, safe_area_top, safe_area_bottom, hud_visible);
let selected_bg = settings.as_ref().map_or(0, |s| s.0.selected_background);
@@ -314,6 +317,7 @@ fn on_window_resized(
mut events: MessageReader<WindowResized>,
safe_area: Option<Res<SafeAreaInsets>>,
windows: Query<&Window>,
hud_vis: Option<Res<HudVisibility>>,
mut layout_res: Option<ResMut<LayoutResource>>,
mut backgrounds: Query<
(&mut Sprite, &mut Transform),
@@ -329,7 +333,8 @@ fn on_window_resized(
let insets = safe_area.as_deref().copied().unwrap_or_default();
let safe_area_top = insets.top / scale;
let safe_area_bottom = insets.bottom / scale;
let new_layout = compute_layout(window_size, safe_area_top, safe_area_bottom);
let hud_visible = hud_vis.as_deref().copied().unwrap_or_default() == HudVisibility::Visible;
let new_layout = compute_layout(window_size, safe_area_top, safe_area_bottom, hud_visible);
if let Some(layout_res) = layout_res.as_deref_mut() {
layout_res.0 = new_layout.clone();