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