8da62bd05f
Phase 3 step 3 of the UX overhaul. Adds a reusable modal helper that
the next 6 commits use to convert each overlay screen. The audit found
11 overlays using 3 different visual styles with scrim alpha drift
between 0.60 and 0.92; this primitive collapses all of that into one
consistent shape.
API surface:
- spawn_modal(commands, plugin_marker, z, build_card) — full-screen
scrim (uniform SCRIM token) + centred card (BG_ELEVATED, RADIUS_LG,
BORDER_STRONG outline, max-width 720, min-width 360, padding
SPACE_5). Returns the scrim entity for one-call despawn.
- spawn_modal_header(parent, title, font_res) — TYPE_HEADLINE
+ TEXT_PRIMARY, the canonical overlay heading.
- spawn_modal_body_text(parent, text, color, font_res) — TYPE_BODY_LG
paragraph; pass TEXT_PRIMARY or TEXT_SECONDARY.
- spawn_modal_actions(parent, build_buttons) — flex-row
justify-end with margin-top.
- spawn_modal_button(parent, marker, label, hotkey,
variant, font_res) — real Button
entity with optional TYPE_CAPTION hotkey-hint chip.
ButtonVariant enum drives colour:
Primary idle ACCENT_PRIMARY hover ACCENT_PRIMARY_HOVER
pressed ACCENT_SECONDARY (yellow → pink press flash)
Secondary idle BG_ELEVATED_HI hover BG_ELEVATED_TOP
pressed BG_ELEVATED
Tertiary idle BG_ELEVATED hover BG_ELEVATED_HI
pressed BG_ELEVATED_PRESSED
A new BG_ELEVATED_TOP token plus ACCENT_PRIMARY_HOVER cover the new
hover/press combinations cleanly.
UiModalPlugin registers paint_modal_buttons so every ModalButton gets
hover and press feedback automatically — overlay plugins don't add
their own paint systems. Plugin registered in solitaire_app.
A self-test asserts each variant's idle / hover / pressed colours are
all distinct; another verifies the plugin builds under MinimalPlugins.
This commit is purely additive — no overlay calls the new helpers
yet. The next commits convert each overlay to use them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
3.8 KiB
Rust
90 lines
3.8 KiB
Rust
use bevy::prelude::*;
|
|
use solitaire_data::{load_settings_from, provider_for_backend, settings_file_path, Settings};
|
|
use solitaire_engine::{
|
|
AchievementPlugin, AnimationPlugin, AudioPlugin, AutoCompletePlugin, CardAnimationPlugin,
|
|
CardPlugin, ChallengePlugin, CursorPlugin, DailyChallengePlugin, FeedbackAnimPlugin,
|
|
FontPlugin, GamePlugin, HelpPlugin, HomePlugin, HudPlugin, InputPlugin, LeaderboardPlugin,
|
|
OnboardingPlugin, PausePlugin, ProfilePlugin, ProgressPlugin, SelectionPlugin, SettingsPlugin,
|
|
StatsPlugin, SyncPlugin, TablePlugin, TimeAttackPlugin, UiModalPlugin, WeeklyGoalsPlugin,
|
|
WinSummaryPlugin,
|
|
};
|
|
|
|
fn main() {
|
|
// Initialise the platform keyring store before any token operations.
|
|
// On Linux this uses the Secret Service (GNOME Keyring / KWallet); on
|
|
// macOS it uses the Keychain; on Windows it uses the Credential store.
|
|
// If the platform has no OS keyring (e.g. a headless CI box), keyring
|
|
// operations will fail gracefully with TokenError::KeychainUnavailable.
|
|
if let Err(e) = keyring::use_native_store(true) {
|
|
eprintln!(
|
|
"warn: could not initialise OS keyring ({e}); \
|
|
server sync login will be unavailable"
|
|
);
|
|
}
|
|
|
|
// Load settings before building the app so we can construct the right
|
|
// sync provider. Falls back to defaults if no settings file exists yet.
|
|
let settings: Settings = settings_file_path()
|
|
.map(|p| load_settings_from(&p))
|
|
.unwrap_or_default();
|
|
let sync_provider = provider_for_backend(&settings.sync_backend);
|
|
|
|
App::new()
|
|
.add_plugins(
|
|
DefaultPlugins
|
|
.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
title: "Solitaire Quest".into(),
|
|
resolution: (1280u32, 800u32).into(),
|
|
resize_constraints: bevy::window::WindowResizeConstraints {
|
|
min_width: 800.0,
|
|
min_height: 600.0,
|
|
..default()
|
|
},
|
|
..default()
|
|
}),
|
|
..default()
|
|
})
|
|
// The `assets/` directory lives at the workspace root, but
|
|
// Bevy resolves `AssetPlugin::file_path` relative to the
|
|
// binary package's `CARGO_MANIFEST_DIR` (`solitaire_app/`).
|
|
// Point one level up so `cargo run -p solitaire_app` finds
|
|
// card faces, backs, backgrounds, and the UI font.
|
|
.set(bevy::asset::AssetPlugin {
|
|
file_path: "../assets".to_string(),
|
|
..default()
|
|
}),
|
|
)
|
|
.add_plugins(FontPlugin)
|
|
.add_plugins(GamePlugin)
|
|
.add_plugins(TablePlugin)
|
|
.add_plugins(CardPlugin)
|
|
.add_plugins(CursorPlugin)
|
|
.add_plugins(InputPlugin)
|
|
.add_plugins(SelectionPlugin)
|
|
.add_plugins(AnimationPlugin)
|
|
.add_plugins(FeedbackAnimPlugin)
|
|
.add_plugins(CardAnimationPlugin)
|
|
.add_plugins(AutoCompletePlugin)
|
|
.add_plugins(StatsPlugin::default())
|
|
.add_plugins(ProgressPlugin::default())
|
|
.add_plugins(AchievementPlugin::default())
|
|
.add_plugins(DailyChallengePlugin)
|
|
.add_plugins(WeeklyGoalsPlugin)
|
|
.add_plugins(ChallengePlugin)
|
|
.add_plugins(TimeAttackPlugin)
|
|
.add_plugins(HudPlugin)
|
|
.add_plugins(HelpPlugin)
|
|
.add_plugins(HomePlugin)
|
|
.add_plugins(ProfilePlugin)
|
|
.add_plugins(PausePlugin)
|
|
.add_plugins(SettingsPlugin::default())
|
|
.add_plugins(AudioPlugin)
|
|
.add_plugins(OnboardingPlugin)
|
|
.add_plugins(SyncPlugin::new(sync_provider))
|
|
.add_plugins(LeaderboardPlugin)
|
|
.add_plugins(WinSummaryPlugin)
|
|
.add_plugins(UiModalPlugin)
|
|
.run();
|
|
}
|