feat(engine): wire Stats Watch Replay button to in-engine playback
Promotes the Stats overlay's Watch Replay button from a stub
InfoToastEvent ("playback coming in a future build") to actually
starting in-engine playback via the new
replay_playback::start_replay_playback API. Pressing the button
when a replay exists resets the game to the recorded deal and the
ReplayOverlayPlugin's banner takes over.
The handler reads ReplayPlaybackState via Option<ResMut<...>> so
headless test fixtures that don't register ReplayPlaybackPlugin
keep compiling — they fall back to a descriptive "Replay ready"
toast. The "no replay yet" branch still surfaces the existing
"win a game first" toast.
Plugin registration in solitaire_app/src/main.rs picks up
ReplayPlaybackPlugin and ReplayOverlayPlugin alongside the existing
StatsPlugin. They run in any order — the playback plugin owns the
state resource, the overlay plugin spawns/despawns based on state
changes, and Stats's button handler dispatches into the playback
plugin's API.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,8 @@ use solitaire_engine::{
|
||||
AudioPlugin, AutoCompletePlugin, CardAnimationPlugin, CardPlugin, ChallengePlugin,
|
||||
CursorPlugin, DailyChallengePlugin, FeedbackAnimPlugin, FontPlugin, GamePlugin, HelpPlugin,
|
||||
HomePlugin, HudPlugin, InputPlugin, LeaderboardPlugin, OnboardingPlugin, PausePlugin,
|
||||
ProfilePlugin, ProgressPlugin, RadialMenuPlugin, SelectionPlugin, SettingsPlugin, SplashPlugin,
|
||||
ProfilePlugin, ProgressPlugin, RadialMenuPlugin, ReplayOverlayPlugin, ReplayPlaybackPlugin,
|
||||
SelectionPlugin, SettingsPlugin, SplashPlugin,
|
||||
StatsPlugin, SyncPlugin, TablePlugin, ThemePlugin, ThemeRegistryPlugin, TimeAttackPlugin,
|
||||
UiFocusPlugin, UiModalPlugin, UiTooltipPlugin, WeeklyGoalsPlugin, WinSummaryPlugin,
|
||||
};
|
||||
@@ -117,6 +118,8 @@ fn main() {
|
||||
.add_plugins(FeedbackAnimPlugin)
|
||||
.add_plugins(CardAnimationPlugin)
|
||||
.add_plugins(AutoCompletePlugin)
|
||||
.add_plugins(ReplayPlaybackPlugin)
|
||||
.add_plugins(ReplayOverlayPlugin)
|
||||
.add_plugins(StatsPlugin::default())
|
||||
.add_plugins(ProgressPlugin::default())
|
||||
.add_plugins(AchievementPlugin::default())
|
||||
|
||||
@@ -187,27 +187,44 @@ fn refresh_latest_replay_on_win(
|
||||
|
||||
/// Click handler for the "Watch replay" button.
|
||||
///
|
||||
/// Replay playback lives on the sync server's web UI rather than in
|
||||
/// the desktop client. This handler currently surfaces a clear toast
|
||||
/// pointing the player there once the upload + URL is wired; until
|
||||
/// then it acknowledges the click and signals that the feature is on
|
||||
/// the way.
|
||||
/// Starts in-engine replay playback when the Watch Replay button is
|
||||
/// pressed. If no replay has been recorded yet, surfaces an
|
||||
/// [`InfoToastEvent`] instead. The playback path resets the live
|
||||
/// game to the recorded deal and ticks through the move list via
|
||||
/// [`crate::replay_playback`]; the [`crate::replay_overlay`] banner
|
||||
/// surfaces while playback runs.
|
||||
fn handle_watch_replay_button(
|
||||
mut commands: Commands,
|
||||
buttons: Query<&Interaction, (With<WatchReplayButton>, Changed<Interaction>)>,
|
||||
latest: Res<LatestReplayResource>,
|
||||
playback: Option<ResMut<crate::replay_playback::ReplayPlaybackState>>,
|
||||
mut toast: MessageWriter<InfoToastEvent>,
|
||||
) {
|
||||
if !buttons.iter().any(|i| *i == Interaction::Pressed) {
|
||||
return;
|
||||
}
|
||||
let message = match &latest.0 {
|
||||
Some(replay) => format!(
|
||||
"Replay ready ({}) \u{2014} web playback coming in a future build",
|
||||
format_replay_caption(replay),
|
||||
),
|
||||
None => "No replay recorded yet \u{2014} win a game first.".to_string(),
|
||||
};
|
||||
toast.write(InfoToastEvent(message));
|
||||
match (&latest.0, playback) {
|
||||
(Some(replay), Some(mut playback)) => {
|
||||
crate::replay_playback::start_replay_playback(
|
||||
&mut commands,
|
||||
&mut playback,
|
||||
replay.clone(),
|
||||
);
|
||||
}
|
||||
(Some(replay), None) => {
|
||||
// ReplayPlaybackPlugin not registered (headless test
|
||||
// fixtures); fall back to a descriptive toast.
|
||||
toast.write(InfoToastEvent(format!(
|
||||
"Replay ready ({})",
|
||||
format_replay_caption(replay)
|
||||
)));
|
||||
}
|
||||
(None, _) => {
|
||||
toast.write(InfoToastEvent(
|
||||
"No replay recorded yet \u{2014} win a game first.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure helper: render a one-line caption for a [`Replay`] suitable
|
||||
|
||||
Reference in New Issue
Block a user