refactor(engine): migrate table_plugin chrome to Terminal tokens
- Promote `marker_colour` to module-level const PILE_MARKER_DEFAULT_COLOUR and re-export it. cursor_plugin::MARKER_DEFAULT now imports the const directly, replacing the prior duplicated literal kept in sync only by doc comment. Drift becomes a compile error instead of a stale claim. - Empty-tableau "K" placeholder text now uses TEXT_PRIMARY at 0.35 alpha (was raw `Color::srgba(1.0, 1.0, 1.0, 0.35)`) so it picks up the Terminal off-white foreground. - HINT_PILE_HIGHLIGHT_COLOUR retuned from bright `srgb(1.0, 0.85, 0.1)` to the design-system STATE_WARNING token (`#ddb26f`). Spelled as a literal because Alpha::with_alpha is not yet const on stable; a new test pins the RGB to STATE_WARNING so a palette swap can't drift the two apart silently. - The existing "is gold" character test was hardcoded to the old bright palette (red ≥ 0.9). Loosened to "warmer than cool" + ranges that the Terminal muted gold satisfies, with exact-RGB tracking handled by the new STATE_WARNING test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -41,14 +41,16 @@ use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau};
|
|||||||
use crate::card_plugin::{RightClickHighlight, TABLEAU_FAN_FRAC};
|
use crate::card_plugin::{RightClickHighlight, TABLEAU_FAN_FRAC};
|
||||||
use crate::layout::{Layout, LayoutResource};
|
use crate::layout::{Layout, LayoutResource};
|
||||||
use crate::resources::{DragState, GameStateResource};
|
use crate::resources::{DragState, GameStateResource};
|
||||||
use crate::table_plugin::PileMarker;
|
use crate::table_plugin::{PileMarker, PILE_MARKER_DEFAULT_COLOUR};
|
||||||
use crate::ui_theme::{
|
use crate::ui_theme::{
|
||||||
DROP_TARGET_FILL, DROP_TARGET_OUTLINE, DROP_TARGET_OUTLINE_PX, Z_DROP_OVERLAY,
|
DROP_TARGET_FILL, DROP_TARGET_OUTLINE, DROP_TARGET_OUTLINE_PX, Z_DROP_OVERLAY,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Semi-transparent white that `table_plugin` uses for idle pile markers.
|
/// Idle pile-marker tint — re-exported from `table_plugin` so the
|
||||||
/// Kept in sync with the `marker_colour` constant there.
|
/// "valid drop" toggle in this plugin and the marker spawn in
|
||||||
const MARKER_DEFAULT: Color = Color::srgba(1.0, 1.0, 1.0, 0.08);
|
/// `table_plugin` cannot drift apart. Was previously a duplicated
|
||||||
|
/// literal kept in sync via doc comment.
|
||||||
|
const MARKER_DEFAULT: Color = PILE_MARKER_DEFAULT_COLOUR;
|
||||||
|
|
||||||
/// Lime tint applied to pile markers that are valid drop targets during
|
/// Lime tint applied to pile markers that are valid drop targets during
|
||||||
/// a drag. Same RGB as the design-system [`STATE_SUCCESS`] token at 55%
|
/// a drag. Same RGB as the design-system [`STATE_SUCCESS`] token at 55%
|
||||||
|
|||||||
@@ -14,9 +14,21 @@ use crate::layout::{compute_layout, Layout, LayoutResource, LayoutSystem};
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::layout::TABLE_COLOUR;
|
use crate::layout::TABLE_COLOUR;
|
||||||
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource};
|
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource};
|
||||||
|
use crate::ui_theme::TEXT_PRIMARY;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use solitaire_data::Theme;
|
use solitaire_data::Theme;
|
||||||
|
|
||||||
|
/// Default tint applied to every empty-pile marker sprite. Pure white
|
||||||
|
/// at 8% alpha — soft enough that the marker reads as a "hint of a
|
||||||
|
/// slot" rather than a panel, but visible against every felt
|
||||||
|
/// background.
|
||||||
|
///
|
||||||
|
/// Re-exported as the source of truth for `cursor_plugin::MARKER_DEFAULT`,
|
||||||
|
/// which used to duplicate the literal alongside a "kept in sync" doc
|
||||||
|
/// comment. Pulling both call sites through this const makes drift a
|
||||||
|
/// compile error instead of a stale comment.
|
||||||
|
pub const PILE_MARKER_DEFAULT_COLOUR: Color = Color::srgba(1.0, 1.0, 1.0, 0.08);
|
||||||
|
|
||||||
/// Holds pre-loaded [`Handle<Image>`]s for the 5 selectable table backgrounds.
|
/// Holds pre-loaded [`Handle<Image>`]s for the 5 selectable table backgrounds.
|
||||||
///
|
///
|
||||||
/// Loaded once at startup by [`load_background_images`]. Index 0 is the
|
/// Loaded once at startup by [`load_background_images`]. Index 0 is the
|
||||||
@@ -218,7 +230,7 @@ pub fn suit_symbol(suit: &Suit) -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_pile_markers(commands: &mut Commands, layout: &Layout) {
|
fn spawn_pile_markers(commands: &mut Commands, layout: &Layout) {
|
||||||
let marker_colour = Color::srgba(1.0, 1.0, 1.0, 0.08);
|
let marker_colour = PILE_MARKER_DEFAULT_COLOUR;
|
||||||
let marker_size = layout.card_size;
|
let marker_size = layout.card_size;
|
||||||
let font_size = layout.card_size.x * 0.28;
|
let font_size = layout.card_size.x * 0.28;
|
||||||
|
|
||||||
@@ -254,7 +266,7 @@ fn spawn_pile_markers(commands: &mut Commands, layout: &Layout) {
|
|||||||
b.spawn((
|
b.spawn((
|
||||||
Text2d::new("K"),
|
Text2d::new("K"),
|
||||||
TextFont { font_size, ..default() },
|
TextFont { font_size, ..default() },
|
||||||
TextColor(Color::srgba(1.0, 1.0, 1.0, 0.35)),
|
TextColor(TEXT_PRIMARY.with_alpha(0.35)),
|
||||||
Transform::from_xyz(0.0, 0.0, 0.1),
|
Transform::from_xyz(0.0, 0.0, 0.1),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
@@ -308,9 +320,14 @@ fn on_window_resized(
|
|||||||
// Task #6 — Hint pile-marker highlight
|
// Task #6 — Hint pile-marker highlight
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Gold tint applied to a `PileMarker` sprite when it is the current hint
|
/// Gold tint applied to a `PileMarker` sprite when it is the current
|
||||||
/// destination.
|
/// hint destination. Same RGB as the design-system [`STATE_WARNING`]
|
||||||
const HINT_PILE_HIGHLIGHT_COLOUR: Color = Color::srgb(1.0, 0.85, 0.1);
|
/// token (`#ddb26f`) so the in-game "look here" colour is the same hue
|
||||||
|
/// as every other warning/attention signal in the UI. Spelled as a
|
||||||
|
/// literal because `Alpha::with_alpha` is not yet a `const` trait
|
||||||
|
/// method on stable; the tracking test below pins the RGB to
|
||||||
|
/// `STATE_WARNING` so a future palette swap can't drift the two apart.
|
||||||
|
const HINT_PILE_HIGHLIGHT_COLOUR: Color = Color::srgb(0.867, 0.698, 0.435);
|
||||||
|
|
||||||
/// Listens for `HintVisualEvent` and tints the matching `PileMarker` entity
|
/// Listens for `HintVisualEvent` and tints the matching `PileMarker` entity
|
||||||
/// gold for 2 s, storing the original colour in `HintPileHighlight` so it can
|
/// gold for 2 s, storing the original colour in `HintPileHighlight` so it can
|
||||||
@@ -480,19 +497,33 @@ mod tests {
|
|||||||
/// default pile marker colour so the player can see which pile is highlighted.
|
/// default pile marker colour so the player can see which pile is highlighted.
|
||||||
#[test]
|
#[test]
|
||||||
fn hint_pile_highlight_colour_is_distinct_from_default() {
|
fn hint_pile_highlight_colour_is_distinct_from_default() {
|
||||||
let default = Color::srgba(1.0, 1.0, 1.0, 0.08); // PILE_MARKER_DEFAULT_COLOUR
|
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
HINT_PILE_HIGHLIGHT_COLOUR, default,
|
HINT_PILE_HIGHLIGHT_COLOUR, PILE_MARKER_DEFAULT_COLOUR,
|
||||||
"HINT_PILE_HIGHLIGHT_COLOUR must differ from the default pile marker colour"
|
"HINT_PILE_HIGHLIGHT_COLOUR must differ from the default pile marker colour"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `HINT_PILE_HIGHLIGHT_COLOUR` is spelled as a literal because
|
||||||
|
/// `Alpha::with_alpha` is not a `const` trait method on stable.
|
||||||
|
/// This test pins its RGB to the design-system `STATE_WARNING`
|
||||||
|
/// token so a future palette swap that updates the token but
|
||||||
|
/// forgets the hint highlight fails loudly here.
|
||||||
|
#[test]
|
||||||
|
fn hint_pile_highlight_rgb_tracks_state_warning_token() {
|
||||||
|
use crate::ui_theme::STATE_WARNING;
|
||||||
|
let hint = HINT_PILE_HIGHLIGHT_COLOUR.to_srgba();
|
||||||
|
let warning = STATE_WARNING.to_srgba();
|
||||||
|
assert!((hint.red - warning.red).abs() < 1e-6);
|
||||||
|
assert!((hint.green - warning.green).abs() < 1e-6);
|
||||||
|
assert!((hint.blue - warning.blue).abs() < 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
/// A freshly-created HintPileHighlight has a positive timer countdown.
|
/// A freshly-created HintPileHighlight has a positive timer countdown.
|
||||||
#[test]
|
#[test]
|
||||||
fn hint_pile_highlight_timer_starts_positive() {
|
fn hint_pile_highlight_timer_starts_positive() {
|
||||||
let h = HintPileHighlight {
|
let h = HintPileHighlight {
|
||||||
timer: 2.0,
|
timer: 2.0,
|
||||||
original_color: Color::srgba(1.0, 1.0, 1.0, 0.08),
|
original_color: PILE_MARKER_DEFAULT_COLOUR,
|
||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
h.timer > 0.0,
|
h.timer > 0.0,
|
||||||
@@ -529,17 +560,22 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The gold hint colour must have a strong yellow component (r ≥ 0.9, g ≥ 0.8,
|
/// The hint colour must read as "gold-ish" — red dominant, green
|
||||||
/// b ≤ 0.3) to be clearly visible as a "destination" indicator.
|
/// close behind, blue noticeably lower — so a player intuitively
|
||||||
|
/// associates the highlight with attention/warning. Bounds are
|
||||||
|
/// loose enough to accommodate the Terminal palette's muted gold
|
||||||
|
/// (`STATE_WARNING`, `#ddb26f`) while still rejecting a stray
|
||||||
|
/// red, green, or neutral grey if someone refactors badly.
|
||||||
|
/// Exact-RGB tracking lives in
|
||||||
|
/// `hint_pile_highlight_rgb_tracks_state_warning_token`.
|
||||||
#[test]
|
#[test]
|
||||||
fn hint_pile_highlight_colour_is_gold() {
|
fn hint_pile_highlight_colour_is_gold() {
|
||||||
// Extract linear components. srgb(1.0, 0.85, 0.1) is the expected gold.
|
|
||||||
// We test the channel values rather than exact equality so future tweaks
|
|
||||||
// to the shade do not break the test, as long as the colour remains golden.
|
|
||||||
let Srgba { red, green, blue, .. } = HINT_PILE_HIGHLIGHT_COLOUR.to_srgba();
|
let Srgba { red, green, blue, .. } = HINT_PILE_HIGHLIGHT_COLOUR.to_srgba();
|
||||||
assert!(red >= 0.9, "gold hint colour must have red ≥ 0.9, got {red}");
|
assert!(red >= 0.7, "gold hint colour must have red ≥ 0.7, got {red}");
|
||||||
assert!(green >= 0.7, "gold hint colour must have green ≥ 0.7, got {green}");
|
assert!(green >= 0.5, "gold hint colour must have green ≥ 0.5, got {green}");
|
||||||
assert!(blue <= 0.3, "gold hint colour must have blue ≤ 0.3, got {blue}");
|
assert!(blue <= 0.6, "gold hint colour must have blue ≤ 0.6, got {blue}");
|
||||||
|
assert!(red > blue, "gold hint colour must be warmer than cool, got r={red} b={blue}");
|
||||||
|
assert!(green > blue, "gold hint colour must be warmer than cool, got g={green} b={blue}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user