Skip to main content

egui_charts/ui/replay/
status.rs

1//! Replay progress and status display.
2//!
3//! Pass host-managed [`ReplayStatus`] to [`ReplayStatusIndicator`] to show playback
4//! state, progress and optional execution counts.
5
6use super::model::ReplayProgress;
7use crate::icons::icons;
8use crate::localization as l10n;
9use crate::styles::icons as icon_sizes;
10use crate::theme::ThemeExt;
11use egui::{Response, RichText, Sense, Ui, Vec2, Widget};
12
13/// Replay status for the indicator
14#[derive(Clone, Debug, Default)]
15pub enum ReplayStatus {
16    /// Not replaying (using live data or sample)
17    #[default]
18    Inactive,
19    /// Actively replaying tick file
20    Playing {
21        /// Symbol being replayed
22        symbol: String,
23        /// Progress (0.0 - 100.0)
24        progress_pct: f32,
25        /// Ticks processed
26        ticks_processed: u64,
27        /// Replay speed multiplier
28        speed: f32,
29    },
30    /// Replay paused
31    Paused {
32        /// Instrument identifier used by the host.
33        symbol: String,
34        /// Playback progress in percent.
35        progress_pct: f32,
36    },
37    /// Replay finished
38    Finished {
39        /// Instrument identifier used by the host.
40        symbol: String,
41        /// Total number of executions processed by the finished replay.
42        total_ticks: u64,
43    },
44}
45
46impl From<(&ReplayProgress, &str, f32)> for ReplayStatus {
47    fn from((progress, symbol, speed): (&ReplayProgress, &str, f32)) -> Self {
48        if progress.progress_pct >= 99.9 {
49            ReplayStatus::Finished {
50                symbol: symbol.to_string(),
51                total_ticks: progress.ticks_processed,
52            }
53        } else {
54            ReplayStatus::Playing {
55                symbol: symbol.to_string(),
56                progress_pct: progress.progress_pct,
57                ticks_processed: progress.ticks_processed,
58                speed,
59            }
60        }
61    }
62}
63
64/// Replay status indicator widget
65pub struct ReplayStatusIndicator<'a> {
66    /// Current replay status
67    status: &'a ReplayStatus,
68    /// Whether to show detailed info
69    show_details: bool,
70}
71
72impl<'a> ReplayStatusIndicator<'a> {
73    /// Create a new replay status indicator
74    pub fn new(status: &'a ReplayStatus) -> Self {
75        Self {
76            status,
77            show_details: false,
78        }
79    }
80
81    /// Show detailed info (progress, speed)
82    #[must_use]
83    pub fn with_details(mut self) -> Self {
84        self.show_details = true;
85        self
86    }
87}
88
89impl<'a> Widget for ReplayStatusIndicator<'a> {
90    fn ui(self, ui: &mut Ui) -> Response {
91        let theme = ui.active_theme();
92        let (muted, size) = (theme.ui.text_muted, theme.text.small);
93        match self.status {
94            // Takes no space when not replaying.
95            ReplayStatus::Inactive => ui.allocate_response(Vec2::ZERO, Sense::hover()),
96            ReplayStatus::Playing {
97                symbol: _,
98                progress_pct,
99                ticks_processed,
100                speed,
101            } => {
102                ui.horizontal(|ui| {
103                    ui.spacing_mut().item_spacing.x = theme.spacing.xs;
104
105                    // Replay indicator - icon + text
106                    ui.add(icons::MEDIA_PLAY.as_image_tinted(
107                        Vec2::splat(icon_sizes::STATUS_INDICATOR),
108                        theme.ui.warning,
109                    ));
110                    ui.label(
111                        RichText::new(l10n::text(ui.ctx(), "Replay"))
112                            .color(theme.ui.warning)
113                            .strong()
114                            .size(size),
115                    );
116
117                    if self.show_details {
118                        // Progress
119                        ui.label(
120                            RichText::new(format!("{progress_pct:.1}%"))
121                                .color(muted)
122                                .size(size),
123                        );
124
125                        // Speed
126                        ui.label(
127                            RichText::new(l10n::text(ui.ctx(), format!("{speed}x")))
128                                .color(muted)
129                                .size(size),
130                        );
131
132                        // Ticks
133                        ui.label(
134                            RichText::new(l10n::format_text(
135                                ui.ctx(),
136                                "{0}K ticks",
137                                &[("0", format!("{}", ticks_processed / 1000))],
138                            ))
139                            .color(muted)
140                            .size(size),
141                        );
142                    }
143                })
144                .response
145            }
146            ReplayStatus::Paused {
147                symbol: _,
148                progress_pct,
149            } => {
150                ui.horizontal(|ui| {
151                    ui.spacing_mut().item_spacing.x = theme.spacing.xs;
152
153                    // Paused indicator - icon + text
154                    ui.add(
155                        icons::MEDIA_PAUSE
156                            .as_image_tinted(Vec2::splat(icon_sizes::STATUS_INDICATOR), muted),
157                    );
158                    ui.label(
159                        RichText::new(l10n::text(ui.ctx(), "Paused"))
160                            .color(muted)
161                            .size(size),
162                    );
163
164                    if self.show_details {
165                        ui.label(
166                            RichText::new(format!("{progress_pct:.1}%"))
167                                .color(muted)
168                                .size(size),
169                        );
170                    }
171                })
172                .response
173            }
174            ReplayStatus::Finished {
175                symbol: _,
176                total_ticks,
177            } => {
178                ui.horizontal(|ui| {
179                    ui.spacing_mut().item_spacing.x = theme.spacing.xs;
180
181                    // Complete indicator - icon + text
182                    ui.add(icons::SUCCESS.as_image_tinted(
183                        Vec2::splat(icon_sizes::STATUS_INDICATOR),
184                        theme.ui.success,
185                    ));
186                    ui.label(
187                        RichText::new(l10n::text(ui.ctx(), "Complete"))
188                            .color(theme.ui.success)
189                            .size(size),
190                    );
191
192                    if self.show_details {
193                        ui.label(
194                            RichText::new(l10n::format_text(
195                                ui.ctx(),
196                                "{0}K ticks",
197                                &[("0", format!("{}", total_ticks / 1000))],
198                            ))
199                            .color(muted)
200                            .size(size),
201                        );
202                    }
203                })
204                .response
205            }
206        }
207    }
208}
209
210/// Show a replay status indicator in the UI
211pub fn show_replay_status(ui: &mut Ui, status: &ReplayStatus) -> Response {
212    ReplayStatusIndicator::new(status).ui(ui)
213}
214
215/// Show a replay status indicator with details
216pub fn show_replay_status_with_details(ui: &mut Ui, status: &ReplayStatus) -> Response {
217    ReplayStatusIndicator::new(status).with_details().ui(ui)
218}