feat(engine): restyle replay progress text as Terminal MOVE chip

Closes the centre-text half of the replay-overlay enrichments arc.
The plain "Move N of M" text becomes a 1px ACCENT_PRIMARY-bordered
chip containing "MOVE N/M" — uppercase + slash separator reads as a
Terminal output line and matches the floating-chip motif in
docs/ui-mockups/replay-overlay-mobile.html. The chip lives in-banner
rather than floating above the focused card; the screen-takeover
treatment that requires plumbing cursor → card identity remains
deferred per SESSION_HANDOFF.

Implementation: the centre Text spawn is now wrapped in a Node with
1px border + axes(VAL_SPACE_2, VAL_SPACE_1) padding and no background
fill (Terminal aesthetic gets depth from borders + tonal layering,
not shadows). The ReplayOverlayProgressText marker stays on the
inner Text so update_progress_text continues to repaint contents
unchanged. format_progress now returns "MOVE N/M" for Playing and
"REPLAY COMPLETE" for Completed (uppercase to match the chip's
typographic treatment); Inactive still returns "" since the overlay
shouldn't be spawned in that state.

Used BorderColor::all(ACCENT_PRIMARY) — Bevy's BorderColor is per-side
in 0.18, no longer the tuple struct it was earlier.

Module-level docstring + ReplayOverlayScrubFill doc comment both
updated to quote the new "MOVE N/M" string. Test
overlay_progress_text_reflects_cursor swapped its assertion to match.
1182 tests still pass; clippy clean.

This closes Option C from the SESSION_HANDOFF Resume prompt's banner-
local enrichments. The full screen-takeover redesign (mini-tableau,
playback controls, move-log scroll, WIN MOVE marker requiring a
win_move_index field on Replay) remains the multi-session item.
This commit is contained in:
funman300
2026-05-07 22:22:36 -07:00
parent 54005d5494
commit e080b49914
+28 -10
View File
@@ -4,8 +4,9 @@
//!
//! - A "▌ replay" label on the left so the player knows the surface is
//! under playback control rather than live input.
//! - A "Move N of M" progress indicator in the centre, recomputed every
//! frame the cursor advances.
//! - A "MOVE N/M" progress chip in the centre, recomputed every frame
//! the cursor advances and bordered in cyan ACCENT_PRIMARY so it
//! reads as a discrete callout.
//! - A "Stop" button on the right that aborts playback and returns
//! control to the player.
//!
@@ -30,7 +31,7 @@ use crate::replay_playback::{stop_replay_playback, ReplayPlaybackState};
use crate::ui_modal::{spawn_modal_button, ButtonVariant};
use crate::ui_theme::{
ACCENT_PRIMARY, BG_ELEVATED_HI, BORDER_SUBTLE, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY,
TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_2, VAL_SPACE_4, Z_DROP_OVERLAY,
TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_4, Z_DROP_OVERLAY,
};
// ---------------------------------------------------------------------------
@@ -110,7 +111,7 @@ pub struct ReplayOverlayGameCaption;
/// continuous visual cue of how far through the replay they are.
///
/// Distinct from the simpler text-based `ReplayOverlayProgressText`
/// (which spells out "Move N of M"): the scrub fill gives immediate
/// (which spells out "MOVE N/M" in a chip): the scrub fill gives immediate
/// at-a-glance positioning; the text gives the exact numbers. Both
/// surfaces stay together because they answer the same question for
/// players with different scanning preferences.
@@ -284,10 +285,23 @@ fn spawn_overlay(
));
});
// Centre: progress readout — neutral primary text
// colour so the eye treats it as data, not a
// callout.
// Centre: progress readout, wrapped in a 1 px
// ACCENT_PRIMARY-bordered chip so it reads as a
// discrete callout rather than free-floating
// text. No fill — the Terminal aesthetic gets
// depth from borders + tonal layering, not
// shadows. The marker stays on the inner Text so
// `update_progress_text` keeps working unchanged.
row.spawn((
Node {
border: UiRect::all(Val::Px(1.0)),
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
..default()
},
BorderColor::all(ACCENT_PRIMARY),
))
.with_children(|chip| {
chip.spawn((
ReplayOverlayProgressText,
Text::new(progress_label),
TextFont {
@@ -297,6 +311,7 @@ fn spawn_overlay(
},
TextColor(TEXT_PRIMARY),
));
});
// Right: Stop button. Tertiary variant — the
// action is available but not the loudest element
@@ -452,8 +467,11 @@ fn format_game_caption(state: &ReplayPlaybackState) -> Option<String> {
/// path produce the exact same string.
fn format_progress(state: &ReplayPlaybackState) -> String {
match state.progress() {
Some((cursor, total)) => format!("Move {cursor} of {total}"),
None if state.is_completed() => "Replay complete".to_string(),
// `MOVE N/M` (uppercase + slash) reads as a Terminal output
// line and matches the floating-chip motif in the mockup at
// `docs/ui-mockups/replay-overlay-mobile.html`.
Some((cursor, total)) => format!("MOVE {cursor}/{total}"),
None if state.is_completed() => "REPLAY COMPLETE".to_string(),
None => String::new(),
}
}
@@ -604,7 +622,7 @@ mod tests {
);
app.update();
assert_eq!(progress_text(&mut app), "Move 5 of 10");
assert_eq!(progress_text(&mut app), "MOVE 5/10");
}
/// Pressing the Stop button resets the state back to `Inactive` and