launcher/widgets/
replay_row.rs1use eframe::egui;
2use egui_extras::TableRow;
3use std::path::Path;
4use supreme_replay_analyzer::AnalyzeResult;
5
6pub enum ReplayRowAction {
7 CreateSnapshot,
8}
9
10pub fn render_replay_row(
11 row: &mut TableRow,
12 path: &Path,
13 result: &Result<AnalyzeResult, String>,
14 is_advanced_mode: bool,
15) -> Option<ReplayRowAction> {
16 let mut action = None;
17
18 row.col(|ui| {
20 if ui
21 .button("🔗")
22 .on_hover_text("Save and share replay")
23 .clicked()
24 {
25 action = Some(ReplayRowAction::CreateSnapshot);
26 }
27 });
28 row.col(|ui| {
30 let file_name = path.file_name().unwrap_or_default().to_string_lossy();
31 ui.label(file_name).on_hover_text(path.to_string_lossy());
32 });
33
34 match result {
35 Ok(analysis) => {
36 row.col(|ui| {
38 ui.label(&analysis.player_name);
39 });
40 row.col(|ui| {
42 ui.label(analysis.track_name.as_deref().unwrap_or("Unknown"));
43 });
44
45 if let Some(checkpoint_times) = &analysis.checkpoint_times {
46 row.col(|ui| {
48 display_checkpoint_time(
49 ui,
50 Some(analysis.start_ms),
51 checkpoint_times.start_computed,
52 is_advanced_mode,
53 );
54 });
55
56 row.col(|ui| {
58 display_checkpoint_time(
59 ui,
60 Some(analysis.checkpoint1_ms),
61 checkpoint_times
62 .cp1_computed
63 .zip(checkpoint_times.start_computed)
64 .map(|(cp, start)| cp - start),
65 is_advanced_mode,
66 );
67 });
68
69 if is_advanced_mode {
71 row.col(|ui| {
72 display_checkpoint_time(
73 ui,
74 None,
75 checkpoint_times
76 .cp2_computed
77 .zip(checkpoint_times.start_computed)
78 .map(|(cp, start)| cp - start),
79 is_advanced_mode,
80 );
81 });
82 }
83
84 row.col(|ui| {
86 display_checkpoint_time(
87 ui,
88 Some(analysis.displayed_ms),
89 checkpoint_times
90 .finish_computed
91 .zip(checkpoint_times.start_computed)
92 .map(|(finish, start)| finish - start),
93 is_advanced_mode,
94 );
95 });
96 } else {
97 row.col(|ui| {
99 display_checkpoint_time(ui, Some(analysis.start_ms), None, is_advanced_mode);
100 });
101
102 row.col(|ui| {
104 display_checkpoint_time(
105 ui,
106 Some(analysis.checkpoint1_ms),
107 None,
108 is_advanced_mode,
109 );
110 });
111
112 if is_advanced_mode {
114 row.col(|ui| {
115 display_checkpoint_time(ui, None, None, is_advanced_mode);
116 });
117 }
118
119 row.col(|ui| {
121 display_checkpoint_time(
122 ui,
123 Some(analysis.displayed_ms),
124 None,
125 is_advanced_mode,
126 );
127 });
128 }
129
130 row.col(|ui| {
132 if !is_advanced_mode {
133 ui.label(format_time(analysis.total_ms, 2));
134 } else if let Some(checkpoint_times) = &analysis.checkpoint_times
135 && let Some(finish_time) = checkpoint_times.finish_computed
136 {
137 display_checkpoint_time(
138 ui,
139 Some(analysis.total_ms),
140 Some(finish_time),
141 is_advanced_mode,
142 );
143 } else {
144 ui.label(format_time(analysis.total_ms, 2));
145 }
146 });
147
148 if is_advanced_mode {
149 row.col(|ui| {
151 ui.label(format_time(analysis.lag_start_ms, 2));
152 });
153 row.col(|ui| {
155 ui.label(format_time(analysis.lag_finish_ms, 2));
156 });
157 row.col(|ui| {
159 ui.label(format_time(analysis.recording_ms, 2));
160 });
161 }
162 }
163 Err(e) => {
164 row.col(|ui| {
165 ui.colored_label(egui::Color32::RED, "Error");
166 });
167 row.col(|ui| {
168 ui.colored_label(egui::Color32::RED, e);
169 });
170
171 let remaining = if is_advanced_mode { 8 } else { 3 };
174
175 for _ in 0..remaining {
176 row.col(|ui| {
177 ui.label("-");
178 });
179 }
180 }
181 }
182 action
183}
184
185fn display_checkpoint_time(
186 ui: &mut egui::Ui,
187 dat_time: Option<f32>,
188 computed_time: Option<f32>,
189 is_advanced_mode: bool,
190) {
191 let show_dat = |ui: &mut egui::Ui, dat: Option<f32>| {
193 match dat {
194 Some(t) => ui.label(egui::RichText::new(format_time(t, 2)).strong()),
195 None => ui.label(egui::RichText::new("-").strong()),
196 };
197 };
198
199 let show_computed = |ui: &mut egui::Ui, dat: Option<f32>, comp: Option<f32>| match (dat, comp) {
201 (Some(dat), Some(comp)) => {
202 let delta = comp - dat;
203 let sign = if delta >= 0.0 { "+" } else { "" };
204 ui.label(format!(
205 "{} ({sign}{:.3}s)",
206 format_time(comp, 3),
207 delta / 1000.0
208 ));
209 }
210 (None, Some(comp)) => {
211 ui.label(format_time(comp, 3));
212 }
213 _ => {}
214 };
215
216 if is_advanced_mode {
217 ui.vertical(|ui| {
218 show_dat(ui, dat_time);
219 show_computed(ui, dat_time, computed_time);
220 });
221 } else {
222 show_dat(ui, dat_time);
223 }
224}
225
226fn format_time(ms: f32, precision: usize) -> String {
227 let total_s = ms / 1000.0;
228 let abs_s = total_s.abs();
229 if abs_s >= 60.0 {
230 let minutes = (abs_s / 60.0).floor() as i32;
231 let seconds = abs_s % 60.0;
232 let sign = if total_s < 0.0 { "-" } else { "" };
233 if precision == 3 {
234 format!("{sign}{minutes}:{seconds:06.3}")
235 } else {
236 format!("{sign}{minutes}:{seconds:05.2}")
237 }
238 } else {
239 format!("{total_s:.precision$}s")
240 }
241}