Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c50eaf81f7 | |||
| b44d2777ec | |||
| 52407e7256 | |||
| da3e5423dc | |||
| a1864271de | |||
| f63db769ae | |||
| 4437a1aaf9 | |||
| e7345aed6c | |||
| 140251beae | |||
| d6f32d3154 | |||
| 8fdc41f36f | |||
| 2e25476d0a | |||
| d3cb1a51d4 | |||
| c8358f4275 | |||
| a2432dfe7a | |||
| 511550232c | |||
| e5c4f51a6e | |||
| 23902cdc44 | |||
| 3cc8eacafa | |||
| 90e24d9711 | |||
| decbe0bbd9 | |||
| 1873b3f9be | |||
| d11d97e677 | |||
| d322abf67b | |||
| c9e4c0b4cd | |||
| fe68861e10 | |||
| c33b39cf11 |
+291
-1
@@ -6,9 +6,299 @@ project follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
No threads in flight. v0.21.4 cut on 2026-05-08; CHANGELOG accumulates
|
No threads in flight. v0.21.7 cut on 2026-05-08; CHANGELOG accumulates
|
||||||
the next cycle here.
|
the next cycle here.
|
||||||
|
|
||||||
|
## [0.21.7] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release closing the last major B-2 sub-piece. Through-line:
|
||||||
|
**mini-tableau preview dim layer**. The mockup's "Game Peek Band at
|
||||||
|
50 % opacity" is now implemented as a full-screen UI scrim that darkens
|
||||||
|
the card world during replay so the chrome (banner + move-log panel)
|
||||||
|
reads clearly against the scene.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Full-screen tableau dim layer** (`da3e542`). Spawns a
|
||||||
|
`ReplayTableauDimLayer` UI node (100 % × 100 %, 50 % opacity
|
||||||
|
black) at `Z_REPLAY_DIM = Z_REPLAY_OVERLAY − 1 = 54` whenever
|
||||||
|
a replay starts; despawned alongside the banner and move-log
|
||||||
|
panel when the replay ends. Bevy's UI/world compositor means
|
||||||
|
no changes to `card_plugin` are needed — UI nodes always
|
||||||
|
render above world-space sprites regardless of `Transform.z`.
|
||||||
|
The dim layer carries no `Interaction` component (purely
|
||||||
|
visual; pointer events pass through). Adds `Z_REPLAY_DIM`
|
||||||
|
and `TABLEAU_DIM_ALPHA` constants plus two new tests:
|
||||||
|
lifecycle (spawn/despawn mirrors the floating-chip pattern)
|
||||||
|
and z-ordering invariant (`Z_REPLAY_DIM < Z_REPLAY_OVERLAY`
|
||||||
|
pinned). 1275 tests pass / 0 failing.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- Tests: 1275 passing / 0 failing
|
||||||
|
- Clippy: clean
|
||||||
|
- Crates touched: `solitaire_engine` (replay_overlay.rs)
|
||||||
|
|
||||||
|
## [0.21.6] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.5 work. Through-line:
|
||||||
|
**Move Log panel + scrub-UX polish**. v0.21.5 closed out the
|
||||||
|
keyboard-accelerator surface (Space / Esc / ← / →) and the
|
||||||
|
keybind footer; v0.21.6 builds on that with two parallel
|
||||||
|
threads — accessibility + scrub-on-hold polish for the v0.21.5
|
||||||
|
surfaces, plus a brand-new Move Log panel anchored to the
|
||||||
|
viewport's bottom edge that gives players a 5-row recent-and-
|
||||||
|
upcoming move history alongside the existing top-edge banner.
|
||||||
|
|
||||||
|
The Move Log panel is the first replay-overlay surface that
|
||||||
|
*isn't* attached to the banner — it lives at a separate screen
|
||||||
|
anchor (bottom: 0) with its own spawn/despawn lifecycle.
|
||||||
|
Establishes the pattern for "multi-anchor replay UI" that the
|
||||||
|
remaining B-2 sub-piece (mini-tableau preview) will inherit.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **HC-mode coverage for the scrub track + quarter-mark notch
|
||||||
|
ticks** (`d3cb1a5`). Adds parallel primitive
|
||||||
|
`HighContrastBackground` to `ui_theme` and a paint system
|
||||||
|
`update_high_contrast_backgrounds` in `settings_plugin` that
|
||||||
|
mirrors the existing border-marker pattern but targets
|
||||||
|
`BackgroundColor` instead of `BorderColor`. Tags the 1 px
|
||||||
|
scrub track Node and all five quarter-mark notch ticks so
|
||||||
|
they bump from `BORDER_SUBTLE` (`#505050`) →
|
||||||
|
`BORDER_SUBTLE_HC` (`#a0a0a0`) under HC mode. Scrub fill
|
||||||
|
(`ACCENT_PRIMARY`) and WIN MOVE marker (`STATE_SUCCESS`)
|
||||||
|
don't get the marker — accent and state colours are already
|
||||||
|
saturated and don't need an HC luminance variant.
|
||||||
|
- **Continuous scrub on key-held arrow keys** (`2e25476`).
|
||||||
|
Holding ← or → triggers continuous step at 100 ms cadence
|
||||||
|
(10 steps/sec) — matches the mockup's `[← →] scrub`
|
||||||
|
terminology while keeping single-press = single-step
|
||||||
|
semantics. Per-key accumulators in a new
|
||||||
|
`ReplayScrubKeyHold` resource; `just_pressed` events bypass
|
||||||
|
the accumulator and fire immediately. Release resets to 0
|
||||||
|
so the next fresh press fires immediately rather than at
|
||||||
|
half-interval.
|
||||||
|
- **Move Log panel** (`d6f32d3` + `140251b` + `e7345ae` +
|
||||||
|
`4437a1a`). New bottom-edge UI panel showing a 5-row window
|
||||||
|
onto recent + upcoming moves: 2 prev rows above the active
|
||||||
|
row + active row highlighted in `ACCENT_PRIMARY` + 2 next
|
||||||
|
rows below. Header reads `▌ MOVE LOG · N/M` (or
|
||||||
|
`▌ MOVE LOG · COMPLETE` when finished). Active row carries
|
||||||
|
a `▶` focus prefix and `TEXT_PRIMARY_HC` text colour for
|
||||||
|
legible contrast against the brick-red highlight. Prev /
|
||||||
|
next rows render in `TEXT_SECONDARY` so the active row
|
||||||
|
stays the focal point.
|
||||||
|
- Sibling-of-banner pattern (separate root entity anchored
|
||||||
|
at viewport bottom, not a banner child) — same
|
||||||
|
spawn/despawn lifecycle as `ReplayFloatingProgressChip`,
|
||||||
|
different screen anchor.
|
||||||
|
- Five pure helpers handle the formatting:
|
||||||
|
`format_pile`, `format_move_body`,
|
||||||
|
`format_move_log_header`, `format_kth_recent_row` (active
|
||||||
|
+ prev), `format_kth_next_row` (next). 1-indexed display
|
||||||
|
numbers throughout (`Foundation(2)` reads as "foundation
|
||||||
|
3" rather than the enum's 0-index).
|
||||||
|
- Panel grows from 56 → 84 → 112 px across the four
|
||||||
|
move-log commits. `MOVE_LOG_PREV_ROWS` and
|
||||||
|
`MOVE_LOG_NEXT_ROWS` constants (both = 2) parameterise
|
||||||
|
the row count; `format_kth_recent_row` and
|
||||||
|
`format_kth_next_row` return empty for out-of-range k so
|
||||||
|
panels gracefully under-fill at the start (cursor=1) and
|
||||||
|
end (cursor=N-1) of a replay.
|
||||||
|
- HC marker on the panel's top border so the 1 px edge
|
||||||
|
bumps under HC mode (same pattern as the keybind footer).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`react_to_state_change` despawns the Move Log panel** on
|
||||||
|
`Playing → Inactive` alongside the banner root and floating
|
||||||
|
progress chip. Third query in the same defer-and-despawn
|
||||||
|
cycle.
|
||||||
|
- **Move Log panel height grew 56 → 84 → 112 px** across the
|
||||||
|
prev-rows and next-rows commits. The panel is sized to fit
|
||||||
|
the chosen row count + header + padding; tunable via the
|
||||||
|
`MOVE_LOG_PANEL_HEIGHT` const.
|
||||||
|
- **`format_active_move_row` now prefixes the `▶` focus
|
||||||
|
marker** (`e7345ae`). Wraps `format_kth_recent_row(state, 1)`
|
||||||
|
and prepends the prefix when the body is non-empty. Empty
|
||||||
|
case still returns empty — cursor=0 doesn't paint a stray
|
||||||
|
`▶` on an otherwise-empty row.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `SESSION_HANDOFF.md` refreshed twice this cycle — once
|
||||||
|
recording the HC paint + continuous-scrub polish, then
|
||||||
|
again as the Move Log arc shipped commit-by-commit. The
|
||||||
|
Resume menu's B option now traces the full arc:
|
||||||
|
notches → labels → footer → ESC → HC → arrow keys →
|
||||||
|
HC paint → continuous scrub → move log.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1273 passing tests / 0 failing** across the workspace
|
||||||
|
(net +23 from v0.21.5's 1250 baseline):
|
||||||
|
- 2 from `d3cb1a5` (HC marker on track + notches).
|
||||||
|
- 2 from `2e25476` (continuous-scrub repeat-while-held +
|
||||||
|
release-resets-accumulator).
|
||||||
|
- 8 from `d6f32d3` (move-log panel init + 5 helpers + 3
|
||||||
|
spawn / lifecycle scenarios).
|
||||||
|
- 4 from `140251b` (prev rows: helper k coverage + spawn
|
||||||
|
cardinality + spawn texts + repaint on cursor advance).
|
||||||
|
- 3 from `e7345ae` (active row highlight: wrapper bg +
|
||||||
|
text colour + focus prefix + cursor=0 stays empty).
|
||||||
|
- 4 from `4437a1a` (next rows: helper k coverage + spawn
|
||||||
|
cardinality + spawn texts + under-fill at replay end).
|
||||||
|
- Clippy clean across the workspace.
|
||||||
|
|
||||||
|
## [0.21.5] — 2026-05-08
|
||||||
|
|
||||||
|
Patch release for the post-v0.21.4 work. One through-line:
|
||||||
|
**replay-overlay scrubbing affordances + accessibility**. v0.21.4
|
||||||
|
shipped pause / resume / step + the WIN MOVE marker as the first
|
||||||
|
*scrubbing-shaped* additions to the replay overlay; v0.21.5
|
||||||
|
fills out the rest of the scrubbing UX so the player has both
|
||||||
|
visual anchor points (notches + labels) and a complete keyboard
|
||||||
|
control surface (Space / Esc / ← / →) for navigating a paused
|
||||||
|
replay.
|
||||||
|
|
||||||
|
Two of the six commits in this cycle are layout-changing — they
|
||||||
|
grow the banner height from 60 px → 76 px → 92 px to make room
|
||||||
|
for the notch labels and keybind footer. Banner geometry was
|
||||||
|
fixed for every prior B-2 commit; this release establishes the
|
||||||
|
"grow the container, add a flex-column child" pattern that the
|
||||||
|
remaining B-2 sub-pieces (move-log scroller, mini-tableau
|
||||||
|
preview) will inherit when they land.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Quarter-mark scrub-bar notches** (`fe68861`). Five 1 px
|
||||||
|
vertical ticks at 0 / 25 / 50 / 75 / 100 % give the player
|
||||||
|
visual anchor points without needing to mentally bisect the
|
||||||
|
bar. Pure helper `scrub_notch_positions()` returns the fixed
|
||||||
|
array; spawn loop sits next to the WIN MOVE marker spawn so
|
||||||
|
the lifecycles match. Notches paint in `BORDER_SUBTLE` (same
|
||||||
|
as the unfilled track) and rely on extending past the 1 px
|
||||||
|
track (5 px tall, anchored 2 px above the track top) for
|
||||||
|
visibility — same trick the WIN MOVE marker uses. Spawned
|
||||||
|
*after* the WIN MOVE marker so a notch and the marker
|
||||||
|
landing on the same percentage paint the marker on top.
|
||||||
|
- **Percentage labels under each notch** (`d322abf`). Five
|
||||||
|
`0%` / `25%` / `50%` / `75%` / `100%` labels in a new 16 px
|
||||||
|
row beneath the 1 px scrub track give the player explicit
|
||||||
|
quarter-mark readouts. Banner grew from 60 → 76 px to
|
||||||
|
accommodate the row — first **layout-changing** commit in
|
||||||
|
the B-2 arc. Pure helper `scrub_notch_labels()` returns the
|
||||||
|
fixed array, paired index-for-index with
|
||||||
|
`scrub_notch_positions()`. Spawn loop applies an "endpoints
|
||||||
|
flush, middle three percent-anchored" positioning pattern:
|
||||||
|
leftmost label gets `left: 0`, rightmost gets `right: 0`,
|
||||||
|
middle three anchor at `left: Val::Percent(p)` since Bevy
|
||||||
|
0.18 UI lacks a clean CSS-style `translate-x: -50%`
|
||||||
|
centering primitive. Label colour is `TEXT_SECONDARY`
|
||||||
|
rather than the mockup's `BORDER_SUBTLE` (the latter would
|
||||||
|
match the notches but is too low-contrast against
|
||||||
|
`BG_ELEVATED_HI` to read at 12 px).
|
||||||
|
- **Keybind-hint footer** (`1873b3f`). Vim-style mode line on
|
||||||
|
the left (`▌ NORMAL │ replay`) plus a keybind hint on the
|
||||||
|
right at the bottom edge of the banner. Banner grew from
|
||||||
|
76 → 92 px to fit the 16 px footer row. Surfaces every
|
||||||
|
wired keyboard accelerator visually so CLAUDE.md §3.3's
|
||||||
|
UI-first contract holds for keyboard accelerators too. The
|
||||||
|
footer lists *only* keybinds that are actually wired —
|
||||||
|
the only-wired-keybinds discipline means each release
|
||||||
|
cycle's hint string is a precise honest contract with the
|
||||||
|
player. Two pure helpers (`keybind_footer_mode_text`,
|
||||||
|
`keybind_footer_hint_text`) keep the static text testable.
|
||||||
|
1 px top border in `BORDER_SUBTLE` separates the footer
|
||||||
|
from the labels row.
|
||||||
|
- **ESC keyboard accelerator for replay-stop** (`90e24d9`).
|
||||||
|
New `handle_stop_keyboard` system parallels
|
||||||
|
`handle_pause_keyboard` in shape — fires only when state
|
||||||
|
is `Playing`, calls `stop_replay_playback`. Cross-plugin
|
||||||
|
coordination via `pause_plugin::toggle_pause`: added a
|
||||||
|
fourth defer-if check
|
||||||
|
(`replay_state.is_some_and(|s| s.is_playing())`) right
|
||||||
|
after the existing `other_modal_scrims` check so ESC
|
||||||
|
during active replay belongs to the replay overlay, not
|
||||||
|
the pause modal.
|
||||||
|
- **HC-mode coverage for the keybind-footer top border**
|
||||||
|
(`23902cd`).
|
||||||
|
`HighContrastBorder::with_default(BORDER_SUBTLE)` marker
|
||||||
|
on the footer's border-carrying Node so the existing
|
||||||
|
`apply_high_contrast_borders` system bumps the 1 px top
|
||||||
|
border from `#505050` → `#a0a0a0` when
|
||||||
|
`Settings::high_contrast_mode` is on. Without the marker
|
||||||
|
the footer reads as floating loose under HC because the
|
||||||
|
border that anchors it to the labels row is
|
||||||
|
near-invisible.
|
||||||
|
- **← / → keyboard accelerators for paused stepping**
|
||||||
|
(`e5c4f51`). New `step_backwards_replay_playback` in
|
||||||
|
`replay_playback.rs` decrements the cursor and dispatches
|
||||||
|
`UndoRequestEvent`; the game's `handle_undo` reads it
|
||||||
|
next frame to reverse its most-recent move. Hooks the
|
||||||
|
existing undo system rather than replaying-forward-from-
|
||||||
|
zero — every replay-applied move pushes to the undo stack
|
||||||
|
the same way a player move would, so undo is the right
|
||||||
|
reversal primitive. Both arrow keys are paused-only via
|
||||||
|
the same destructure-gate pattern the forward step uses.
|
||||||
|
The mockup labels these `[← →] scrub`; single-move step
|
||||||
|
is the closest behaviour shippable today, so the footer
|
||||||
|
hint reads `[← →] step` — only-wired-keybinds discipline.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Banner height grew 60 → 76 → 92 px** across two
|
||||||
|
layout-changing commits (`d322abf` then `1873b3f`). Top
|
||||||
|
row's `flex_grow: 1.0` still consumes 59 px so the
|
||||||
|
existing content (label / progress chip / buttons) has
|
||||||
|
the same vertical space; the new rows (16 px labels +
|
||||||
|
16 px footer) extend the banner downward into the
|
||||||
|
gameplay area. Banner geometry is now mutable — every
|
||||||
|
prior B-2 commit fit inside fixed 60 px space.
|
||||||
|
- **Keybind-footer hint text grew alongside the wirings**:
|
||||||
|
`[SPACE] pause/resume` →
|
||||||
|
`[SPACE] pause/resume · [ESC] stop` →
|
||||||
|
`[SPACE] pause/resume · [ESC] stop · [← →] step`.
|
||||||
|
- **`pause_plugin::toggle_pause` now defers when a replay
|
||||||
|
is active** (`90e24d9`). Adds a fourth defer-if check to
|
||||||
|
the existing modal-stack pattern.
|
||||||
|
- **`ReplayOverlayPlugin` registers
|
||||||
|
`add_message::<UndoRequestEvent>()`** (`e5c4f51`).
|
||||||
|
Defensive registration so the plugin runs cleanly under
|
||||||
|
`MinimalPlugins` without `GamePlugin` attached.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `SESSION_HANDOFF.md` refreshed five times this cycle.
|
||||||
|
The B option in the Resume menu now traces the full arc:
|
||||||
|
notches → labels → footer → ESC → HC → arrow keys.
|
||||||
|
- The pre-existing `daily_challenge` warning test that
|
||||||
|
fails when wall-clock UTC is within 30 minutes of
|
||||||
|
midnight is documented in this cycle's handoff. Same
|
||||||
|
shape as the earlier `winnable_seed_search` flake —
|
||||||
|
time-dependent, deterministically passes outside the
|
||||||
|
trigger window.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- **1250 total tests / 1249 passing / 1 pre-existing
|
||||||
|
time-dependent flake** across the workspace (net +22 from
|
||||||
|
v0.21.4's 1228 baseline):
|
||||||
|
- 4 from `fe68861` (scrub-notch coverage)
|
||||||
|
- 4 from `d322abf` (notch-label coverage)
|
||||||
|
- 4 from `1873b3f` (keybind-footer coverage)
|
||||||
|
- 3 from `90e24d9` (ESC-accelerator coverage)
|
||||||
|
- 1 from `23902cd` (HC-marker coverage)
|
||||||
|
- 6 from `e5c4f51` (arrow-keyboard coverage)
|
||||||
|
- **Pre-existing flake**:
|
||||||
|
`daily_challenge_plugin::tests::check_system_fires_warning_event_only_once_per_day`
|
||||||
|
fails when wall-clock UTC is within 30 minutes of
|
||||||
|
midnight. Verified pre-existing by stash-and-retest
|
||||||
|
before each commit. Will pass deterministically outside
|
||||||
|
the trigger window. Not introduced by this release.
|
||||||
|
- Clippy clean across the workspace.
|
||||||
|
|
||||||
## [0.21.4] — 2026-05-08
|
## [0.21.4] — 2026-05-08
|
||||||
|
|
||||||
Patch release for the post-v0.21.3 work. One through-line:
|
Patch release for the post-v0.21.3 work. One through-line:
|
||||||
|
|||||||
+81
-109
@@ -1,91 +1,66 @@
|
|||||||
# Solitaire Quest — Session Handoff
|
# Solitaire Quest — Session Handoff
|
||||||
|
|
||||||
**Last updated:** 2026-05-08 — **v0.21.3 cut and tagged at
|
**Last updated:** 2026-05-08 — **v0.21.7 cut and tagged at
|
||||||
`3d92a91`**, working tree clean, all post-tag work pushed to
|
`da3e542`**, working tree clean (tag pending push).
|
||||||
origin.
|
|
||||||
|
|
||||||
v0.21.3 is a patch release with one through-line: **accessibility
|
v0.21.7 is a single-commit patch closing the last major B-2
|
||||||
arc closure**. v0.21.2 explicitly carved out "dynamic-paint sites"
|
sub-piece: **mini-tableau preview dim layer**. A full-screen
|
||||||
(HUD action buttons, modal buttons, radial menu rim) on the
|
`ReplayTableauDimLayer` UI node (100 % × 100 %, 50 % opacity
|
||||||
assumption that their existing paint cycles would race the
|
black) at `Z_REPLAY_DIM = 54` (one rung below the replay
|
||||||
central `update_high_contrast_borders` system. v0.21.3 walks the
|
chrome at z=55) darkens the card world during replay so the
|
||||||
actual code, finds the carve-out was over-cautious, and closes
|
banner and move-log panel read clearly against the scene —
|
||||||
it. Bonus: the first real consumer of `ToastVariant::Warning`
|
matching the mockup's "Game Peek Band at 50 % opacity" spec
|
||||||
also lands here, making the `ToastVariant` enum fully load-bearing
|
without touching `card_plugin`. 13 commits have now shipped
|
||||||
(every variant has at least one driver).
|
across v0.21.4–v0.21.7 on the B-2 replay screen-takeover
|
||||||
|
arc; every major sub-piece is closed.
|
||||||
|
|
||||||
Full v0.21.3 detail lives in `CHANGELOG.md` § [0.21.3]. This
|
Full v0.21.7 detail lives in `CHANGELOG.md` § [0.21.7]. This
|
||||||
file from here on focuses on what's *open* post-cut and how to
|
file from here on focuses on what's *open* post-cut and how to
|
||||||
resume.
|
resume.
|
||||||
|
|
||||||
## Status at pause
|
## Status at pause
|
||||||
|
|
||||||
- **HEAD locally:** see `git rev-parse HEAD`. The cut commit is
|
- **HEAD locally:** `da3e542` (v0.21.7 commit). Tag pending —
|
||||||
`3d92a91`; post-cut work on B-2 (`ab857bb` data field +
|
push with `git tag v0.21.7 da3e542 && git push origin v0.21.7`.
|
||||||
`52befa6` WIN MOVE marker UI + `fbe48ac` playback controls)
|
- **HEAD on origin:** `f63db76` (v0.21.6). v0.21.7 commit
|
||||||
rides on top of that.
|
not pushed yet; a docs-only edit will ride on top before push.
|
||||||
- **HEAD on origin:** matches local. v0.21.3 is fully on origin.
|
|
||||||
- **Working tree:** clean. No WIP outstanding.
|
- **Working tree:** clean. No WIP outstanding.
|
||||||
- **`artwork/` directory:** still untracked. Intentional.
|
- **`artwork/` directory:** still untracked. Intentional.
|
||||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
||||||
clean.
|
clean.
|
||||||
- **Tests:** **1228 passing / 0 failing** across the workspace
|
- **Tests:** **1275 passing / 0 failing** across the workspace.
|
||||||
(1207 from v0.21.3's stats + 5 from `ab857bb`'s
|
Detail in `CHANGELOG.md` § [0.21.7] § Stats.
|
||||||
`win_move_index` coverage + 8 from `52befa6`'s WIN MOVE marker
|
- **Tags on origin:** `v0.9.0` through `v0.21.6`. v0.21.7
|
||||||
pure-helper truth-table + spawn lifecycle + 8 from `fbe48ac`'s
|
tag exists locally at `da3e542`; push to origin when ready.
|
||||||
pause / step / keyboard accelerator coverage).
|
|
||||||
- **Tags on origin:** `v0.9.0` through `v0.21.3`. v0.21.3 is on
|
|
||||||
`3d92a91`; v0.21.2 stays on `f23df3b`; v0.21.1 stays on
|
|
||||||
`daa655a`; v0.21.0 stays on `04f9bf9`; v0.20.0 stays on
|
|
||||||
`41a009a`.
|
|
||||||
|
|
||||||
## Since the v0.21.3 cut
|
## Since the v0.21.7 cut
|
||||||
|
|
||||||
- **`ab857bb` — `Replay::win_move_index` data field landed.**
|
One commit in flight (not yet pushed to origin): `da3e542`
|
||||||
First finite step toward the B-2 replay screen-takeover
|
adds the full-screen tableau dim layer. CHANGELOG and
|
||||||
redesign. Additive optional `Option<usize>` on `Replay` with
|
SESSION_HANDOFF updates ride on top. Push with:
|
||||||
`#[serde(default)]` so older `latest_replay.json` /
|
```
|
||||||
`replays.json` files load unchanged (no schema bump). Populated
|
git push origin master
|
||||||
at the live recording site via a new `with_win_move_index`
|
git push origin v0.21.7
|
||||||
builder; for fresh recordings the value is always
|
```
|
||||||
`Some(moves.len() - 1)` because recording freezes on win, but
|
|
||||||
storing it explicitly lets the playback UI read the WIN MOVE
|
Open next-step menu (all major B-2 sub-pieces now closed):
|
||||||
position directly without re-deriving on every render. 5 new
|
1. **Polish: notch label centering.** Bevy 0.18 lacks a
|
||||||
tests (1207 → 1212): default, builder set / set-None, on-disk
|
clean `translate-x: -50%` primitive so the middle three
|
||||||
round-trip, legacy-JSON-loads-with-None backward-compat.
|
scrub-bar labels sit slightly right-of-notch. Could use a
|
||||||
- **`52befa6` — WIN MOVE marker on the scrub bar.** Second
|
child Text wrapper with computed left-margin compensation.
|
||||||
commit on B-2 — the UI that consumes the data field. New
|
Tiny commit, requires visual review.
|
||||||
`ReplayOverlayWinMoveMarker` component spawned as a sibling
|
2. **Polish: WIN MOVE marker HC bump.** Currently uses
|
||||||
to `ReplayOverlayScrubFill` under the 1px scrub track,
|
`STATE_SUCCESS` lime which stays visible under HC, but a
|
||||||
absolute-positioned at `replay.win_move_index / total` along
|
contrast bump under HC would make it even more legible
|
||||||
the bar. Painted in `STATE_SUCCESS` (green) so the marker
|
alongside the bumped notches. Optional.
|
||||||
reads as "this is where the win lives." Pure helper
|
3. **Move Log auto-scroll** — only relevant if the panel's
|
||||||
`win_move_marker_pct` returns `None` for any state where the
|
row count grows beyond the current 5-row fixed window.
|
||||||
marker shouldn't draw (Inactive, Completed, replay missing
|
Currently the prev-2 / active / next-2 layout fits all
|
||||||
the field, empty move list); percentage clamps to `[0, 100]`
|
visible content, so auto-scroll is unneeded.
|
||||||
defensively. Lifecycle is spawn-time only — the marker is
|
|
||||||
immutable during a single playback because the underlying
|
Recommended order: options 1 and 2 are tiny polish commits
|
||||||
`Replay` doesn't change while `Playing`. Despawned with the
|
that benefit from visual review. Option 3 is a non-starter
|
||||||
overlay tree on transition back to `Inactive`. 8 new tests
|
unless the panel's row capacity grows.
|
||||||
(1212 → 1220): pure-helper truth table + spawn-presence /
|
|
||||||
spawn-absence / despawn-lifecycle observables.
|
|
||||||
- **`fbe48ac` — playback controls (pause / resume / step).**
|
|
||||||
Third commit on B-2. New `paused: bool` field on
|
|
||||||
`ReplayPlaybackState::Playing`; `tick_replay_playback` skips
|
|
||||||
the `secs_to_next` decrement entirely while paused so cursor
|
|
||||||
and timer freeze together. New public API:
|
|
||||||
`toggle_pause_replay_playback` and `step_replay_playback`
|
|
||||||
(the latter hard-gated to `Playing { paused: true }` so
|
|
||||||
manual stepping can't race the tick loop). UI: Pause /
|
|
||||||
Resume button (label repaints reactively via
|
|
||||||
`update_pause_button_label` which walks `Children` from
|
|
||||||
marker to inner `Text`) + Step button + Space keyboard
|
|
||||||
accelerator. Existing 25 `Playing { ... }` construction
|
|
||||||
sites across tests gained `paused: false` mechanically.
|
|
||||||
8 new tests (1220 → 1228): label truth table, label repaint
|
|
||||||
on state change, click-toggles-paused, step advances exactly
|
|
||||||
one cursor with paused preserved, step-while-running no-op,
|
|
||||||
Space toggles paused.
|
|
||||||
|
|
||||||
## Open punch list
|
## Open punch list
|
||||||
|
|
||||||
@@ -117,20 +92,18 @@ chrome migration, splash boot screen, replay-overlay banner,
|
|||||||
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
|
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
|
||||||
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
|
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
|
||||||
|
|
||||||
- **Replay-overlay screen-takeover redesign.** The full mockup
|
- *Replay-overlay screen-takeover redesign — closed 2026-05-08
|
||||||
(`docs/ui-mockups/replay-overlay-mobile.html`) calls for a
|
across 13 commits (v0.21.4–v0.21.7).* The full mockup
|
||||||
mini-tableau preview, playback controls, move-log scroll, and
|
(`docs/ui-mockups/replay-overlay-mobile.html`) has shipped:
|
||||||
a WIN MOVE marker on the scrub bar. Banner-local pieces all
|
banner chrome (v0.21.0), floating MOVE chip (v0.21.2), WIN
|
||||||
shipped in v0.21.0 (`c84d9f4` + `6204db8` + `54005d5` +
|
MOVE scrub-bar marker (post-v0.21.3), playback controls /
|
||||||
`e080b49`); the floating MOVE chip above the focused card
|
Space accelerator (post-v0.21.3), scrub notches + labels +
|
||||||
shipped in v0.21.2 (`2fb2d63`). The WIN MOVE scrub-bar marker
|
keybind footer + ESC / ← / → accelerators + HC border
|
||||||
shipped post-v0.21.3 in `ab857bb` (data field) + `52befa6`
|
(v0.21.5), Move Log panel + HC scrub track + continuous
|
||||||
(UI). Playback controls (pause / resume / step + Space
|
scrub (v0.21.6), and full-screen 50 % opacity dim layer
|
||||||
accelerator) shipped post-v0.21.3 in `fbe48ac`. What still
|
(v0.21.7). Every major B-2 sub-piece is now closed. The
|
||||||
needs to land: a move-log scroller and a mini-tableau
|
only remaining items are minor polish: notch-label centering
|
||||||
preview — both screen-takeover-only pieces that need a
|
and WIN MOVE HC contrast bump (see Open next-step menu).*
|
||||||
larger layout reflow than the existing banner can carry.
|
|
||||||
Multi-session.
|
|
||||||
- *Floating `MOVE N/M` chip above the focused card during
|
- *Floating `MOVE N/M` chip above the focused card during
|
||||||
playback — closed 2026-05-08 by `2fb2d63`.* World-space
|
playback — closed 2026-05-08 by `2fb2d63`.* World-space
|
||||||
`Text2d` entity sibling to the banner overlay; uses the same
|
`Text2d` entity sibling to the banner overlay; uses the same
|
||||||
@@ -273,20 +246,22 @@ into a v0.21.1 / v0.22.0 cut.
|
|||||||
```
|
```
|
||||||
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
||||||
Working directory: <Rusty_Solitaire clone path on this machine>.
|
Working directory: <Rusty_Solitaire clone path on this machine>.
|
||||||
Branch: master. v0.21.3 is tagged at 3d92a91 (cut 2026-05-08, a
|
Branch: master. v0.21.7 is tagged at da3e542 (cut 2026-05-08,
|
||||||
patch release rolling up the accessibility-arc closure: HC reaches
|
closes the last major B-2 sub-piece: full-screen tableau dim
|
||||||
the previously-carved-out dynamic-paint sites, and the first real
|
layer — 50 % opacity black UI scrim at z=54 that darkens the
|
||||||
consumer of `ToastVariant::Warning` lands as the daily-challenge
|
card world during replay so the chrome reads clearly above it).
|
||||||
expiry toast). v0.21.2 stays at f23df3b, v0.21.1 at daa655a,
|
v0.21.6 stays at f63db76, v0.21.5 at a2432df, v0.21.4 at
|
||||||
v0.21.0 at 04f9bf9. Working tree clean. See CHANGELOG.md §
|
23ff62c, v0.21.3 at 3d92a91, v0.21.2 at f23df3b, v0.21.1 at
|
||||||
[0.21.3] for full detail.
|
daa655a, v0.21.0 at 04f9bf9. Working tree clean (CHANGELOG +
|
||||||
|
SESSION_HANDOFF docs ride on top of da3e542; push pending).
|
||||||
|
See CHANGELOG.md § [0.21.7] for full detail.
|
||||||
|
|
||||||
State: HEAD locally — see `git rev-parse HEAD`. All workspace tests
|
State: HEAD locally — see `git rev-parse HEAD`. Workspace
|
||||||
pass (1207+; check with `cargo test --workspace`), clippy clean.
|
tests: 1275 passing / 0 failing. Clippy clean.
|
||||||
|
|
||||||
READ FIRST (in order, before doing anything):
|
READ FIRST (in order, before doing anything):
|
||||||
1. SESSION_HANDOFF.md — this file
|
1. SESSION_HANDOFF.md — this file
|
||||||
2. CHANGELOG.md — [0.21.3] section is the most recent cut
|
2. CHANGELOG.md — [0.21.6] section is the most recent cut
|
||||||
3. CLAUDE.md — unified-3.0 rule set
|
3. CLAUDE.md — unified-3.0 rule set
|
||||||
4. CLAUDE_SPEC.md — formal architecture spec
|
4. CLAUDE_SPEC.md — formal architecture spec
|
||||||
5. ARCHITECTURE.md — crate responsibilities + data flow
|
5. ARCHITECTURE.md — crate responsibilities + data flow
|
||||||
@@ -306,18 +281,15 @@ DECISION TO ASK THE PLAYER FIRST:
|
|||||||
tests can't catch. Likely surfaces JNI ClipboardManager
|
tests can't catch. Likely surfaces JNI ClipboardManager
|
||||||
and Android Keystore stubs that need real bridges. Larger
|
and Android Keystore stubs that need real bridges. Larger
|
||||||
scope; needs an Android device or emulator running.
|
scope; needs an Android device or emulator running.
|
||||||
B. Replay-overlay screen-takeover redesign — multi-session
|
B. Replay-overlay polish (B-2 arc fully closed in v0.21.7).
|
||||||
work. Three sub-pieces shipped post-v0.21.3: WIN MOVE
|
All 13 planned sub-pieces shipped. Remaining items are
|
||||||
marker (`ab857bb` data field + `52befa6` UI), playback
|
minor polish: (a) scrub-bar notch-label centering — middle
|
||||||
controls (`fbe48ac` pause/resume/step + Space). What
|
three labels sit slightly right-of-notch due to Bevy 0.18
|
||||||
still needs to land: a move-log scroller and a
|
lacking `translate-x: -50%`; tiny commit, needs visual
|
||||||
mini-tableau preview — both layout-heavy pieces that need
|
review. (b) WIN MOVE marker HC contrast bump — optional
|
||||||
more vertical real estate than the current banner-only
|
luminance boost under HC mode. Both are single commits
|
||||||
overlay carries, so the natural next finite step is the
|
requiring visual review; recommend treating as a v0.21.8
|
||||||
screen-takeover layout itself (mockup at
|
polish pass after manual testing.
|
||||||
`docs/ui-mockups/replay-overlay-mobile.html`). The
|
|
||||||
smaller floating-MOVE-chip piece shipped in v0.21.2
|
|
||||||
(`2fb2d63`).
|
|
||||||
C. Phase 8 (sync) — local storage scaffolding, self-hosted
|
C. Phase 8 (sync) — local storage scaffolding, self-hosted
|
||||||
Axum server, `SolitaireServerClient` impl, GPGS stub
|
Axum server, `SolitaireServerClient` impl, GPGS stub
|
||||||
wired into Settings. The biggest open arc by scope; rolls
|
wired into Settings. The biggest open arc by scope; rolls
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use crate::events::{
|
|||||||
use crate::font_plugin::FontResource;
|
use crate::font_plugin::FontResource;
|
||||||
use crate::game_plugin::{GameOverScreen, GameStatePath};
|
use crate::game_plugin::{GameOverScreen, GameStatePath};
|
||||||
use crate::progress_plugin::ProgressResource;
|
use crate::progress_plugin::ProgressResource;
|
||||||
|
use crate::replay_playback::ReplayPlaybackState;
|
||||||
use crate::resources::{DragState, GameStateResource};
|
use crate::resources::{DragState, GameStateResource};
|
||||||
use crate::selection_plugin::{SelectionKeySet, SelectionState};
|
use crate::selection_plugin::{SelectionKeySet, SelectionState};
|
||||||
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource, SettingsStoragePath};
|
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource, SettingsStoragePath};
|
||||||
@@ -154,6 +155,7 @@ fn toggle_pause(
|
|||||||
mut drag: Option<ResMut<DragState>>,
|
mut drag: Option<ResMut<DragState>>,
|
||||||
mut changed: MessageWriter<StateChangedEvent>,
|
mut changed: MessageWriter<StateChangedEvent>,
|
||||||
selection: Option<Res<SelectionState>>,
|
selection: Option<Res<SelectionState>>,
|
||||||
|
replay_state: Option<Res<ReplayPlaybackState>>,
|
||||||
) {
|
) {
|
||||||
let PauseModalQueries {
|
let PauseModalQueries {
|
||||||
pause_screens: screens,
|
pause_screens: screens,
|
||||||
@@ -184,6 +186,15 @@ fn toggle_pause(
|
|||||||
if !other_modal_scrims.is_empty() {
|
if !other_modal_scrims.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If a replay is currently playing, let `replay_overlay::handle_stop_keyboard`
|
||||||
|
// own the Esc press — that handler stops the replay. Without this guard a
|
||||||
|
// single Esc both stops the replay AND opens the pause modal on top of the
|
||||||
|
// (now empty) board, leaving the player on a screen they didn't ask for.
|
||||||
|
// The HUD-button path is gated too; clicking Pause while watching a replay
|
||||||
|
// is almost always an accident.
|
||||||
|
if replay_state.is_some_and(|s| s.is_playing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// If a card is currently selected, let SelectionPlugin handle this Escape
|
// If a card is currently selected, let SelectionPlugin handle this Escape
|
||||||
// (it will clear the selection). Pause must not also open in the same frame.
|
// (it will clear the selection). Pause must not also open in the same frame.
|
||||||
if selection.is_some_and(|s| s.selected_pile.is_some()) {
|
if selection.is_some_and(|s| s.selected_pile.is_some()) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use solitaire_data::{Replay, ReplayMove};
|
use solitaire_data::{Replay, ReplayMove};
|
||||||
|
|
||||||
use crate::events::{DrawRequestEvent, MoveRequestEvent, StateChangedEvent};
|
use crate::events::{DrawRequestEvent, MoveRequestEvent, StateChangedEvent, UndoRequestEvent};
|
||||||
use crate::game_plugin::{GameMutation, RecordingReplay};
|
use crate::game_plugin::{GameMutation, RecordingReplay};
|
||||||
use crate::resources::GameStateResource;
|
use crate::resources::GameStateResource;
|
||||||
use crate::settings_plugin::SettingsResource;
|
use crate::settings_plugin::SettingsResource;
|
||||||
@@ -284,6 +284,52 @@ pub fn step_replay_playback(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Steps the replay **backwards** by exactly one move while paused.
|
||||||
|
///
|
||||||
|
/// Strategy: the live game's undo system is the source of truth for
|
||||||
|
/// reversing moves. Every move the replay forward-stepped (via
|
||||||
|
/// [`step_replay_playback`] or the auto-advance loop in
|
||||||
|
/// [`tick_replay_playback`]) was dispatched as a canonical
|
||||||
|
/// [`MoveRequestEvent`] / [`DrawRequestEvent`], which the game
|
||||||
|
/// applied and pushed onto its undo stack. So a backwards step here
|
||||||
|
/// is simply: decrement the cursor (so the about-to-apply move
|
||||||
|
/// re-points at the one we're rewinding past) and fire an
|
||||||
|
/// [`UndoRequestEvent`] so the game reverses its most-recent move
|
||||||
|
/// next frame.
|
||||||
|
///
|
||||||
|
/// Hard-gated to the paused state via destructure pattern —
|
||||||
|
/// matches the existing [`step_replay_playback`] gate so the
|
||||||
|
/// player can only scrub one direction at a time and the tick
|
||||||
|
/// loop never races a manual rewind.
|
||||||
|
///
|
||||||
|
/// Returns `false` and is a no-op in three cases:
|
||||||
|
/// - State isn't `Playing` (no replay attached).
|
||||||
|
/// - State is `Playing` but not paused (the tick loop owns the cursor).
|
||||||
|
/// - Cursor is already at 0 (nothing to rewind past).
|
||||||
|
///
|
||||||
|
/// Returns `true` on a successful step; the actual game-state
|
||||||
|
/// reversal happens next frame when `handle_undo` reads the
|
||||||
|
/// `UndoRequestEvent`.
|
||||||
|
pub fn step_backwards_replay_playback(
|
||||||
|
state: &mut ResMut<ReplayPlaybackState>,
|
||||||
|
undo_writer: &mut MessageWriter<UndoRequestEvent>,
|
||||||
|
) -> bool {
|
||||||
|
let ReplayPlaybackState::Playing {
|
||||||
|
cursor,
|
||||||
|
paused: true,
|
||||||
|
..
|
||||||
|
} = state.as_mut()
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if *cursor == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*cursor -= 1;
|
||||||
|
undo_writer.write(UndoRequestEvent);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Tick system. Runs every frame; only does work when
|
/// Tick system. Runs every frame; only does work when
|
||||||
/// [`ReplayPlaybackState::is_playing`].
|
/// [`ReplayPlaybackState::is_playing`].
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ use crate::ui_modal::{
|
|||||||
};
|
};
|
||||||
use crate::ui_tooltip::Tooltip;
|
use crate::ui_tooltip::Tooltip;
|
||||||
use crate::ui_theme::{
|
use crate::ui_theme::{
|
||||||
BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BORDER_SUBTLE, BORDER_SUBTLE_HC, HighContrastBorder,
|
BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BORDER_SUBTLE, BORDER_SUBTLE_HC, HighContrastBackground,
|
||||||
|
HighContrastBorder,
|
||||||
RADIUS_SM, SPACE_2, STATE_SUCCESS, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY, TYPE_BODY_LG,
|
RADIUS_SM, SPACE_2, STATE_SUCCESS, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY, TYPE_BODY_LG,
|
||||||
TYPE_CAPTION, VAL_SPACE_2, VAL_SPACE_3, Z_MODAL_PANEL,
|
TYPE_CAPTION, VAL_SPACE_2, VAL_SPACE_3, Z_MODAL_PANEL,
|
||||||
};
|
};
|
||||||
@@ -365,6 +366,7 @@ impl Plugin for SettingsPlugin {
|
|||||||
update_color_blind_text,
|
update_color_blind_text,
|
||||||
update_high_contrast_text,
|
update_high_contrast_text,
|
||||||
update_high_contrast_borders,
|
update_high_contrast_borders,
|
||||||
|
update_high_contrast_backgrounds,
|
||||||
update_reduce_motion_text,
|
update_reduce_motion_text,
|
||||||
update_tooltip_delay_text,
|
update_tooltip_delay_text,
|
||||||
update_time_bonus_multiplier_text,
|
update_time_bonus_multiplier_text,
|
||||||
@@ -674,6 +676,41 @@ fn update_high_contrast_borders(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Repaints `BackgroundColor` on every entity tagged with
|
||||||
|
/// [`HighContrastBackground`] based on `Settings::high_contrast_mode`.
|
||||||
|
/// Off → the marker's `default_color`; on → `BORDER_SUBTLE_HC`
|
||||||
|
/// (`#a0a0a0`). Compares against the current background and only
|
||||||
|
/// mutates when different so Bevy's change-detection doesn't trigger
|
||||||
|
/// repaints every frame.
|
||||||
|
///
|
||||||
|
/// Parallel to [`update_high_contrast_borders`]. Same on/off rule,
|
||||||
|
/// same change-suppression idiom, different colour channel —
|
||||||
|
/// `BackgroundColor` for tick marks, decorative strips, fine
|
||||||
|
/// separators that paint their shape directly rather than via a
|
||||||
|
/// `BorderColor` on a wider Node.
|
||||||
|
///
|
||||||
|
/// Tagged sites in v0.21.x: the replay overlay's 1 px scrub track
|
||||||
|
/// + 5 quarter-mark notch ticks (`replay_overlay::spawn_overlay`).
|
||||||
|
///
|
||||||
|
/// More sites can be tagged in follow-ups by adding
|
||||||
|
/// `HighContrastBackground::with_default(...)` to their spawn tuple.
|
||||||
|
pub(crate) fn update_high_contrast_backgrounds(
|
||||||
|
settings: Res<SettingsResource>,
|
||||||
|
mut backgrounds: Query<(&HighContrastBackground, &mut BackgroundColor)>,
|
||||||
|
) {
|
||||||
|
let high_contrast = settings.0.high_contrast_mode;
|
||||||
|
for (marker, mut bg) in backgrounds.iter_mut() {
|
||||||
|
let target = if high_contrast {
|
||||||
|
marker.hc_color
|
||||||
|
} else {
|
||||||
|
marker.default_color
|
||||||
|
};
|
||||||
|
if bg.0 != target {
|
||||||
|
*bg = BackgroundColor(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_reduce_motion_text(
|
fn update_reduce_motion_text(
|
||||||
settings: Res<SettingsResource>,
|
settings: Res<SettingsResource>,
|
||||||
mut text_nodes: Query<&mut Text, With<ReduceMotionText>>,
|
mut text_nodes: Query<&mut Text, With<ReduceMotionText>>,
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ pub const ACCENT_SECONDARY: Color = Color::srgb(0.882, 0.639, 0.933);
|
|||||||
/// from base16-eighties. `#acc267`.
|
/// from base16-eighties. `#acc267`.
|
||||||
pub const STATE_SUCCESS: Color = Color::srgb(0.675, 0.761, 0.404);
|
pub const STATE_SUCCESS: Color = Color::srgb(0.675, 0.761, 0.404);
|
||||||
|
|
||||||
|
/// High-contrast variant of [`STATE_SUCCESS`] — `#c8e862`. Brighter
|
||||||
|
/// lime that maintains the success hue while lifting luminance from
|
||||||
|
/// ~0.51 → ~0.73 so the WIN MOVE scrub-bar marker stands out from
|
||||||
|
/// the bumped notch ticks (`BORDER_SUBTLE_HC` `#a0a0a0`, L≈0.60) in
|
||||||
|
/// high-contrast mode.
|
||||||
|
pub const STATE_SUCCESS_HC: Color = Color::srgb(0.784, 0.910, 0.384);
|
||||||
|
|
||||||
/// Warning — penalty signal, daily-seed expiry countdown, sync-pending
|
/// Warning — penalty signal, daily-seed expiry countdown, sync-pending
|
||||||
/// status. Gold from base16-eighties. **Both** Undo and Recycle
|
/// status. Gold from base16-eighties. **Both** Undo and Recycle
|
||||||
/// counters use this when non-zero. `#ddb26f`.
|
/// counters use this when non-zero. `#ddb26f`.
|
||||||
@@ -252,6 +259,56 @@ impl HighContrastBorder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marker for entities whose [`BackgroundColor`] should swap to
|
||||||
|
/// [`BORDER_SUBTLE_HC`] when `Settings::high_contrast_mode` is on.
|
||||||
|
/// Parallel to [`HighContrastBorder`] but for sites that paint their
|
||||||
|
/// shape via `BackgroundColor` rather than `BorderColor` —
|
||||||
|
/// `bevy::ui` 1 px decorative strips, tick marks, fine separators
|
||||||
|
/// often render as tiny full-bleed `Node`s, not as borders, so the
|
||||||
|
/// border-marker pattern doesn't apply.
|
||||||
|
///
|
||||||
|
/// `default_color` records the off-state colour; `hc_color` the on-
|
||||||
|
/// state colour. [`with_default`] fills `hc_color` with
|
||||||
|
/// [`BORDER_SUBTLE_HC`] so the 90 % of sites that just need the
|
||||||
|
/// standard subtle-border bump can continue using a one-argument
|
||||||
|
/// constructor. [`with_hc`] overrides the HC colour for the rare
|
||||||
|
/// site (currently only the WIN MOVE scrub-bar marker) that needs a
|
||||||
|
/// domain-specific HC variant (`STATE_SUCCESS_HC` instead of a gray).
|
||||||
|
///
|
||||||
|
/// [`with_default`]: HighContrastBackground::with_default
|
||||||
|
/// [`with_hc`]: HighContrastBackground::with_hc
|
||||||
|
/// [`BackgroundColor`]: bevy::prelude::BackgroundColor
|
||||||
|
#[derive(bevy::prelude::Component, Debug, Clone, Copy)]
|
||||||
|
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,
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HighContrastBackground {
|
||||||
|
/// Convenience constructor — HC colour defaults to
|
||||||
|
/// [`BORDER_SUBTLE_HC`].
|
||||||
|
pub const fn with_default(default_color: bevy::prelude::Color) -> Self {
|
||||||
|
Self { default_color, hc_color: BORDER_SUBTLE_HC }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor for sites whose HC colour differs from the standard
|
||||||
|
/// [`BORDER_SUBTLE_HC`]. Currently used by the WIN MOVE scrub-bar
|
||||||
|
/// 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,
|
||||||
|
) -> Self {
|
||||||
|
Self { default_color, hc_color }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Strong border — hover outline, focused button, active popover.
|
/// Strong border — hover outline, focused button, active popover.
|
||||||
/// `outline` from the design system. `#505050`.
|
/// `outline` from the design system. `#505050`.
|
||||||
pub const BORDER_STRONG: Color = Color::srgba(0.314, 0.314, 0.314, 1.0);
|
pub const BORDER_STRONG: Color = Color::srgba(0.314, 0.314, 0.314, 1.0);
|
||||||
|
|||||||
Reference in New Issue
Block a user