chore: add pedantic workspace lints (#90)
Add [workspace.lints.rust] and wire each member crate up with [lints] workspace = true: unsafe_code = "deny" (forbid would break the Android JNI build) single_use_lifetimes = "warn" trivial_casts = "warn" unused_lifetimes = "warn" unused_qualifications = "warn" variant_size_differences = "warn" unexpected_cfgs = "warn" unsafe_code is "deny" rather than the issue's "forbid" so the three Android JNI FFI modules (android_keystore, android_clipboard, safe_area) can opt back in with a scoped #![allow(unsafe_code)] — forbid cannot be locally overridden. Pure crates carry no unsafe and stay clean. Clean up the warnings the new lints surface: - 150ish unused_qualifications removed via `cargo fix` (purely syntactic redundant-path-prefix removals). - table_plugin: the TABLE_COLOUR import was #[cfg(test)]-gated while the camera clear-colour used the fully-qualified path; unqualifying it left a non-test build with no import. Made the import unconditional instead. - assets/sources: the `as &[u8]` casts in embed_*_svg! coerce each fixed-size &[u8; N] to a uniform slice so the tuples fit the &[(&str, &[u8])] arrays — load-bearing, so scoped #[allow(trivial_casts)]. Workspace clippy -D warnings and the full test suite pass. Android build not compiled here (needs the NDK; built separately per CLAUDE.md §15) — the deny + scoped-allow keeps the JNI unsafe blocks legal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,3 +52,6 @@ web-sys = { version = "0.3", features = ["Storage", "Window"] }
|
||||
async-trait = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
solitaire_core = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -116,7 +116,7 @@ impl Plugin for AchievementPlugin {
|
||||
// achievements-scroll system also runs cleanly under
|
||||
// `MinimalPlugins` in tests.
|
||||
.add_message::<MouseWheel>()
|
||||
.add_message::<bevy::input::touch::TouchInput>()
|
||||
.add_message::<TouchInput>()
|
||||
// Run after GameMutation (so GameWonEvent is available), after
|
||||
// StatsUpdate (so stats reflect this win), and after ProgressUpdate
|
||||
// (so daily_challenge_streak is up to date for daily_devotee).
|
||||
@@ -671,7 +671,7 @@ mod tests {
|
||||
.add_plugins(AchievementPlugin::headless());
|
||||
// StatsPlugin's UI toggle system reads ButtonInput<KeyCode>; under
|
||||
// MinimalPlugins it isn't auto-registered.
|
||||
app.init_resource::<bevy::input::ButtonInput<KeyCode>>();
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.update();
|
||||
app
|
||||
}
|
||||
@@ -819,7 +819,7 @@ mod tests {
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.set_test_draw_mode(solitaire_core::DrawStockConfig::DrawThree);
|
||||
.set_test_draw_mode(DrawStockConfig::DrawThree);
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
score: 500,
|
||||
@@ -868,7 +868,7 @@ mod tests {
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.set_test_draw_mode(solitaire_core::DrawStockConfig::DrawThree);
|
||||
.set_test_draw_mode(DrawStockConfig::DrawThree);
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
score: 500,
|
||||
@@ -912,7 +912,7 @@ mod tests {
|
||||
// Put the active game in Zen mode. evaluate_on_win reads
|
||||
// GameStateResource.mode directly to populate last_win_is_zen.
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.mode =
|
||||
solitaire_core::game_state::GameMode::Zen;
|
||||
GameMode::Zen;
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
score: 0,
|
||||
@@ -946,7 +946,7 @@ mod tests {
|
||||
// Default GameMode is Classic; assert and rely on it.
|
||||
assert_eq!(
|
||||
app.world().resource::<GameStateResource>().0.mode,
|
||||
solitaire_core::game_state::GameMode::Classic
|
||||
GameMode::Classic
|
||||
);
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
@@ -1250,7 +1250,7 @@ mod tests {
|
||||
.add_plugins(crate::progress_plugin::ProgressPlugin::headless())
|
||||
.add_plugins(crate::settings_plugin::SettingsPlugin::headless())
|
||||
.add_plugins(AchievementPlugin::headless());
|
||||
app.init_resource::<bevy::input::ButtonInput<KeyCode>>();
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.update();
|
||||
app
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// JNI FFI requires `unsafe` to reconstruct `JavaVM` / `JObject` handles from
|
||||
// raw pointers handed over by the Android runtime. Scoped to this module so
|
||||
// the rest of the workspace stays `deny(unsafe_code)`.
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
/// Android clipboard bridge via JNI.
|
||||
///
|
||||
/// Writes text to the system clipboard by calling into `ClipboardManager`
|
||||
|
||||
@@ -354,7 +354,7 @@ fn handle_win_cascade(
|
||||
end: target.truncate(),
|
||||
elapsed: 0.0,
|
||||
duration,
|
||||
curve: crate::card_animation::MotionCurve::Expressive,
|
||||
curve: MotionCurve::Expressive,
|
||||
delay: i as f32 * step,
|
||||
start_z: start.z,
|
||||
end_z: target.z,
|
||||
|
||||
@@ -115,6 +115,10 @@ macro_rules! embed_classic_svg {
|
||||
}
|
||||
|
||||
/// Every Dark-theme SVG file bundled into the binary.
|
||||
// The `as &[u8]` in `embed_dark_svg!` coerces each fixed-size
|
||||
// `&[u8; N]` (N varies per file) to a uniform `&[u8]` so the tuples fit
|
||||
// this array type. The cast is load-bearing, not trivial.
|
||||
#[allow(trivial_casts)]
|
||||
const DARK_THEME_SVGS: &[(&str, &[u8])] = &[
|
||||
embed_dark_svg!("back.svg"),
|
||||
embed_dark_svg!("clubs_ace.svg"),
|
||||
@@ -172,6 +176,8 @@ const DARK_THEME_SVGS: &[(&str, &[u8])] = &[
|
||||
];
|
||||
|
||||
/// Every Classic-theme SVG file bundled into the binary.
|
||||
// See `DARK_THEME_SVGS`: the `as &[u8]` cast is load-bearing.
|
||||
#[allow(trivial_casts)]
|
||||
const CLASSIC_THEME_SVGS: &[(&str, &[u8])] = &[
|
||||
embed_classic_svg!("back.svg"),
|
||||
embed_classic_svg!("clubs_ace.svg"),
|
||||
|
||||
@@ -192,7 +192,7 @@ fn shared_fontdb() -> Arc<fontdb::Database> {
|
||||
fn bundled_font_resolver() -> usvg::FontResolver<'static> {
|
||||
use usvg::FontResolver;
|
||||
|
||||
usvg::FontResolver {
|
||||
FontResolver {
|
||||
select_font: Box::new(|_font, db| db.faces().next().map(|face| face.id)),
|
||||
select_fallback: FontResolver::default_fallback_selector(),
|
||||
}
|
||||
@@ -282,7 +282,7 @@ mod tests {
|
||||
/// tightens.
|
||||
#[test]
|
||||
fn settings_satisfies_loader_bounds() {
|
||||
fn assert_loader_settings<T: Default + serde::Serialize + serde::de::DeserializeOwned>() {}
|
||||
fn assert_loader_settings<T: Default + Serialize + serde::de::DeserializeOwned>() {}
|
||||
assert_loader_settings::<SvgLoaderSettings>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ mod tests {
|
||||
.add_plugins(GamePlugin)
|
||||
.add_plugins(TablePlugin)
|
||||
.add_plugins(AutoCompletePlugin);
|
||||
app.init_resource::<bevy::input::ButtonInput<KeyCode>>();
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.update();
|
||||
app
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct AvatarFetchEvent {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl bevy::prelude::Message for AvatarFetchEvent {}
|
||||
impl Message for AvatarFetchEvent {}
|
||||
|
||||
/// In-flight avatar download task. Returns the raw image bytes on success,
|
||||
/// or `None` on any network / decode error.
|
||||
|
||||
@@ -73,7 +73,7 @@ pub struct HoverState {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BufferedInput {
|
||||
Move {
|
||||
from: crate::events::MoveRequestEvent,
|
||||
from: MoveRequestEvent,
|
||||
},
|
||||
Draw,
|
||||
Undo,
|
||||
|
||||
@@ -483,7 +483,7 @@ impl Plugin for CardPlugin {
|
||||
update_stock_empty_indicator.after(GameMutation),
|
||||
update_stock_count_badge
|
||||
.after(GameMutation)
|
||||
.run_if(resource_changed::<crate::GameStateResource>),
|
||||
.run_if(resource_changed::<GameStateResource>),
|
||||
collect_resize_events.after(LayoutSystem::UpdateOnResize),
|
||||
snap_cards_on_window_resize.after(collect_resize_events),
|
||||
),
|
||||
@@ -2431,7 +2431,7 @@ fn update_tableau_fan_frac(
|
||||
.into_iter()
|
||||
.map(|tableau| {
|
||||
game.0
|
||||
.pile(solitaire_core::KlondikePile::Tableau(tableau))
|
||||
.pile(KlondikePile::Tableau(tableau))
|
||||
.into_iter()
|
||||
.filter(|(_, face_up)| *face_up)
|
||||
.count()
|
||||
@@ -2566,7 +2566,7 @@ mod tests {
|
||||
#[test]
|
||||
fn card_positions_includes_all_52_cards_at_game_start() {
|
||||
// At game start waste is empty, so all 52 cards are across stock + tableau.
|
||||
let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne);
|
||||
let g = GameState::new(42, DrawStockConfig::DrawOne);
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
assert_eq!(positions.len(), 52);
|
||||
@@ -2580,7 +2580,7 @@ mod tests {
|
||||
for _ in 0..3 {
|
||||
let _ = g.draw();
|
||||
}
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
g.waste_cards().iter().map(|c| c.0.clone()).collect();
|
||||
assert_eq!(waste_ids.len(), 3);
|
||||
|
||||
@@ -2624,7 +2624,7 @@ mod tests {
|
||||
"need at least 3 waste cards for this test"
|
||||
);
|
||||
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
waste_pile.iter().map(|c| c.0.clone()).collect();
|
||||
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
@@ -2677,7 +2677,7 @@ mod tests {
|
||||
let count = waste_pile.len();
|
||||
assert!(count >= 2, "need at least 2 waste cards");
|
||||
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
waste_pile.iter().map(|c| c.0.clone()).collect();
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
@@ -2715,7 +2715,7 @@ mod tests {
|
||||
for _ in 0..3 {
|
||||
let _ = g.draw();
|
||||
}
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
g.waste_cards().iter().map(|c| c.0.clone()).collect();
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
@@ -2740,7 +2740,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn card_positions_tableau_cards_are_fanned_downward() {
|
||||
let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne);
|
||||
let g = GameState::new(42, DrawStockConfig::DrawOne);
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
|
||||
@@ -3083,7 +3083,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn facedown_cards_use_tighter_fan_than_uniform_faceup_fan() {
|
||||
let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne);
|
||||
let g = GameState::new(42, DrawStockConfig::DrawOne);
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
|
||||
@@ -3153,7 +3153,7 @@ mod tests {
|
||||
|
||||
fn fire_window_resize(app: &mut App, width: f32, height: f32) {
|
||||
// Any Entity will do — the snap system reads only width/height.
|
||||
let window = bevy::ecs::entity::Entity::from_raw_u32(0)
|
||||
let window = Entity::from_raw_u32(0)
|
||||
.expect("Entity::from_raw_u32(0) is a valid placeholder");
|
||||
app.world_mut().write_message(WindowResized {
|
||||
window,
|
||||
@@ -3171,9 +3171,9 @@ mod tests {
|
||||
// entity IDs must remain alive.
|
||||
let mut app = app();
|
||||
|
||||
let labels_before: std::collections::HashSet<bevy::prelude::Entity> = app
|
||||
let labels_before: HashSet<Entity> = app
|
||||
.world_mut()
|
||||
.query_filtered::<bevy::prelude::Entity, With<CardLabel>>()
|
||||
.query_filtered::<Entity, With<CardLabel>>()
|
||||
.iter(app.world())
|
||||
.collect();
|
||||
assert!(
|
||||
@@ -3184,9 +3184,9 @@ mod tests {
|
||||
fire_window_resize(&mut app, 1024.0, 768.0);
|
||||
advance_past_resize_throttle(&mut app);
|
||||
|
||||
let labels_after: std::collections::HashSet<bevy::prelude::Entity> = app
|
||||
let labels_after: HashSet<Entity> = app
|
||||
.world_mut()
|
||||
.query_filtered::<bevy::prelude::Entity, With<CardLabel>>()
|
||||
.query_filtered::<Entity, With<CardLabel>>()
|
||||
.iter(app.world())
|
||||
.collect();
|
||||
|
||||
@@ -3336,9 +3336,9 @@ mod tests {
|
||||
|
||||
// Each shadow's parent must be a CardEntity, so the child relation
|
||||
// is wired correctly.
|
||||
let cards: HashSet<bevy::prelude::Entity> = app
|
||||
let cards: HashSet<Entity> = app
|
||||
.world_mut()
|
||||
.query_filtered::<bevy::prelude::Entity, With<CardEntity>>()
|
||||
.query_filtered::<Entity, With<CardEntity>>()
|
||||
.iter(app.world())
|
||||
.collect();
|
||||
let mut q = app
|
||||
@@ -3423,7 +3423,7 @@ mod tests {
|
||||
let card_entity = {
|
||||
let mut q = app
|
||||
.world_mut()
|
||||
.query::<(bevy::prelude::Entity, &CardEntity)>();
|
||||
.query::<(Entity, &CardEntity)>();
|
||||
q.iter(app.world())
|
||||
.find(|(_, c)| c.card == *card)
|
||||
.map(|(e, _)| e)
|
||||
@@ -3533,7 +3533,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stock_card_count_helper_reads_zero_for_empty_stock() {
|
||||
let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne);
|
||||
let g = GameState::new(42, DrawStockConfig::DrawOne);
|
||||
let mut g_empty_stock = g.clone();
|
||||
g_empty_stock.set_test_stock_cards(Vec::new());
|
||||
assert_eq!(stock_card_count(&g_empty_stock), 0);
|
||||
@@ -3553,9 +3553,9 @@ mod tests {
|
||||
// Allocate five different strong handles by passing each a
|
||||
// distinct dummy `Image`. We never render these; we only
|
||||
// compare ids.
|
||||
let mut images = bevy::asset::Assets::<bevy::image::Image>::default();
|
||||
let backs: [Handle<bevy::image::Image>; 5] =
|
||||
std::array::from_fn(|_| images.add(bevy::image::Image::default()));
|
||||
let mut images = Assets::<Image>::default();
|
||||
let backs: [Handle<Image>; 5] =
|
||||
std::array::from_fn(|_| images.add(Image::default()));
|
||||
CardImageSet {
|
||||
faces: std::array::from_fn(|_| std::array::from_fn(|_| Handle::default())),
|
||||
backs,
|
||||
@@ -3569,8 +3569,8 @@ mod tests {
|
||||
// card must render with the theme's back regardless of which
|
||||
// legacy back the player picked in Settings.
|
||||
let mut set = image_set_with_distinct_back_handles();
|
||||
let mut images = bevy::asset::Assets::<bevy::image::Image>::default();
|
||||
let theme_back: Handle<bevy::image::Image> = images.add(bevy::image::Image::default());
|
||||
let mut images = Assets::<Image>::default();
|
||||
let theme_back: Handle<Image> = images.add(Image::default());
|
||||
set.theme_back = Some(theme_back.clone());
|
||||
|
||||
let face_down = make_card(Suit::Spades, Rank::Ace);
|
||||
@@ -3634,8 +3634,8 @@ mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut set = image_set_with_distinct_back_handles();
|
||||
let mut images = bevy::asset::Assets::<bevy::image::Image>::default();
|
||||
let theme_back: Handle<bevy::image::Image> = images.add(bevy::image::Image::default());
|
||||
let mut images = Assets::<Image>::default();
|
||||
let theme_back: Handle<Image> = images.add(Image::default());
|
||||
|
||||
let theme = CardTheme {
|
||||
meta: ThemeMeta {
|
||||
@@ -3645,7 +3645,7 @@ mod tests {
|
||||
version: "0".into(),
|
||||
card_aspect: (2, 3),
|
||||
},
|
||||
faces: HashMap::<CardKey, Handle<bevy::image::Image>>::new(),
|
||||
faces: HashMap::<CardKey, Handle<Image>>::new(),
|
||||
back: theme_back.clone(),
|
||||
};
|
||||
|
||||
@@ -3813,7 +3813,7 @@ mod tests {
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
g.waste_cards().iter().map(|c| c.0.clone()).collect();
|
||||
|
||||
let mut waste_zs: Vec<f32> = positions
|
||||
@@ -3863,7 +3863,7 @@ mod tests {
|
||||
|
||||
let stock_x = layout.pile_positions[&KlondikePile::Stock].x;
|
||||
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
g.waste_cards().iter().map(|c| c.0.clone()).collect();
|
||||
|
||||
let mut waste_positions: Vec<_> = card_positions(&g, &layout)
|
||||
@@ -3893,7 +3893,7 @@ mod tests {
|
||||
let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true);
|
||||
let positions = card_positions(&g, &layout);
|
||||
|
||||
let waste_ids: std::collections::HashSet<Card> =
|
||||
let waste_ids: HashSet<Card> =
|
||||
g.waste_cards().iter().map(|c| c.0.clone()).collect();
|
||||
|
||||
let mut waste_zs: Vec<f32> = positions
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Plugin for CursorPlugin {
|
||||
Update,
|
||||
(
|
||||
update_cursor_icon,
|
||||
update_drop_highlights.run_if(resource_changed::<crate::resources::DragState>),
|
||||
update_drop_highlights.run_if(resource_changed::<DragState>),
|
||||
update_drop_target_overlays,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -639,7 +639,7 @@ fn lerp_color(from: Color, to: Color, t: f32) -> Color {
|
||||
fn pile_cards(
|
||||
game: &solitaire_core::game_state::GameState,
|
||||
pile: &KlondikePile,
|
||||
) -> Vec<(solitaire_core::Card, bool)> {
|
||||
) -> Vec<(Card, bool)> {
|
||||
match pile {
|
||||
KlondikePile::Stock => game.waste_cards(),
|
||||
_ => game.pile(*pile),
|
||||
|
||||
@@ -201,7 +201,7 @@ impl Plugin for GamePlugin {
|
||||
.add_message::<StateChangedEvent>()
|
||||
.add_message::<crate::events::MoveRejectedEvent>()
|
||||
.add_message::<GameWonEvent>()
|
||||
.add_message::<crate::events::CardFlippedEvent>()
|
||||
.add_message::<CardFlippedEvent>()
|
||||
.add_message::<crate::events::AchievementUnlockedEvent>()
|
||||
.add_message::<FoundationCompletedEvent>()
|
||||
.add_message::<InfoToastEvent>()
|
||||
@@ -530,7 +530,7 @@ fn handle_new_game(
|
||||
// hides that information and reads naturally as "dealt from the
|
||||
// deck." Skipped when LayoutResource isn't present (headless tests).
|
||||
if let Some(layout) = layout.as_ref()
|
||||
&& let Some(stock) = layout.0.pile_positions.get(&solitaire_core::KlondikePile::Stock)
|
||||
&& let Some(stock) = layout.0.pile_positions.get(&KlondikePile::Stock)
|
||||
{
|
||||
for mut tx in &mut card_transforms {
|
||||
tx.translation.x = stock.x;
|
||||
@@ -868,7 +868,7 @@ fn handle_move(
|
||||
mut game: ResMut<GameStateResource>,
|
||||
mut changed: MessageWriter<StateChangedEvent>,
|
||||
mut won: MessageWriter<GameWonEvent>,
|
||||
mut flipped: MessageWriter<crate::events::CardFlippedEvent>,
|
||||
mut flipped: MessageWriter<CardFlippedEvent>,
|
||||
mut foundation_done: MessageWriter<FoundationCompletedEvent>,
|
||||
mut recording: ResMut<RecordingReplay>,
|
||||
path: Option<Res<GameStatePath>>,
|
||||
@@ -909,7 +909,7 @@ fn handle_move(
|
||||
.last()
|
||||
.is_some_and(|c| c.0 == fcard && c.1)
|
||||
{
|
||||
flipped.write(crate::events::CardFlippedEvent(fcard));
|
||||
flipped.write(CardFlippedEvent(fcard));
|
||||
}
|
||||
// If this move landed on a foundation pile and that pile is
|
||||
// now complete (Ace → King, 13 cards), fire the per-suit
|
||||
@@ -1530,7 +1530,7 @@ mod tests {
|
||||
// Persistence tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
fn tmp_gs_path(name: &str) -> std::path::PathBuf {
|
||||
fn tmp_gs_path(name: &str) -> PathBuf {
|
||||
std::env::temp_dir().join(format!("engine_test_gs_{name}.json"))
|
||||
}
|
||||
|
||||
@@ -1684,7 +1684,7 @@ mod tests {
|
||||
|
||||
let events = app
|
||||
.world()
|
||||
.resource::<Messages<crate::events::CardFlippedEvent>>();
|
||||
.resource::<Messages<CardFlippedEvent>>();
|
||||
let mut cursor = events.get_cursor();
|
||||
let fired: Vec<_> = cursor.read(events).collect();
|
||||
assert!(
|
||||
|
||||
@@ -51,7 +51,7 @@ impl Plugin for HelpPlugin {
|
||||
// plugin under `DefaultPlugins`; register them explicitly so
|
||||
// scroll systems run cleanly under `MinimalPlugins` in tests.
|
||||
.add_message::<MouseWheel>()
|
||||
.add_message::<bevy::input::touch::TouchInput>()
|
||||
.add_message::<TouchInput>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
|
||||
@@ -1878,7 +1878,7 @@ mod tests {
|
||||
|
||||
let states: Vec<(HomeMode, bool)> = app
|
||||
.world_mut()
|
||||
.query::<(&HomeModeCard, bevy::ecs::query::Has<Disabled>)>()
|
||||
.query::<(&HomeModeCard, Has<Disabled>)>()
|
||||
.iter(app.world())
|
||||
.map(|(c, d)| (c.0, d))
|
||||
.collect();
|
||||
|
||||
@@ -2428,8 +2428,8 @@ mod tests {
|
||||
app.init_resource::<HintSolverConfig>();
|
||||
app.init_resource::<crate::pending_hint::PendingHintTask>();
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.insert_resource(crate::layout::LayoutResource(
|
||||
crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true),
|
||||
app.insert_resource(LayoutResource(
|
||||
compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true),
|
||||
));
|
||||
app.insert_resource(GameStateResource(GameState::new(42, DrawStockConfig::DrawOne)));
|
||||
app.add_systems(Update, handle_keyboard_hint);
|
||||
|
||||
@@ -745,7 +745,7 @@ mod tests {
|
||||
);
|
||||
// The HUD band top clearance (distance from window top to card top)
|
||||
// must match as well — this is the quantity directly visible in Bug 2.
|
||||
let card_top = |layout: &super::Layout| {
|
||||
let card_top = |layout: &Layout| {
|
||||
layout.pile_positions[&KlondikePile::Stock].y + layout.card_size.y / 2.0
|
||||
};
|
||||
assert!(
|
||||
|
||||
@@ -862,7 +862,7 @@ fn handle_display_name_confirm(
|
||||
.leaderboard_display_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| {
|
||||
if let solitaire_data::settings::SyncBackend::SolitaireServer {
|
||||
if let SyncBackend::SolitaireServer {
|
||||
ref username,
|
||||
..
|
||||
} = settings.0.sync_backend
|
||||
@@ -1091,7 +1091,7 @@ mod tests {
|
||||
.add_plugins(crate::achievement_plugin::AchievementPlugin::headless())
|
||||
.add_plugins(SyncPlugin::new(NoOpProvider))
|
||||
.add_plugins(LeaderboardPlugin);
|
||||
app.init_resource::<bevy::input::ButtonInput<KeyCode>>();
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.update();
|
||||
app
|
||||
}
|
||||
|
||||
@@ -2312,8 +2312,8 @@ fn format_suit_glyph_all_suits() {
|
||||
fn format_foundations_row_empty_board() {
|
||||
let game = solitaire_core::game_state::GameState::new_with_mode(
|
||||
42,
|
||||
solitaire_core::DrawStockConfig::DrawOne,
|
||||
solitaire_core::game_state::GameMode::Classic,
|
||||
DrawStockConfig::DrawOne,
|
||||
GameMode::Classic,
|
||||
);
|
||||
assert_eq!(format_foundations_row(&game), "F: -- -- -- --");
|
||||
}
|
||||
@@ -2324,8 +2324,8 @@ fn format_foundations_row_empty_board() {
|
||||
fn format_stock_waste_row_initial_state() {
|
||||
let game = solitaire_core::game_state::GameState::new_with_mode(
|
||||
42,
|
||||
solitaire_core::DrawStockConfig::DrawOne,
|
||||
solitaire_core::game_state::GameMode::Classic,
|
||||
DrawStockConfig::DrawOne,
|
||||
GameMode::Classic,
|
||||
);
|
||||
let text = format_stock_waste_row(&game);
|
||||
assert!(
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
//! Safe-area insets.
|
||||
//!
|
||||
// JNI FFI (Android only) requires `unsafe` to reconstruct `JavaVM` /
|
||||
// `JObject` handles from raw pointers handed over by the runtime. Scoped to
|
||||
// this module so the rest of the workspace stays `deny(unsafe_code)`.
|
||||
#![allow(unsafe_code)]
|
||||
//!
|
||||
//! Reports the OS-reserved regions around the playable surface (status
|
||||
//! bar at the top, gesture / navigation bar at the bottom on Android,
|
||||
//! display cutouts, etc.) so UI anchored to a screen edge can avoid
|
||||
|
||||
@@ -163,7 +163,7 @@ impl Plugin for SelectionPlugin {
|
||||
update_selection_highlight.after(GameMutation).run_if(
|
||||
resource_changed::<SelectionState>
|
||||
.or(resource_changed::<KeyboardDragState>)
|
||||
.or(resource_changed::<crate::GameStateResource>),
|
||||
.or(resource_changed::<GameStateResource>),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -534,7 +534,7 @@ fn handle_selection_keys(
|
||||
/// destination after a lift. Players who want a different column simply
|
||||
/// press the right-arrow key once or twice.
|
||||
pub(crate) fn legal_destinations_for(
|
||||
_bottom: &solitaire_core::Card,
|
||||
_bottom: &Card,
|
||||
source: &KlondikePile,
|
||||
game: &GameState,
|
||||
stack_count: usize,
|
||||
@@ -579,7 +579,7 @@ pub(crate) fn legal_destinations_for(
|
||||
/// Walks backwards from the last element and stops at the first face-down card
|
||||
/// (or when the slice is exhausted). Returns at least `1` when the top card is
|
||||
/// face-up; returns `0` for an empty slice or when the top card is face-down.
|
||||
fn face_up_run_len(cards: &[(solitaire_core::Card, bool)]) -> usize {
|
||||
fn face_up_run_len(cards: &[(Card, bool)]) -> usize {
|
||||
let mut count = 0;
|
||||
for (_, face_up) in cards.iter().rev() {
|
||||
if *face_up {
|
||||
@@ -598,8 +598,8 @@ fn face_up_run_len(cards: &[(solitaire_core::Card, bool)]) -> usize {
|
||||
/// handler can attempt a foundation move first and fall through to a
|
||||
/// multi-card stack move rather than accepting a single-card tableau move.
|
||||
fn try_foundation_dest(
|
||||
card: &solitaire_core::Card,
|
||||
game: &solitaire_core::game_state::GameState,
|
||||
card: &Card,
|
||||
game: &GameState,
|
||||
) -> Option<KlondikePile> {
|
||||
let source = game.pile_containing_card(card.clone())?;
|
||||
for foundation in [
|
||||
|
||||
@@ -379,8 +379,8 @@ impl Plugin for SettingsPlugin {
|
||||
.add_message::<DeleteAccountRequestEvent>()
|
||||
.add_message::<ToggleSettingsRequestEvent>()
|
||||
.add_message::<InfoToastEvent>()
|
||||
.add_message::<bevy::input::mouse::MouseWheel>()
|
||||
.add_message::<bevy::input::touch::TouchInput>()
|
||||
.add_message::<MouseWheel>()
|
||||
.add_message::<TouchInput>()
|
||||
// `WindowResized` / `WindowMoved` are real Bevy window events
|
||||
// and emitted by the windowing backend under `DefaultPlugins`,
|
||||
// but we register them explicitly here so the geometry watcher
|
||||
@@ -2662,7 +2662,7 @@ fn handle_scan_themes(
|
||||
|
||||
let themes_dir = user_theme_dir();
|
||||
|
||||
let zips: Vec<std::path::PathBuf> = match std::fs::read_dir(&themes_dir) {
|
||||
let zips: Vec<PathBuf> = match std::fs::read_dir(&themes_dir) {
|
||||
Ok(entries) => entries
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
@@ -3019,7 +3019,7 @@ mod tests {
|
||||
unit: MouseScrollUnit::Line,
|
||||
x: 0.0,
|
||||
y: -3.0,
|
||||
window: bevy::ecs::entity::Entity::PLACEHOLDER,
|
||||
window: Entity::PLACEHOLDER,
|
||||
});
|
||||
app.update();
|
||||
// ScrollPosition must remain at 0.0 — panel was closed.
|
||||
@@ -3052,7 +3052,7 @@ mod tests {
|
||||
unit: MouseScrollUnit::Line,
|
||||
x: 0.0,
|
||||
y: -2.0,
|
||||
window: bevy::ecs::entity::Entity::PLACEHOLDER,
|
||||
window: Entity::PLACEHOLDER,
|
||||
});
|
||||
app.update();
|
||||
let offset = app
|
||||
@@ -3364,7 +3364,7 @@ mod tests {
|
||||
|
||||
fn fire_resize(app: &mut App, width: f32, height: f32) {
|
||||
app.world_mut().write_message(WindowResized {
|
||||
window: bevy::ecs::entity::Entity::PLACEHOLDER,
|
||||
window: Entity::PLACEHOLDER,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
@@ -3372,7 +3372,7 @@ mod tests {
|
||||
|
||||
fn fire_move(app: &mut App, x: i32, y: i32) {
|
||||
app.world_mut().write_message(WindowMoved {
|
||||
window: bevy::ecs::entity::Entity::PLACEHOLDER,
|
||||
window: Entity::PLACEHOLDER,
|
||||
position: IVec2::new(x, y),
|
||||
});
|
||||
}
|
||||
@@ -3494,7 +3494,7 @@ mod tests {
|
||||
unit: MouseScrollUnit::Line,
|
||||
x: 0.0,
|
||||
y: 5.0,
|
||||
window: bevy::ecs::entity::Entity::PLACEHOLDER,
|
||||
window: Entity::PLACEHOLDER,
|
||||
});
|
||||
app.update();
|
||||
let offset = app
|
||||
|
||||
@@ -1010,8 +1010,8 @@ mod tests {
|
||||
use solitaire_data::Settings;
|
||||
let mut app = App::new();
|
||||
app.add_plugins(MinimalPlugins)
|
||||
.add_plugins(bevy::asset::AssetPlugin::default())
|
||||
.init_asset::<bevy::image::Image>()
|
||||
.add_plugins(AssetPlugin::default())
|
||||
.init_asset::<Image>()
|
||||
.add_plugins(SplashPlugin);
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.init_resource::<ButtonInput<MouseButton>>();
|
||||
|
||||
@@ -203,7 +203,7 @@ impl Plugin for StatsPlugin {
|
||||
// `DefaultPlugins`; register it explicitly so the stats-scroll
|
||||
// system also runs cleanly under `MinimalPlugins` in tests.
|
||||
.add_message::<MouseWheel>()
|
||||
.add_message::<bevy::input::touch::TouchInput>()
|
||||
.add_message::<TouchInput>()
|
||||
// record_abandoned must read `move_count` BEFORE handle_new_game
|
||||
// clobbers it with a fresh game. These are NOT in StatsUpdate because
|
||||
// StatsUpdate (as a set) is ordered after GameMutation by external
|
||||
@@ -464,7 +464,7 @@ fn repaint_replay_selector_detail(
|
||||
/// Pure helper: render the detail line for the selected replay. Returns
|
||||
/// `"{duration} win on {date}"` plus a `" \u{2022} Shareable"` badge
|
||||
/// when a share URL is present. Empty when the history slice is empty.
|
||||
pub fn replay_selector_detail(replays: &[solitaire_data::Replay], index: usize) -> String {
|
||||
pub fn replay_selector_detail(replays: &[Replay], index: usize) -> String {
|
||||
let Some(r) = replays.get(index.min(replays.len().saturating_sub(1))) else {
|
||||
return String::new();
|
||||
};
|
||||
@@ -1325,7 +1325,7 @@ mod tests {
|
||||
fn draw_three_win_increments_draw_three_wins_only() {
|
||||
let mut app = headless_app();
|
||||
app.world_mut()
|
||||
.resource_mut::<crate::resources::GameStateResource>()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.set_test_draw_mode(solitaire_core::DrawStockConfig::DrawThree);
|
||||
|
||||
@@ -1371,7 +1371,7 @@ mod tests {
|
||||
let mut app = headless_app();
|
||||
|
||||
app.world_mut()
|
||||
.resource_mut::<crate::resources::GameStateResource>()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.set_test_move_count(3);
|
||||
|
||||
@@ -1501,7 +1501,7 @@ mod tests {
|
||||
fn zen_win_event_updates_zen_best_score_only() {
|
||||
let mut app = headless_app();
|
||||
app.world_mut()
|
||||
.resource_mut::<crate::resources::GameStateResource>()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.mode = solitaire_core::game_state::GameMode::Zen;
|
||||
|
||||
@@ -1697,7 +1697,7 @@ mod tests {
|
||||
stats.0.win_streak_current = 3;
|
||||
}
|
||||
app.world_mut()
|
||||
.resource_mut::<crate::resources::GameStateResource>()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.set_test_move_count(1);
|
||||
|
||||
@@ -1723,7 +1723,7 @@ mod tests {
|
||||
stats.0.win_streak_current = 1;
|
||||
}
|
||||
app.world_mut()
|
||||
.resource_mut::<crate::resources::GameStateResource>()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.set_test_move_count(1);
|
||||
|
||||
@@ -1948,9 +1948,9 @@ mod tests {
|
||||
///
|
||||
/// Uses a fixed seed, DrawOne mode, Classic game, 2026-05-08 date.
|
||||
/// `time_seconds` and `share_url` are the only varying fields across tests.
|
||||
fn make_test_replay(time_seconds: u64, share_url: Option<String>) -> solitaire_data::Replay {
|
||||
fn make_test_replay(time_seconds: u64, share_url: Option<String>) -> Replay {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 8).expect("valid date");
|
||||
let mut r = solitaire_data::Replay::new(
|
||||
let mut r = Replay::new(
|
||||
1,
|
||||
solitaire_core::DrawStockConfig::DrawOne,
|
||||
solitaire_core::game_state::GameMode::Classic,
|
||||
|
||||
@@ -496,7 +496,7 @@ mod tests {
|
||||
.add_plugins(crate::achievement_plugin::AchievementPlugin::headless())
|
||||
.add_plugins(SyncPlugin::new(provider));
|
||||
// MinimalPlugins does not register keyboard input.
|
||||
app.init_resource::<bevy::input::ButtonInput<KeyCode>>();
|
||||
app.init_resource::<ButtonInput<KeyCode>>();
|
||||
app.update();
|
||||
app
|
||||
}
|
||||
@@ -629,8 +629,8 @@ mod tests {
|
||||
replays: vec![initial],
|
||||
};
|
||||
save_replay_history_to(&path, &history).expect("seed history on disk");
|
||||
app.insert_resource(crate::stats_plugin::ReplayHistoryResource(history));
|
||||
app.insert_resource(crate::stats_plugin::LatestReplayPath(Some(path.clone())));
|
||||
app.insert_resource(ReplayHistoryResource(history));
|
||||
app.insert_resource(LatestReplayPath(Some(path.clone())));
|
||||
|
||||
// Pre-resolved task carrying the URL the production path would
|
||||
// get back from the server.
|
||||
@@ -659,7 +659,7 @@ mod tests {
|
||||
// In-memory contract: replays[0].share_url is now Some(url).
|
||||
let live = app
|
||||
.world()
|
||||
.resource::<crate::stats_plugin::ReplayHistoryResource>();
|
||||
.resource::<ReplayHistoryResource>();
|
||||
assert_eq!(
|
||||
live.0.replays.first().and_then(|r| r.share_url.clone()),
|
||||
Some(url.clone()),
|
||||
|
||||
@@ -11,9 +11,7 @@ use solitaire_core::Suit;
|
||||
|
||||
use crate::events::{HintVisualEvent, StateChangedEvent};
|
||||
use crate::hud_plugin::HudVisibility;
|
||||
#[cfg(test)]
|
||||
use crate::layout::TABLE_COLOUR;
|
||||
use crate::layout::{Layout, LayoutResource, LayoutSystem, compute_layout};
|
||||
use crate::layout::{Layout, LayoutResource, LayoutSystem, TABLE_COLOUR, compute_layout};
|
||||
use crate::resources::GameStateResource;
|
||||
use crate::safe_area::SafeAreaInsets;
|
||||
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource};
|
||||
@@ -177,9 +175,9 @@ fn setup_table(
|
||||
Camera2d,
|
||||
Camera {
|
||||
clear_color: ClearColorConfig::Custom(Color::srgb(
|
||||
crate::layout::TABLE_COLOUR[0],
|
||||
crate::layout::TABLE_COLOUR[1],
|
||||
crate::layout::TABLE_COLOUR[2],
|
||||
TABLE_COLOUR[0],
|
||||
TABLE_COLOUR[1],
|
||||
TABLE_COLOUR[2],
|
||||
)),
|
||||
..default()
|
||||
},
|
||||
|
||||
@@ -236,7 +236,7 @@ pub fn import_theme_into(zip_path: &Path, target_root: &Path) -> Result<ThemeId,
|
||||
/// Sums every entry's declared uncompressed size and rejects archives
|
||||
/// that overflow [`MAX_ARCHIVE_BYTES`]. Iterates the central
|
||||
/// directory only — does not actually decompress anything.
|
||||
fn enforce_archive_size_limit<R: io::Read + io::Seek>(
|
||||
fn enforce_archive_size_limit<R: Read + io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
) -> Result<(), ImportError> {
|
||||
let mut total: u64 = 0;
|
||||
@@ -257,7 +257,7 @@ fn enforce_archive_size_limit<R: io::Read + io::Seek>(
|
||||
/// (after normalisation) escapes its root. Catches `..`, absolute
|
||||
/// paths, drive prefixes on Windows, and the awkward case where
|
||||
/// `enclosed_name` returns `None` because the entry is suspicious.
|
||||
fn enforce_zip_slip_safe<R: io::Read + io::Seek>(
|
||||
fn enforce_zip_slip_safe<R: Read + io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
) -> Result<(), ImportError> {
|
||||
for i in 0..archive.len() {
|
||||
@@ -291,7 +291,7 @@ fn is_safe_relative_path(p: &Path) -> bool {
|
||||
/// `theme.ron` entry at its root.
|
||||
/// - [`ImportError::ManifestParse`] when the bytes don't form valid
|
||||
/// RON for `ThemeManifest`.
|
||||
fn read_manifest<R: io::Read + io::Seek>(
|
||||
fn read_manifest<R: Read + io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
) -> Result<ThemeManifest, ImportError> {
|
||||
// We can't use `?` directly across `by_name` because a missing
|
||||
@@ -318,7 +318,7 @@ fn read_manifest<R: io::Read + io::Seek>(
|
||||
///
|
||||
/// Returns [`ImportError::MissingFile`] when the archive has no entry
|
||||
/// matching the path.
|
||||
fn read_archive_entry<R: io::Read + io::Seek>(
|
||||
fn read_archive_entry<R: Read + io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
path: &Path,
|
||||
) -> Result<Vec<u8>, ImportError> {
|
||||
@@ -351,7 +351,7 @@ fn archive_key(path: &Path) -> String {
|
||||
/// parent directories as needed. The destination path is rebuilt from
|
||||
/// the safe components we already vetted in
|
||||
/// [`enforce_zip_slip_safe`], not from the raw entry name.
|
||||
fn write_archive_entry<R: io::Read + io::Seek>(
|
||||
fn write_archive_entry<R: Read + io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
name: &str,
|
||||
staging: &Path,
|
||||
@@ -384,7 +384,7 @@ fn write_archive_entry<R: io::Read + io::Seek>(
|
||||
|
||||
/// Variant of [`write_archive_entry`] keyed by `Path` for the
|
||||
/// manifest-declared face/back paths.
|
||||
fn write_archive_entry_pathbuf<R: io::Read + io::Seek>(
|
||||
fn write_archive_entry_pathbuf<R: Read + io::Seek>(
|
||||
archive: &mut zip::ZipArchive<R>,
|
||||
path: &Path,
|
||||
staging: &Path,
|
||||
|
||||
@@ -484,7 +484,7 @@ mod tests {
|
||||
let mut image_set = empty_card_image_set();
|
||||
// Snapshot the legacy back ids so we can prove they don't
|
||||
// change when a theme is applied.
|
||||
let legacy_ids_before: [bevy::asset::AssetId<bevy::image::Image>; 5] =
|
||||
let legacy_ids_before: [AssetId<Image>; 5] =
|
||||
std::array::from_fn(|i| image_set.backs[i].id());
|
||||
let theme = empty_theme();
|
||||
assert!(image_set.theme_back.is_none(), "theme_back starts empty");
|
||||
|
||||
@@ -521,7 +521,7 @@ mod tests {
|
||||
// the session timer or the running win count.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
fn tmp_ta_path(name: &str) -> std::path::PathBuf {
|
||||
fn tmp_ta_path(name: &str) -> PathBuf {
|
||||
std::env::temp_dir().join(format!("engine_test_ta_{name}.json"))
|
||||
}
|
||||
|
||||
|
||||
@@ -1324,7 +1324,7 @@ mod tests {
|
||||
let row = world.spawn((FocusRow, Node::default())).id();
|
||||
world.entity_mut(scrim).add_child(row);
|
||||
|
||||
let make_swatch = |w: &mut World, marker: fn(&mut bevy::ecs::world::EntityWorldMut)| {
|
||||
let make_swatch = |w: &mut World, marker: fn(&mut EntityWorldMut)| {
|
||||
let mut e = w.spawn((
|
||||
Button,
|
||||
Node::default(),
|
||||
|
||||
@@ -982,9 +982,9 @@ mod tests {
|
||||
outline_width: 0.0,
|
||||
outline_offset: 0.0,
|
||||
unrounded_size: card_size,
|
||||
border: bevy::sprite::BorderRect::default(),
|
||||
border_radius: bevy::ui::ResolvedBorderRadius::default(),
|
||||
padding: bevy::sprite::BorderRect::default(),
|
||||
border: BorderRect::default(),
|
||||
border_radius: ResolvedBorderRadius::default(),
|
||||
padding: BorderRect::default(),
|
||||
inverse_scale_factor: 1.0,
|
||||
};
|
||||
// `is_empty` guard inside Bevy treats zero-size
|
||||
|
||||
@@ -249,12 +249,12 @@ pub const BORDER_SUBTLE_HC: Color = Color::srgba(0.627, 0.627, 0.627, 1.0);
|
||||
pub struct HighContrastBorder {
|
||||
/// Border colour to use when high-contrast mode is *off* — the
|
||||
/// site's normal idle / active-state colour.
|
||||
pub default_color: bevy::prelude::Color,
|
||||
pub default_color: Color,
|
||||
}
|
||||
|
||||
impl HighContrastBorder {
|
||||
/// Convenience constructor — `HighContrastBorder::with_default(BORDER_SUBTLE)`.
|
||||
pub const fn with_default(default_color: bevy::prelude::Color) -> Self {
|
||||
pub const fn with_default(default_color: Color) -> Self {
|
||||
Self { default_color }
|
||||
}
|
||||
}
|
||||
@@ -282,18 +282,18 @@ impl HighContrastBorder {
|
||||
pub struct HighContrastBackground {
|
||||
/// Background colour to use when high-contrast mode is *off* —
|
||||
/// the site's normal idle / active-state colour.
|
||||
pub default_color: bevy::prelude::Color,
|
||||
pub default_color: Color,
|
||||
/// Background colour to use when high-contrast mode is *on*.
|
||||
/// Defaults to [`BORDER_SUBTLE_HC`] via [`with_default`].
|
||||
///
|
||||
/// [`with_default`]: HighContrastBackground::with_default
|
||||
pub hc_color: bevy::prelude::Color,
|
||||
pub hc_color: Color,
|
||||
}
|
||||
|
||||
impl HighContrastBackground {
|
||||
/// Convenience constructor — HC colour defaults to
|
||||
/// [`BORDER_SUBTLE_HC`].
|
||||
pub const fn with_default(default_color: bevy::prelude::Color) -> Self {
|
||||
pub const fn with_default(default_color: Color) -> Self {
|
||||
Self {
|
||||
default_color,
|
||||
hc_color: BORDER_SUBTLE_HC,
|
||||
@@ -305,8 +305,8 @@ impl HighContrastBackground {
|
||||
/// marker which bumps `STATE_SUCCESS` → `STATE_SUCCESS_HC` rather
|
||||
/// than to a neutral gray.
|
||||
pub const fn with_hc(
|
||||
default_color: bevy::prelude::Color,
|
||||
hc_color: bevy::prelude::Color,
|
||||
default_color: Color,
|
||||
hc_color: Color,
|
||||
) -> Self {
|
||||
Self {
|
||||
default_color,
|
||||
|
||||
@@ -551,7 +551,7 @@ fn spawn_win_summary_after_delay(
|
||||
// speed the duration is zero anyway, suppressing the shake.
|
||||
let speed = settings
|
||||
.as_ref()
|
||||
.map_or(solitaire_data::AnimSpeed::Normal, |s| s.0.animation_speed);
|
||||
.map_or(AnimSpeed::Normal, |s| s.0.animation_speed);
|
||||
let scaled = scaled_duration(SHAKE_DURATION_SECS, speed);
|
||||
shake.remaining = scaled;
|
||||
shake.total = scaled;
|
||||
|
||||
Reference in New Issue
Block a user