feat(engine): restructure HUD into 4-tier layout, adopt design tokens
Phase 3 step 2 of the UX overhaul. Closes the player's #1 complaint ("HUD too cluttered") by regrouping the 10 readouts that previously sat in a single dense horizontal row. HUD structure (top → bottom): - Tier 1 (always on) Score · Moves · Timer Score uses TYPE_HEADLINE so it's the visual protagonist; Moves/Timer use TYPE_BODY_LG with TEXT_SECONDARY tone. - Tier 2 (mode context) Mode · Daily-challenge constraint · Draw-cycle indicator. Each cell is empty when not relevant — the row collapses visually if all are empty. - Tier 3 (penalty / bonus) Undos · Recycles · Auto-complete badge. Both penalty counters now share STATE_WARNING — the audit found Undos were amber but Recycles were white, making the "you took a penalty" signal inconsistent. - Tier 4 (selection chip) keyboard-driven pile selector. Action bar polish: - Each button gains a TYPE_CAPTION hotkey-hint chip (Undo · U, Pause · Esc, Help · F1, New Game · N). Menu and Modes get no chip because each row in their popovers carries its own hotkey. - Buttons recoloured to BG_ELEVATED / BG_ELEVATED_HI / BG_ELEVATED_PRESSED — the bright-blue palette stood out from the rest of the (still-to-come) midnight purple chrome. - Buttons gain a BORDER_SUBTLE outline so the boundary reads even when hovered over the felt. Other migrations in this commit: - Popover panels (Menu, Modes) now use BG_ELEVATED instead of an ad-hoc dark grey. - challenge_time_color now returns STATE_DANGER / STATE_WARNING / STATE_INFO tokens instead of literal hexes; tests updated. - The Undos in-place colour toggle uses TEXT_PRIMARY / STATE_WARNING. The four `ui_theme` self-tests plus all existing 791 tests stay green (795 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,12 @@ use crate::auto_complete_plugin::AutoCompleteState;
|
|||||||
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
|
||||||
use crate::daily_challenge_plugin::DailyChallengeResource;
|
use crate::daily_challenge_plugin::DailyChallengeResource;
|
||||||
use crate::progress_plugin::ProgressResource;
|
use crate::progress_plugin::ProgressResource;
|
||||||
|
use crate::ui_theme::{
|
||||||
|
ACCENT_PRIMARY, ACCENT_SECONDARY, BG_ELEVATED, BG_ELEVATED_HI, BG_ELEVATED_PRESSED,
|
||||||
|
BORDER_SUBTLE, RADIUS_MD, STATE_DANGER, STATE_INFO, STATE_SUCCESS, STATE_WARNING,
|
||||||
|
TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY, TYPE_BODY_LG, TYPE_CAPTION, TYPE_HEADLINE,
|
||||||
|
VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_3,
|
||||||
|
};
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
HelpRequestEvent, InfoToastEvent, NewGameRequestEvent, PauseRequestEvent,
|
HelpRequestEvent, InfoToastEvent, NewGameRequestEvent, PauseRequestEvent,
|
||||||
StartChallengeRequestEvent, StartDailyChallengeRequestEvent, StartTimeAttackRequestEvent,
|
StartChallengeRequestEvent, StartDailyChallengeRequestEvent, StartTimeAttackRequestEvent,
|
||||||
@@ -167,12 +173,15 @@ pub enum MenuOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// HUD Z-layer — above cards (which start at z=0) but below overlay screens.
|
/// HUD Z-layer — above cards (which start at z=0) but below overlay screens.
|
||||||
const Z_HUD: i32 = 50;
|
/// Mirrors `ui_theme::Z_HUD` and is duplicated here only so the hud module
|
||||||
|
/// can use it as a `const` without a non-const expression in `ZIndex(...)`.
|
||||||
|
const Z_HUD: i32 = crate::ui_theme::Z_HUD;
|
||||||
|
|
||||||
/// Idle / hover / pressed colours shared by every action button.
|
/// Idle / hover / pressed colours shared by every action button. Aliased
|
||||||
const ACTION_BTN_IDLE: Color = Color::srgb(0.20, 0.55, 0.85);
|
/// to the theme tokens so the HUD picks up palette changes for free.
|
||||||
const ACTION_BTN_HOVER: Color = Color::srgb(0.28, 0.65, 0.95);
|
const ACTION_BTN_IDLE: Color = BG_ELEVATED;
|
||||||
const ACTION_BTN_PRESSED: Color = Color::srgb(0.15, 0.45, 0.75);
|
const ACTION_BTN_HOVER: Color = BG_ELEVATED_HI;
|
||||||
|
const ACTION_BTN_PRESSED: Color = BG_ELEVATED_PRESSED;
|
||||||
|
|
||||||
/// Renders the in-game HUD: score counter, move counter, elapsed timer, draw-mode indicator, and the auto-complete badge that lights up when the game is solvable without further input.
|
/// Renders the in-game HUD: score counter, move counter, elapsed timer, draw-mode indicator, and the auto-complete badge that lights up when the game is solvable without further input.
|
||||||
pub struct HudPlugin;
|
pub struct HudPlugin;
|
||||||
@@ -219,79 +228,145 @@ impl Plugin for HudPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns the in-game HUD as a 4-tier vertical column anchored to the
|
||||||
|
/// top-left of the play area.
|
||||||
|
///
|
||||||
|
/// Tiers (top to bottom):
|
||||||
|
/// 1. **Primary** — Score (display weight) · Moves · Timer.
|
||||||
|
/// Always visible during gameplay.
|
||||||
|
/// 2. **Mode context** — Mode badge · Daily-challenge constraint ·
|
||||||
|
/// Draw-cycle indicator. Each cell is empty when not relevant; the
|
||||||
|
/// row collapses visually when all cells are empty.
|
||||||
|
/// 3. **Penalty / bonus** — Undos · Recycles · Auto-complete badge.
|
||||||
|
/// Both penalty counters share `STATE_WARNING` (the audit found
|
||||||
|
/// they were inconsistent: Undos amber, Recycles white).
|
||||||
|
/// 4. **Selection** — keyboard-driven pile selector chip.
|
||||||
|
///
|
||||||
|
/// The audit identified the original single-row layout (10 readouts in
|
||||||
|
/// one horizontal flex row, 5+ colour families competing) as the
|
||||||
|
/// player's #1 complaint. This restructure groups by purpose, lets
|
||||||
|
/// transient items disappear cleanly, and uses the typography scale to
|
||||||
|
/// make Score the visual protagonist.
|
||||||
fn spawn_hud(font_res: Option<Res<FontResource>>, mut commands: Commands) {
|
fn spawn_hud(font_res: Option<Res<FontResource>>, mut commands: Commands) {
|
||||||
let white = TextColor(Color::srgba(1.0, 1.0, 1.0, 0.80));
|
let font_handle = font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default();
|
||||||
let font = TextFont {
|
let font_score = TextFont {
|
||||||
font: font_res.as_ref().map(|f| f.0.clone()).unwrap_or_default(),
|
font: font_handle.clone(),
|
||||||
font_size: 18.0,
|
font_size: TYPE_HEADLINE,
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
let font_lg = TextFont {
|
||||||
|
font: font_handle.clone(),
|
||||||
|
font_size: TYPE_BODY_LG,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
let font_body = TextFont {
|
||||||
|
font: font_handle,
|
||||||
|
font_size: TYPE_BODY,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let row_node = || Node {
|
||||||
|
flex_direction: FlexDirection::Row,
|
||||||
|
column_gap: VAL_SPACE_3,
|
||||||
|
align_items: AlignItems::Baseline,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
left: Val::Px(12.0),
|
left: VAL_SPACE_3,
|
||||||
top: Val::Px(8.0),
|
top: VAL_SPACE_2,
|
||||||
flex_direction: FlexDirection::Row,
|
flex_direction: FlexDirection::Column,
|
||||||
column_gap: Val::Px(20.0),
|
row_gap: VAL_SPACE_1,
|
||||||
align_items: AlignItems::Center,
|
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
ZIndex(Z_HUD),
|
ZIndex(Z_HUD),
|
||||||
))
|
))
|
||||||
.with_children(|b| {
|
.with_children(|hud| {
|
||||||
b.spawn((HudScore, Text::new("Score: 0"), font.clone(), white));
|
// Tier 1 — primary readouts. Score is the protagonist (HEADLINE);
|
||||||
b.spawn((HudMoves, Text::new("Moves: 0"), font.clone(), white));
|
// Moves and Timer are supporting context (BODY_LG, secondary tone).
|
||||||
b.spawn((HudTime, Text::new("0:00"), font.clone(), white));
|
hud.spawn(row_node()).with_children(|t1| {
|
||||||
b.spawn((
|
t1.spawn((
|
||||||
|
HudScore,
|
||||||
|
Text::new("Score: 0"),
|
||||||
|
font_score.clone(),
|
||||||
|
TextColor(TEXT_PRIMARY),
|
||||||
|
));
|
||||||
|
t1.spawn((
|
||||||
|
HudMoves,
|
||||||
|
Text::new("Moves: 0"),
|
||||||
|
font_lg.clone(),
|
||||||
|
TextColor(TEXT_SECONDARY),
|
||||||
|
));
|
||||||
|
t1.spawn((
|
||||||
|
HudTime,
|
||||||
|
Text::new("0:00"),
|
||||||
|
font_lg.clone(),
|
||||||
|
TextColor(TEXT_SECONDARY),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tier 2 — mode context. Each cell is empty until update_hud
|
||||||
|
// populates it (and clears it when no longer relevant), so the
|
||||||
|
// row collapses when nothing in this tier applies.
|
||||||
|
hud.spawn(row_node()).with_children(|t2| {
|
||||||
|
t2.spawn((
|
||||||
HudMode,
|
HudMode,
|
||||||
Text::new(""),
|
Text::new(""),
|
||||||
TextFont { font_size: 17.0, ..default() },
|
font_body.clone(),
|
||||||
TextColor(Color::srgb(1.0, 0.85, 0.25)),
|
TextColor(ACCENT_PRIMARY),
|
||||||
));
|
));
|
||||||
// Daily-challenge constraint (hidden until a challenge is active).
|
t2.spawn((
|
||||||
b.spawn((
|
|
||||||
HudChallenge,
|
HudChallenge,
|
||||||
Text::new(""),
|
Text::new(""),
|
||||||
TextFont { font_size: 17.0, ..default() },
|
font_body.clone(),
|
||||||
TextColor(Color::srgb(0.4, 0.9, 1.0)),
|
TextColor(STATE_INFO),
|
||||||
));
|
));
|
||||||
// Undo counter (white by default; turns amber when undos are used).
|
t2.spawn((
|
||||||
b.spawn((
|
|
||||||
HudUndos,
|
|
||||||
Text::new(""),
|
|
||||||
font.clone(),
|
|
||||||
white,
|
|
||||||
));
|
|
||||||
// Auto-complete badge (green "AUTO" when sequence is running).
|
|
||||||
b.spawn((
|
|
||||||
HudAutoComplete,
|
|
||||||
Text::new(""),
|
|
||||||
TextFont { font_size: 17.0, ..default() },
|
|
||||||
TextColor(Color::srgb(0.2, 0.9, 0.3)),
|
|
||||||
));
|
|
||||||
// Recycle counter — hidden until the first recycle in either draw mode.
|
|
||||||
b.spawn((
|
|
||||||
HudRecycles,
|
|
||||||
Text::new(""),
|
|
||||||
font.clone(),
|
|
||||||
white,
|
|
||||||
));
|
|
||||||
// Draw-cycle indicator — only visible in Draw-Three mode.
|
|
||||||
b.spawn((
|
|
||||||
HudDrawCycle,
|
HudDrawCycle,
|
||||||
Text::new(""),
|
Text::new(""),
|
||||||
font,
|
font_body.clone(),
|
||||||
TextColor(Color::srgb(0.7, 0.85, 1.0)),
|
TextColor(STATE_INFO),
|
||||||
));
|
));
|
||||||
// Keyboard-selection indicator — shows which pile is Tab-selected.
|
});
|
||||||
b.spawn((
|
|
||||||
|
// Tier 3 — penalty / bonus. Undos and Recycles share the
|
||||||
|
// warning hue so they read as the same category ("you took a
|
||||||
|
// penalty"); the auto-complete badge stays success-green.
|
||||||
|
hud.spawn(row_node()).with_children(|t3| {
|
||||||
|
t3.spawn((
|
||||||
|
HudUndos,
|
||||||
|
Text::new(""),
|
||||||
|
font_body.clone(),
|
||||||
|
TextColor(STATE_WARNING),
|
||||||
|
));
|
||||||
|
t3.spawn((
|
||||||
|
HudRecycles,
|
||||||
|
Text::new(""),
|
||||||
|
font_body.clone(),
|
||||||
|
TextColor(STATE_WARNING),
|
||||||
|
));
|
||||||
|
t3.spawn((
|
||||||
|
HudAutoComplete,
|
||||||
|
Text::new(""),
|
||||||
|
font_body.clone(),
|
||||||
|
TextColor(STATE_SUCCESS),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tier 4 — selection chip. Stays in HUD for now; a future
|
||||||
|
// pass can reposition it next to the selected pile.
|
||||||
|
hud.spawn(row_node()).with_children(|t4| {
|
||||||
|
t4.spawn((
|
||||||
HudSelection,
|
HudSelection,
|
||||||
Text::new(""),
|
Text::new(""),
|
||||||
TextFont { font_size: 17.0, ..default() },
|
font_body,
|
||||||
TextColor(Color::srgb(1.0, 1.0, 0.5)),
|
TextColor(ACCENT_SECONDARY),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns the action button bar anchored to the top-right of the window.
|
/// Spawns the action button bar anchored to the top-right of the window.
|
||||||
@@ -322,12 +397,15 @@ fn spawn_action_buttons(font_res: Option<Res<FontResource>>, mut commands: Comma
|
|||||||
ZIndex(Z_HUD),
|
ZIndex(Z_HUD),
|
||||||
))
|
))
|
||||||
.with_children(|row| {
|
.with_children(|row| {
|
||||||
spawn_action_button(row, MenuButton, "Menu \u{25BE}", &font);
|
// Menu and Modes don't have a single hotkey accelerator
|
||||||
spawn_action_button(row, UndoButton, "Undo", &font);
|
// (each row inside their popover has its own); their button
|
||||||
spawn_action_button(row, PauseButton, "Pause", &font);
|
// labels carry the dropdown chevron in lieu of a key chip.
|
||||||
spawn_action_button(row, HelpButton, "Help", &font);
|
spawn_action_button(row, MenuButton, "Menu \u{25BE}", None, &font);
|
||||||
spawn_action_button(row, ModesButton, "Modes \u{25BE}", &font);
|
spawn_action_button(row, UndoButton, "Undo", Some("U"), &font);
|
||||||
spawn_action_button(row, NewGameButton, "New Game", &font);
|
spawn_action_button(row, PauseButton, "Pause", Some("Esc"), &font);
|
||||||
|
spawn_action_button(row, HelpButton, "Help", Some("F1"), &font);
|
||||||
|
spawn_action_button(row, ModesButton, "Modes \u{25BE}", None, &font);
|
||||||
|
spawn_action_button(row, NewGameButton, "New Game", Some("N"), &font);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,23 +416,37 @@ fn spawn_action_button<M: Component>(
|
|||||||
row: &mut ChildSpawnerCommands,
|
row: &mut ChildSpawnerCommands,
|
||||||
marker: M,
|
marker: M,
|
||||||
label: &str,
|
label: &str,
|
||||||
|
hotkey: Option<&'static str>,
|
||||||
font: &TextFont,
|
font: &TextFont,
|
||||||
) {
|
) {
|
||||||
|
let hotkey_font = TextFont {
|
||||||
|
font: font.font.clone(),
|
||||||
|
font_size: TYPE_CAPTION,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
row.spawn((
|
row.spawn((
|
||||||
marker,
|
marker,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
Button,
|
Button,
|
||||||
Node {
|
Node {
|
||||||
padding: UiRect::axes(Val::Px(14.0), Val::Px(8.0)),
|
padding: UiRect::axes(VAL_SPACE_3, VAL_SPACE_2),
|
||||||
justify_content: JustifyContent::Center,
|
justify_content: JustifyContent::Center,
|
||||||
align_items: AlignItems::Center,
|
align_items: AlignItems::Center,
|
||||||
border_radius: BorderRadius::all(Val::Px(6.0)),
|
border_radius: BorderRadius::all(Val::Px(RADIUS_MD)),
|
||||||
|
column_gap: VAL_SPACE_2,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BackgroundColor(ACTION_BTN_IDLE),
|
BackgroundColor(ACTION_BTN_IDLE),
|
||||||
|
BorderColor::all(BORDER_SUBTLE),
|
||||||
))
|
))
|
||||||
.with_children(|b| {
|
.with_children(|b| {
|
||||||
b.spawn((Text::new(label), font.clone(), TextColor(Color::WHITE)));
|
b.spawn((Text::new(label), font.clone(), TextColor(TEXT_PRIMARY)));
|
||||||
|
if let Some(key) = hotkey {
|
||||||
|
// Hotkey hint rendered as a dim caption next to the label —
|
||||||
|
// keeps the keyboard accelerator discoverable without
|
||||||
|
// hijacking the button's primary affordance.
|
||||||
|
b.spawn((Text::new(key), hotkey_font, TextColor(TEXT_SECONDARY)));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +568,7 @@ fn spawn_modes_popover(
|
|||||||
border_radius: BorderRadius::all(Val::Px(6.0)),
|
border_radius: BorderRadius::all(Val::Px(6.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BackgroundColor(Color::srgba(0.10, 0.12, 0.15, 0.96)),
|
BackgroundColor(BG_ELEVATED),
|
||||||
ZIndex(Z_HUD + 5),
|
ZIndex(Z_HUD + 5),
|
||||||
))
|
))
|
||||||
.with_children(|panel| {
|
.with_children(|panel| {
|
||||||
@@ -497,7 +589,7 @@ fn spawn_modes_popover(
|
|||||||
BackgroundColor(ACTION_BTN_IDLE),
|
BackgroundColor(ACTION_BTN_IDLE),
|
||||||
))
|
))
|
||||||
.with_children(|b| {
|
.with_children(|b| {
|
||||||
b.spawn((Text::new(label), font.clone(), TextColor(Color::WHITE)));
|
b.spawn((Text::new(label), font.clone(), TextColor(TEXT_PRIMARY)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -605,7 +697,7 @@ fn spawn_menu_popover(commands: &mut Commands, font_res: Option<&FontResource>)
|
|||||||
border_radius: BorderRadius::all(Val::Px(6.0)),
|
border_radius: BorderRadius::all(Val::Px(6.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BackgroundColor(Color::srgba(0.10, 0.12, 0.15, 0.96)),
|
BackgroundColor(BG_ELEVATED),
|
||||||
ZIndex(Z_HUD + 5),
|
ZIndex(Z_HUD + 5),
|
||||||
))
|
))
|
||||||
.with_children(|panel| {
|
.with_children(|panel| {
|
||||||
@@ -626,7 +718,7 @@ fn spawn_menu_popover(commands: &mut Commands, font_res: Option<&FontResource>)
|
|||||||
BackgroundColor(ACTION_BTN_IDLE),
|
BackgroundColor(ACTION_BTN_IDLE),
|
||||||
))
|
))
|
||||||
.with_children(|b| {
|
.with_children(|b| {
|
||||||
b.spawn((Text::new(label), font.clone(), TextColor(Color::WHITE)));
|
b.spawn((Text::new(label), font.clone(), TextColor(TEXT_PRIMARY)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -679,6 +771,7 @@ fn handle_menu_option_click(
|
|||||||
/// states by mutating `BackgroundColor` whenever the interaction state
|
/// states by mutating `BackgroundColor` whenever the interaction state
|
||||||
/// changes. One query covers all action buttons via the shared
|
/// changes. One query covers all action buttons via the shared
|
||||||
/// `ActionButton` marker.
|
/// `ActionButton` marker.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn paint_action_buttons(
|
fn paint_action_buttons(
|
||||||
mut buttons: Query<
|
mut buttons: Query<
|
||||||
(&Interaction, &mut BackgroundColor),
|
(&Interaction, &mut BackgroundColor),
|
||||||
@@ -894,11 +987,12 @@ fn update_hud(
|
|||||||
let count = g.undo_count;
|
let count = g.undo_count;
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
**t = String::new();
|
**t = String::new();
|
||||||
*color = TextColor(Color::srgba(1.0, 1.0, 1.0, 0.80));
|
*color = TextColor(TEXT_PRIMARY);
|
||||||
} else {
|
} else {
|
||||||
**t = format!("Undos: {count}");
|
**t = format!("Undos: {count}");
|
||||||
// Amber warning: using undo blocks the no-undo achievement.
|
// STATE_WARNING signals "you took a penalty" — same hue
|
||||||
*color = TextColor(Color::srgb(1.0, 0.7, 0.2));
|
// as the Recycles counter so they read as one category.
|
||||||
|
*color = TextColor(STATE_WARNING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1025,20 +1119,22 @@ fn challenge_hud_text(dc: &DailyChallengeResource) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the colour for the challenge time-limit HUD label based on seconds remaining.
|
/// Returns the colour for the challenge time-limit HUD label based on
|
||||||
|
/// seconds remaining. Uses theme tokens so the urgency ramp picks up
|
||||||
|
/// palette changes for free.
|
||||||
///
|
///
|
||||||
/// | Remaining | Colour |
|
/// | Remaining | Token |
|
||||||
/// |-------------|--------|
|
/// |-------------|------------------|
|
||||||
/// | ≥ 60 s | Cyan (default) |
|
/// | ≥ 60 s | `STATE_INFO` |
|
||||||
/// | 30 – 59 s | Orange (warning) |
|
/// | 30 – 59 s | `STATE_WARNING` |
|
||||||
/// | < 30 s | Red (urgent) |
|
/// | < 30 s | `STATE_DANGER` |
|
||||||
pub fn challenge_time_color(remaining: u64) -> Color {
|
pub fn challenge_time_color(remaining: u64) -> Color {
|
||||||
if remaining < 30 {
|
if remaining < 30 {
|
||||||
Color::srgb(1.0, 0.2, 0.2)
|
STATE_DANGER
|
||||||
} else if remaining < 60 {
|
} else if remaining < 60 {
|
||||||
Color::srgb(1.0, 0.6, 0.0)
|
STATE_WARNING
|
||||||
} else {
|
} else {
|
||||||
Color::srgb(0.4, 0.9, 1.0)
|
STATE_INFO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1189,39 +1285,39 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn challenge_time_color_above_60_is_cyan() {
|
fn challenge_time_color_above_60_is_info() {
|
||||||
let c = challenge_time_color(61);
|
let c = challenge_time_color(61);
|
||||||
assert_eq!(c, Color::srgb(0.4, 0.9, 1.0));
|
assert_eq!(c, STATE_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn challenge_time_color_exactly_60_is_cyan() {
|
fn challenge_time_color_exactly_60_is_info() {
|
||||||
let c = challenge_time_color(60);
|
let c = challenge_time_color(60);
|
||||||
assert_eq!(c, Color::srgb(0.4, 0.9, 1.0));
|
assert_eq!(c, STATE_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn challenge_time_color_59_is_orange() {
|
fn challenge_time_color_59_is_warning() {
|
||||||
let c = challenge_time_color(59);
|
let c = challenge_time_color(59);
|
||||||
assert_eq!(c, Color::srgb(1.0, 0.6, 0.0));
|
assert_eq!(c, STATE_WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn challenge_time_color_30_is_orange() {
|
fn challenge_time_color_30_is_warning() {
|
||||||
let c = challenge_time_color(30);
|
let c = challenge_time_color(30);
|
||||||
assert_eq!(c, Color::srgb(1.0, 0.6, 0.0));
|
assert_eq!(c, STATE_WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn challenge_time_color_29_is_red() {
|
fn challenge_time_color_29_is_danger() {
|
||||||
let c = challenge_time_color(29);
|
let c = challenge_time_color(29);
|
||||||
assert_eq!(c, Color::srgb(1.0, 0.2, 0.2));
|
assert_eq!(c, STATE_DANGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn challenge_time_color_zero_is_red() {
|
fn challenge_time_color_zero_is_danger() {
|
||||||
let c = challenge_time_color(0);
|
let c = challenge_time_color(0);
|
||||||
assert_eq!(c, Color::srgb(1.0, 0.2, 0.2));
|
assert_eq!(c, STATE_DANGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user