feat(app): wire desktop window icon — Terminal ▌RS mark at runtime
Closes Resume-prompt Option A (the post-v0.21.0 first option). Half-day desktop work, no cert dependency. Three deliverables: 1. **SVG-authored icon** (`solitaire_engine/src/assets/icon_svg.rs`) — square Terminal mark: `#151515` background, brick-red `#a54242` 1 px border, brick-red ▌ cursor block centered, "RS" monogram in `#d0d0d0` foreground gray beneath. Same shape that already lives on the splash boot screen and card-back monogram, reused as the project's signature visual mark. Authored in a 64-unit logical box so it scales cleanly at every rasterisation target. 2. **9-size PNG hierarchy** (16, 24, 32, 48, 64, 128, 256, 512, 1024 px) regenerated by `solitaire_engine/examples/icon_generator.rs` into `assets/icon/icon_<size>.png`. Sizes cover Linux hicolor (16, 24, 32, 48, 64, 128, 256, 512), Windows .ico targets (16, 32, 48, 256), and macOS .icns targets (16, 32, 64, 128, 256, 512, 1024). The runtime path uses just the 256 px slot; the smaller sizes are pre-rendered for downstream packaging. 3. **Runtime `Window::icon` wiring** (`solitaire_app/src/lib.rs`). Bevy 0.18 has no `Window::icon` field — the icon is set through the underlying `winit::window::Window` via the `WinitWindows` resource. `set_window_icon` runs each Update tick, retries silently until `WinitWindows` is populated (typically frame 1 or 2), decodes the embedded 256 px PNG via `tiny_skia`, builds a `winit::window::Icon`, and self-disables via `Local<bool>`. Same one-shot pattern as `apply_smart_default_window_size`. Desktop-only — Android draws its launcher icon from the APK manifest, so the system is target-gated to `cfg(not(target_os = "android"))`. Dep changes (CLAUDE.md §8 user-confirmed): - `winit = "0.30"` promoted from a transitive Bevy dep to a direct dep on `solitaire_app` so `winit::window::Icon` is in scope — bevy_winit 0.18 doesn't re-export it. Version pinned to whatever Bevy uses; if Bevy bumps winit, this line bumps in lockstep. - `tiny-skia` added as a direct dep on `solitaire_app` for PNG → RGBA decode. Already in workspace deps for `solitaire_engine`; no version drift risk. - Both new deps target-gated to non-Android only. Test infrastructure: `solitaire_engine/tests/icon_svg_pin.rs` hashes the rasterised RGBA bytes at all 9 sizes via FNV-1a (same shape as `card_face_svg_pin`). Bootstrap pattern (empty EXPECTED → panic with hashes formatted as Rust source → paste back in) handles future intentional builder edits cleanly. Workspace clippy + cargo test --workspace clean. 1185 passing (+1 from v0.21.0's 1184 baseline — the icon pin's `rasterised_icon_bytes_match_pinned_hashes`). Out of scope for this commit: `.icns` / `.ico` bundling for macOS / Windows app packaging. Both are packaging-time concerns (set via bundle manifests, not runtime calls) and would need new deps (`ico` and `icns` crates) — separate followup if/when the project ships as a packaged macOS / Windows app rather than just `cargo run`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
//! SVG builder for the Solitaire Quest application icon.
|
||||
//!
|
||||
//! Renders the project's signature `▌RS` Terminal mark (the same
|
||||
//! cursor-block + monogram pair used on the splash boot-screen and
|
||||
//! card backs) on a dark `#151515` background with a 1 px brick-red
|
||||
//! border. Square aspect, authored in a 64-unit logical box and
|
||||
//! scaled at the rasterisation site.
|
||||
//!
|
||||
//! Reads at every size from 16 px taskbar tile to 1024 px macOS
|
||||
//! Retina icon — the high-contrast cursor block carries the
|
||||
//! recognition load and the smaller `RS` letters sit beneath as
|
||||
//! a secondary recognition cue.
|
||||
//!
|
||||
//! Same SVG-to-PNG pipeline as `card_face_svg` — `icon_generator`
|
||||
//! example rasterises this at multiple target sizes and writes
|
||||
//! into `assets/icon/`. The `icon_svg_pin` integration test hashes
|
||||
//! rasterised RGBA bytes to guard against `usvg`/`resvg` drift.
|
||||
|
||||
use bevy::math::UVec2;
|
||||
|
||||
/// Default rasterisation target — single canonical size used by the
|
||||
/// runtime `Window::icon` wiring. The generator example emits
|
||||
/// additional sizes (16, 32, 48, 64, 128, 256, 512, 1024) for the
|
||||
/// Linux hicolor hierarchy and for downstream `.ico` / `.icns`
|
||||
/// packaging.
|
||||
pub const TARGET: UVec2 = UVec2::new(256, 256);
|
||||
|
||||
/// Every size the `icon_generator` example emits. Covers Linux
|
||||
/// hicolor (16, 24, 32, 48, 64, 128, 256, 512), Windows `.ico`
|
||||
/// targets (16, 32, 48, 256), and macOS `.icns` targets (16, 32,
|
||||
/// 64, 128, 256, 512, 1024).
|
||||
pub const ICON_SIZES: &[u32] = &[16, 24, 32, 48, 64, 128, 256, 512, 1024];
|
||||
|
||||
const BG: &str = "#151515"; // BG_BASE
|
||||
const ACCENT: &str = "#a54242"; // ACCENT_PRIMARY brick red
|
||||
const FG: &str = "#d0d0d0"; // TEXT_PRIMARY
|
||||
|
||||
/// Build the icon SVG. Square aspect, 64 logical units per side.
|
||||
pub fn icon_svg() -> String {
|
||||
// Layout in a 64×64 logical box:
|
||||
// border: 1 logical unit, brick-red, inset 0.5 to
|
||||
// centre the stroke inside the pixmap.
|
||||
// corner radius: 6 units (~9 % of side, scales smoothly down
|
||||
// to 16 px where it disappears into pixel grid).
|
||||
// `▌` cursor: 18 px tall, 6 px wide, brick-red, centred
|
||||
// horizontally, sitting on a baseline at y=40
|
||||
// so there's room for `RS` beneath it.
|
||||
// `RS` mark: 14 px FiraMono Bold at y=58, foreground gray,
|
||||
// letter-spaced for readability at small sizes.
|
||||
//
|
||||
// The `▌` glyph is U+258C (LEFT HALF BLOCK) — same character the
|
||||
// splash and card-back monogram use, rendered upright at icon
|
||||
// scale. FiraMono carries this at usable size (verified by the
|
||||
// splash + card-back rendering), so `<text>` is safe here unlike
|
||||
// the suit glyphs.
|
||||
format!(
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||
<rect x="0.5" y="0.5" width="63" height="63" rx="6" ry="6"
|
||||
fill="{BG}" stroke="{ACCENT}" stroke-width="1"/>
|
||||
|
||||
<!-- Centred ▌ cursor block at y=22..40, brick-red. -->
|
||||
<rect x="29" y="22" width="6" height="18" fill="{ACCENT}"/>
|
||||
|
||||
<!-- RS monogram beneath, foreground gray. text-anchor=middle so
|
||||
the letterforms balance around the cursor block above. -->
|
||||
<text x="32" y="56" font-family="Fira Mono" font-size="14" font-weight="700"
|
||||
fill="{FG}" text-anchor="middle" letter-spacing="1">RS</text>
|
||||
</svg>"##
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user