//! Resize handling: window-resize snapping, in-place card resizing, //! and tableau fan spread. use super::*; use std::collections::HashMap; use bevy::window::WindowResized; use solitaire_core::Card; use solitaire_core::game_state::GameState; use crate::animation_plugin::CardAnim; use crate::events::StateChangedEvent; use crate::font_plugin::FontResource; use crate::layout::{Layout, LayoutResource}; use crate::resources::GameStateResource; use crate::table_plugin::PileMarker; use crate::ui_theme::{CARD_SHADOW_ALPHA_DRAG, CARD_SHADOW_PADDING_DRAG, CARD_SHADOW_PADDING_IDLE}; /// Coalesces every `WindowResized` event arriving this frame into the latest /// pending size on [`ResizeThrottle`]. /// /// `WindowResized` fires per pixel of resize drag, so a fast corner drag can /// emit many events per frame. Reading `.last()` keeps only the final size — /// every frame's snap target is the most recent window size, never a stale /// one. Pending stays set across frames until the throttled applier consumes /// it; that's how we still flush the final "release" position when the user /// stops dragging. pub(super) fn collect_resize_events( mut events: MessageReader, mut throttle: ResMut, ) { if let Some(ev) = events.read().last() { throttle.pending = Some(Vec2::new(ev.width, ev.height)); } } /// Snaps every card sprite to its target position, size, and (in the /// fallback Text2d label path) font size when the window is resized. /// /// **In-place mutation only.** Resize is the hot path — events fire per /// pixel of drag, so this system cannot afford the despawn/respawn churn /// `update_card_entity` does. We mutate `Sprite.custom_size`, `Transform`, /// and child `TextFont.font_size` directly, leaving the card image handle, /// suit/rank, and `CardLabel` entity untouched. Cards keep their identity /// across resizes; only their size and position change. The full repaint /// path lives in [`update_card_entity`] and is still used by every non-resize /// caller (deals, moves, flips, settings toggles). /// /// **Throttled to ~20 Hz.** [`ResizeThrottle::pending`] is consumed at most /// once per [`RESIZE_THROTTLE_SECS`]. When events stop arriving, the next /// tick past the throttle window flushes the final size and clears /// `pending`, so the steady-state always matches the user's release size. /// /// **Cancels in-flight slides.** Any `CardAnim` is removed so a mid-slide /// tween is not retargeted relative to the previous card-size's position. /// /// The "↺" stock-empty label's `font_size` is derived from /// `layout.card_size.x`, so this system also reapplies the stock indicator — /// otherwise the label would not rescale on resize. /// /// Scheduled after [`collect_resize_events`] (which itself runs after /// `LayoutSystem::UpdateOnResize`) so `LayoutResource` reflects the latest /// window size before we read it. #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(super) fn snap_cards_on_window_resize( mut commands: Commands, time: Res