refactor(engine): route gameplay-feedback colours through Terminal tokens
Selection-highlight tints in selection_plugin and the valid-drop
marker tint in cursor_plugin were hand-tuned RGB literals from the
prior Premium-Solitaire palette. Migrate them to the semantic
state tokens introduced in ui_theme:
- keyboard-drag source highlight (picking) → ACCENT_PRIMARY
- keyboard-drag source highlight (lifted) → STATE_WARNING
- keyboard-drag destination highlight → STATE_SUCCESS
- cursor_plugin::MARKER_VALID → STATE_SUCCESS @ 0.55α
`MARKER_VALID` stays a Color literal (Alpha::with_alpha is not yet
const on stable); a new tracking test pins its RGB to STATE_SUCCESS
so a future palette swap can't drift the two apart silently.
Also fix three stale doc comments in ui_modal that still described
the previous yellow / magenta palette ("Loud yellow CTA",
"Primary swaps to the magenta secondary accent"). Cyan and lavender
now, matching the actual token values.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -50,8 +50,17 @@ use crate::ui_theme::{
|
|||||||
/// Kept in sync with the `marker_colour` constant there.
|
/// Kept in sync with the `marker_colour` constant there.
|
||||||
const MARKER_DEFAULT: Color = Color::srgba(1.0, 1.0, 1.0, 0.08);
|
const MARKER_DEFAULT: Color = Color::srgba(1.0, 1.0, 1.0, 0.08);
|
||||||
|
|
||||||
/// Green tint applied to pile markers that are valid drop targets during drag.
|
/// Lime tint applied to pile markers that are valid drop targets during
|
||||||
const MARKER_VALID: Color = Color::srgba(0.15, 0.85, 0.25, 0.55);
|
/// a drag. Same RGB as the design-system [`STATE_SUCCESS`] token at 55%
|
||||||
|
/// alpha, so the in-game "this is a legal target" colour stays
|
||||||
|
/// consistent with foundation-completion flourishes and other
|
||||||
|
/// valid-move signals. 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_SUCCESS` so a palette swap can't drift
|
||||||
|
/// the two apart silently. Distinct from [`DROP_TARGET_FILL`] (10%
|
||||||
|
/// alpha) because the marker sprite is thin and would otherwise wash
|
||||||
|
/// out at a similar opacity.
|
||||||
|
const MARKER_VALID: Color = Color::srgba(0.675, 0.761, 0.404, 0.55);
|
||||||
|
|
||||||
/// Marker component on a parent entity that owns one drop-target overlay
|
/// Marker component on a parent entity that owns one drop-target overlay
|
||||||
/// (a translucent fill plus four outline edges as children). The wrapped
|
/// (a translucent fill plus four outline edges as children). The wrapped
|
||||||
@@ -524,6 +533,22 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn marker_valid_rgb_tracks_state_success_token() {
|
||||||
|
// `MARKER_VALID` is spelled as a literal because
|
||||||
|
// `Alpha::with_alpha` is not a `const` trait method on stable.
|
||||||
|
// This test pins its RGB to `STATE_SUCCESS` so a future
|
||||||
|
// palette swap that updates the token but forgets the marker
|
||||||
|
// fails loudly here.
|
||||||
|
use crate::ui_theme::STATE_SUCCESS;
|
||||||
|
let valid = MARKER_VALID.to_srgba();
|
||||||
|
let success = STATE_SUCCESS.to_srgba();
|
||||||
|
assert!((valid.red - success.red).abs() < 1e-6);
|
||||||
|
assert!((valid.green - success.green).abs() < 1e-6);
|
||||||
|
assert!((valid.blue - success.blue).abs() < 1e-6);
|
||||||
|
assert!((valid.alpha - 0.55).abs() < 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// pick_cursor_icon priority-order tests
|
// pick_cursor_icon priority-order tests
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ use crate::input_plugin::{best_destination, best_tableau_destination_for_stack};
|
|||||||
use crate::layout::LayoutResource;
|
use crate::layout::LayoutResource;
|
||||||
use crate::pause_plugin::PausedResource;
|
use crate::pause_plugin::PausedResource;
|
||||||
use crate::resources::{DragState, GameStateResource};
|
use crate::resources::{DragState, GameStateResource};
|
||||||
|
use crate::ui_theme::{ACCENT_PRIMARY, STATE_SUCCESS, STATE_WARNING};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public types
|
// Public types
|
||||||
@@ -660,14 +661,18 @@ fn update_selection_highlight(
|
|||||||
};
|
};
|
||||||
let card_size = layout.0.card_size;
|
let card_size = layout.0.card_size;
|
||||||
|
|
||||||
// Choose colours per mode: cyan in source-pick, gold while lifted.
|
// Highlight tints follow the Terminal palette's semantic state
|
||||||
|
// tokens: cyan focus/selection while picking the source, gold
|
||||||
|
// attention/commitment once the cards are lifted, lime valid-move
|
||||||
|
// tint on the destination. Alphas are kept non-zero so the card
|
||||||
|
// face beneath remains readable through the wash.
|
||||||
let lifted = kbd_drag.is_lifted();
|
let lifted = kbd_drag.is_lifted();
|
||||||
let source_color = if lifted {
|
let source_color = if lifted {
|
||||||
Color::srgba(1.0, 0.84, 0.0, 0.6)
|
STATE_WARNING.with_alpha(0.6)
|
||||||
} else {
|
} else {
|
||||||
Color::srgba(0.0, 1.0, 1.0, 0.5)
|
ACCENT_PRIMARY.with_alpha(0.5)
|
||||||
};
|
};
|
||||||
let dest_color = Color::srgba(0.0, 1.0, 0.4, 0.6);
|
let dest_color = STATE_SUCCESS.with_alpha(0.6);
|
||||||
|
|
||||||
// Resolve the source pile from KeyboardDragState (when lifted) or
|
// Resolve the source pile from KeyboardDragState (when lifted) or
|
||||||
// SelectionState (otherwise). Lifted takes precedence so the gold
|
// SelectionState (otherwise). Lifted takes precedence so the gold
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ pub const MODAL_ENTER_START_SCALE: f32 = 0.96;
|
|||||||
/// Visual emphasis tier applied to a [`ModalButton`].
|
/// Visual emphasis tier applied to a [`ModalButton`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ButtonVariant {
|
pub enum ButtonVariant {
|
||||||
/// Loud yellow CTA — Confirm, Play Again. One per modal; right-aligned.
|
/// Cyan CTA (`ACCENT_PRIMARY`) — Confirm, Play Again, Resume. One per
|
||||||
|
/// modal; right-aligned in the actions row.
|
||||||
Primary,
|
Primary,
|
||||||
/// Mid-emphasis — Cancel, Close, Done.
|
/// Mid-emphasis — Cancel, Close, Done.
|
||||||
Secondary,
|
Secondary,
|
||||||
@@ -332,14 +333,17 @@ pub fn spawn_modal_button<M: Component>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let label_color = match variant {
|
let label_color = match variant {
|
||||||
// Primary buttons sit on the loud yellow accent — dark text on
|
// Primary buttons sit on the cyan accent — `BG_BASE` text on
|
||||||
// top reads well and passes AAA contrast.
|
// top reads well and passes AAA contrast against `#6fc2ef`.
|
||||||
ButtonVariant::Primary => BG_BASE,
|
ButtonVariant::Primary => BG_BASE,
|
||||||
ButtonVariant::Secondary | ButtonVariant::Tertiary => TEXT_PRIMARY,
|
ButtonVariant::Secondary | ButtonVariant::Tertiary => TEXT_PRIMARY,
|
||||||
};
|
};
|
||||||
let caption_color = match variant {
|
let caption_color = match variant {
|
||||||
// Use a slightly muted version of the label colour so the chip
|
// Muted near-black on the cyan Primary so the hotkey chip reads
|
||||||
// reads as a secondary detail without disappearing.
|
// as a secondary detail without disappearing. Deliberately a
|
||||||
|
// pure-black-at-alpha rather than `BG_BASE.with_alpha(...)`:
|
||||||
|
// `BG_BASE` is `#151515` (not 0,0,0), so the alpha-on-cyan
|
||||||
|
// composite would tint slightly cooler than intended here.
|
||||||
ButtonVariant::Primary => Color::srgba(0.0, 0.0, 0.0, 0.55),
|
ButtonVariant::Primary => Color::srgba(0.0, 0.0, 0.0, 0.55),
|
||||||
ButtonVariant::Secondary | ButtonVariant::Tertiary => TEXT_SECONDARY,
|
ButtonVariant::Secondary | ButtonVariant::Tertiary => TEXT_SECONDARY,
|
||||||
};
|
};
|
||||||
@@ -395,9 +399,10 @@ fn hover_bg(variant: ButtonVariant) -> Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pressed-state background colour. Primary swaps to the magenta
|
/// Pressed-state background colour. Primary swaps to the lavender
|
||||||
/// secondary accent for a moment of celebration; Secondary darkens to
|
/// secondary accent (`ACCENT_SECONDARY`, `#e1a3ee`) for a moment of
|
||||||
/// the base elevation; Tertiary darkens further.
|
/// celebration; Secondary darkens to the base elevation; Tertiary
|
||||||
|
/// darkens further to `BG_ELEVATED_PRESSED`.
|
||||||
fn pressed_bg(variant: ButtonVariant) -> Color {
|
fn pressed_bg(variant: ButtonVariant) -> Color {
|
||||||
match variant {
|
match variant {
|
||||||
ButtonVariant::Primary => ACCENT_SECONDARY,
|
ButtonVariant::Primary => ACCENT_SECONDARY,
|
||||||
|
|||||||
Reference in New Issue
Block a user