diff --git a/solitaire_data/src/settings.rs b/solitaire_data/src/settings.rs index 1201c1e..3eb70b9 100644 --- a/solitaire_data/src/settings.rs +++ b/solitaire_data/src/settings.rs @@ -154,6 +154,14 @@ pub struct Settings { /// `#[serde(default)]`. #[serde(default)] pub reduce_motion_mode: bool, + /// When `true`, replaces translucent "glass" chrome (the floating + /// touch tab bar) with opaque fills so table content behind it can + /// never reduce label/icon contrast. The accessibility counterpart + /// of iOS "Reduce Transparency". Older `settings.json` files written + /// before this field existed deserialize cleanly to `false` thanks + /// to `#[serde(default)]`. + #[serde(default)] + pub reduce_transparency_mode: bool, /// Window size and screen position to restore on next launch. `None` /// means "use platform defaults" — set on first run, then populated /// as the player resizes / moves the window. Older `settings.json` @@ -451,6 +459,7 @@ impl Default for Settings { color_blind_mode: false, high_contrast_mode: false, reduce_motion_mode: false, + reduce_transparency_mode: false, window_geometry: None, selected_theme_id: default_theme_id(), shown_achievement_onboarding: false, diff --git a/solitaire_engine/src/hud_plugin/mod.rs b/solitaire_engine/src/hud_plugin/mod.rs index a2ef864..7f918fe 100644 --- a/solitaire_engine/src/hud_plugin/mod.rs +++ b/solitaire_engine/src/hud_plugin/mod.rs @@ -603,6 +603,7 @@ impl Plugin for HudPlugin { // disjoint from the UiTextFx readout writers (tab labels // vs HUD readouts), which Bevy can't prove, so declare it. animate_tab_expansion.ambiguous_with(UiTextFx), + duck_tab_bar_during_drag, ) .chain() .in_set(HudButtons) diff --git a/solitaire_engine/src/hud_plugin/tab_bar.rs b/solitaire_engine/src/hud_plugin/tab_bar.rs index 5d3ef8c..5318090 100644 --- a/solitaire_engine/src/hud_plugin/tab_bar.rs +++ b/solitaire_engine/src/hud_plugin/tab_bar.rs @@ -24,7 +24,10 @@ use crate::assets::hud_icon_svg::HudIcon; use crate::ui_glass::{GLASS_BORDER_PX, glass_surface}; use crate::ui_theme::{ACCENT_PRIMARY_HOVER, SPACE_3}; use bevy::text::{LineBreak, TextLayout}; -use bevy::ui::Overflow; +use bevy::ui::{Overflow, UiTransform, Val2}; + +use crate::resources::DragState; +use crate::safe_area::SafeAreaInsets; /// Height of both the pill and the circular Menu button, in logical px. /// 64 keeps the inner 48-px buttons comfortably past the 44-px touch @@ -99,6 +102,20 @@ pub struct TabLabelWrap { #[derive(Component, Debug)] pub struct TabLabel; +/// Duck-away state on the bar container: while a card drag is committed +/// the whole bar slides below the screen edge so it never occludes a +/// drop target on the bottom tableau rows, then slides back on release. +/// `progress` ∈ [0, 1]; 1 = fully off-screen. +#[derive(Component, Debug)] +pub struct TabBarDuck { + /// Current slide amount, animated by [`duck_tab_bar_during_drag`]. + pub progress: f32, +} + +/// Extra slide distance past the bar's own height so its drop shadow +/// (16 px blur + 6 px offset) also clears the screen edge. +const DUCK_SHADOW_ALLOWANCE_PX: f32 = 24.0; + /// Rasterised icon textures for the five tab-bar actions. pub(super) struct TabIcons { undo: Handle, @@ -179,6 +196,8 @@ pub(super) fn spawn_glass_tab_bar( base_bottom: TAB_BAR_MARGIN, }, HudActionBar, + TabBarDuck { progress: 0.0 }, + UiTransform::IDENTITY, )) .with_children(|bar| { // The main pill: Undo · [Draw] · Hint · Pause. @@ -453,6 +472,46 @@ pub(super) fn animate_tab_expansion( } } +/// Slides the bar off the bottom edge while a card drag is committed and +/// back once it ends. Effectively touch-only: the desktop docked bar +/// never carries [`TabBarDuck`], so the query is empty there. +/// +/// The slide animates `UiTransform::translation` (a render-time offset, +/// +y = down) rather than `Node::bottom`, for two reasons: layout is +/// never dirtied mid-drag (the whole point is not to cost frames while +/// the player is dragging), and `Node::bottom` is owned by the safe-area +/// anchor system — two writers on one field would fight. +pub(super) fn duck_tab_bar_during_drag( + time: Res