feat(engine): card-theme picker in Settings → Cosmetic
CI / Test & Lint (push) Failing after 5s
CI / Release Build (push) Has been skipped

Wires the runtime theme system (CARD_PLAN.md phases 1–7) into the
visible Settings UI so a player can switch between every theme
discovered by `ThemeRegistry` without restarting.

solitaire_data/src/settings.rs
  Settings gains `selected_theme_id: String` (default "default"),
  guarded by `#[serde(default = "default_theme_id")]` so existing
  settings.json files deserialize cleanly.

solitaire_engine/src/settings_plugin.rs
  - SettingsButton::SelectTheme(String) variant + focus order 85.
  - sync_settings_panel_visibility now reads
    Option<Res<ThemeRegistry>>, snapshots id+display_name pairs, and
    threads them into spawn_settings_panel. When the registry is
    absent (tests under MinimalPlugins) the picker silently skips —
    every existing test continues to pass unchanged.
  - theme_picker_row helper: like picker_row but keyed by String
    rather than usize, with chips wide enough for theme display
    names. Attaches the canonical tooltip ("Choose card-face
    artwork. Imported themes appear here.") and the FocusRow marker
    so Left/Right arrows cycle within the row.
  - Click handler updates settings.selected_theme_id, persists, and
    fires SettingsChangedEvent — same shape as every other picker.

solitaire_engine/src/theme/plugin.rs
  - load_default_theme renamed to load_initial_theme; reads
    SettingsResource on Startup and seeds ActiveTheme from
    settings.selected_theme_id (falling back to embedded default).
  - react_to_settings_theme_change watches SettingsChangedEvent,
    no-ops when the active theme already matches, and otherwise
    swaps ActiveTheme — the existing
    sync_card_image_set_with_active_theme system then refreshes
    every card sprite on the next AssetEvent::LoadedWithDependencies.

cargo build / clippy --workspace --all-targets -- -D warnings / test
--workspace all green (960 passed, 0 failed, 9 ignored).
This commit is contained in:
funman300
2026-05-01 16:24:24 +00:00
parent a6b8348332
commit 924a1e2af7
3 changed files with 181 additions and 6 deletions
+14
View File
@@ -124,6 +124,14 @@ pub struct Settings {
/// `None` thanks to `#[serde(default)]`.
#[serde(default)]
pub window_geometry: Option<WindowGeometry>,
/// Identifier of the active card-art theme. Matches `meta.id` from
/// the theme's `theme.ron` manifest. `"default"` is the bundled
/// theme and is always present in the registry; user-supplied
/// themes register under their own ids when they're imported.
/// Older `settings.json` files default cleanly to `"default"` via
/// `#[serde(default = ...)]`.
#[serde(default = "default_theme_id")]
pub selected_theme_id: String,
}
fn default_draw_mode() -> DrawMode {
@@ -138,6 +146,10 @@ fn default_music_volume() -> f32 {
0.5
}
fn default_theme_id() -> String {
"default".to_string()
}
impl Default for Settings {
fn default() -> Self {
Self {
@@ -152,6 +164,7 @@ impl Default for Settings {
first_run_complete: false,
color_blind_mode: false,
window_geometry: None,
selected_theme_id: default_theme_id(),
}
}
}
@@ -304,6 +317,7 @@ mod tests {
first_run_complete: true,
color_blind_mode: false,
window_geometry: None,
selected_theme_id: "default".to_string(),
};
save_settings_to(&path, &s).expect("save");
let loaded = load_settings_from(&path);