diff --git a/solitaire_engine/src/assets/hud_icon_svg.rs b/solitaire_engine/src/assets/hud_icon_svg.rs new file mode 100644 index 0000000..b8a7e40 --- /dev/null +++ b/solitaire_engine/src/assets/hud_icon_svg.rs @@ -0,0 +1,130 @@ +//! SVG builders for the floating tab bar's action icons. +//! +//! The touch tab bar (`hud_plugin::tab_bar`) is icon-first, but the bundled +//! FiraMono face has no icon glyphs (and the Geometric Shapes block U+25xx +//! is missing on Android entirely), so the icons are authored here as tiny +//! stroke-based SVGs and rasterised through the same `usvg`/`resvg` pipeline +//! as the card themes (`assets::rasterize_svg`). +//! +//! Icons are drawn in **white** on a transparent background inside a +//! 24-unit logical box; the spawn site tints them to the theme colour via +//! `ImageNode::color` (a per-node multiply), so one texture serves every +//! button state. + +use bevy::image::Image; +use bevy::math::UVec2; + +use super::svg_loader::{SvgLoaderError, rasterize_svg}; + +/// Rasterisation size for one tab-bar icon texture. Icons display at +/// 24 logical px; 96 px texels keep them crisp at the Fold's 1.2 scale +/// factor (and any future 3× phone) while staying trivially small. +pub const HUD_ICON_TEXTURE_PX: u32 = 96; + +/// The five actions the floating touch tab bar can show. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HudIcon { + /// Counter-clockwise "take back" arrow. + Undo, + /// Two offset card outlines — draw from the stock. + Draw, + /// Lightbulb — highlight a suggested move. + Hint, + /// Two vertical bars. + Pause, + /// Three-line hamburger — the floating menu button. + Menu, +} + +impl HudIcon { + /// Build the icon's SVG document. All icons share one visual language: + /// 2-unit white strokes, round caps, transparent fill, 24-unit box. + pub fn svg(self) -> String { + let body = match self { + // Arrow head pointing left into a half-circle sweeping back + // over the top — the conventional undo mark. + HudIcon::Undo => concat!( + r##""##, + r##""##, + ), + // Back card peeks out behind the front card; the front card is + // drawn second so its stroke reads on top. + HudIcon::Draw => concat!( + r##""##, + r##""##, + ), + // Bulb dome with a flat lamp base beneath. + HudIcon::Hint => concat!( + r##""##, + r##""##, + ), + HudIcon::Pause => concat!( + r##""##, + r##""##, + ), + HudIcon::Menu => concat!( + r##""##, + r##""##, + r##""##, + ), + }; + format!( + concat!( + r##""##, + r##"{body}"##, + r##""##, + ), + body = body + ) + } + + /// Rasterise this icon into a Bevy [`Image`] at + /// [`HUD_ICON_TEXTURE_PX`] square. + pub fn rasterize(self) -> Result { + rasterize_svg( + self.svg().as_bytes(), + UVec2::splat(HUD_ICON_TEXTURE_PX), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const ALL: [HudIcon; 5] = [ + HudIcon::Undo, + HudIcon::Draw, + HudIcon::Hint, + HudIcon::Pause, + HudIcon::Menu, + ]; + + /// Every icon must survive the real usvg/resvg pipeline at the real + /// texture size — a malformed path here would otherwise only surface + /// as a blank button on device. + #[test] + fn every_icon_rasterizes_at_texture_size() { + for icon in ALL { + let img = icon.rasterize().expect("icon SVG must rasterise"); + assert_eq!(img.width(), HUD_ICON_TEXTURE_PX, "{icon:?} width"); + assert_eq!(img.height(), HUD_ICON_TEXTURE_PX, "{icon:?} height"); + } + } + + /// Rasterised icons must contain visible (non-transparent) pixels — + /// guards against a stroke accidentally drawn outside the viewBox. + #[test] + fn every_icon_has_visible_pixels() { + for icon in ALL { + let img = icon.rasterize().expect("icon SVG must rasterise"); + let data = img.data.as_ref().expect("rasterised image has data"); + let opaque = data.chunks_exact(4).filter(|px| px[3] > 0).count(); + assert!( + opaque > 100, + "{icon:?} renders only {opaque} visible pixels" + ); + } + } +} diff --git a/solitaire_engine/src/assets/mod.rs b/solitaire_engine/src/assets/mod.rs index 7b11934..21d946a 100644 --- a/solitaire_engine/src/assets/mod.rs +++ b/solitaire_engine/src/assets/mod.rs @@ -5,6 +5,7 @@ //! custom `AssetSource` implementations used by the theme system. pub mod card_face_svg; +pub mod hud_icon_svg; pub mod icon_svg; pub mod sources; pub mod svg_loader;