1use super::action_logger::ActionLog;
8use super::cell::CellPreview;
9
10#[derive(Debug, Clone)]
12pub enum DebugTableRow {
13 Section(String),
15 Entry { key: String, value: String },
17}
18
19#[derive(Debug, Clone)]
21pub struct DebugTableOverlay {
22 pub title: String,
24 pub rows: Vec<DebugTableRow>,
26 pub cell_preview: Option<CellPreview>,
28}
29
30impl DebugTableOverlay {
31 pub fn new(title: impl Into<String>, rows: Vec<DebugTableRow>) -> Self {
33 Self {
34 title: title.into(),
35 rows,
36 cell_preview: None,
37 }
38 }
39
40 pub fn with_cell_preview(
42 title: impl Into<String>,
43 rows: Vec<DebugTableRow>,
44 preview: CellPreview,
45 ) -> Self {
46 Self {
47 title: title.into(),
48 rows,
49 cell_preview: Some(preview),
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
56pub enum DebugOverlay {
57 Inspect(DebugTableOverlay),
59 State(DebugTableOverlay),
61 ActionLog(ActionLogOverlay),
63}
64
65impl DebugOverlay {
66 pub fn table(&self) -> Option<&DebugTableOverlay> {
68 match self {
69 DebugOverlay::Inspect(table) | DebugOverlay::State(table) => Some(table),
70 DebugOverlay::ActionLog(_) => None,
71 }
72 }
73
74 pub fn action_log(&self) -> Option<&ActionLogOverlay> {
76 match self {
77 DebugOverlay::ActionLog(log) => Some(log),
78 _ => None,
79 }
80 }
81
82 pub fn action_log_mut(&mut self) -> Option<&mut ActionLogOverlay> {
84 match self {
85 DebugOverlay::ActionLog(log) => Some(log),
86 _ => None,
87 }
88 }
89
90 pub fn kind(&self) -> &'static str {
92 match self {
93 DebugOverlay::Inspect(_) => "inspect",
94 DebugOverlay::State(_) => "state",
95 DebugOverlay::ActionLog(_) => "action_log",
96 }
97 }
98}
99
100#[derive(Debug, Clone)]
106pub struct ActionLogDisplayEntry {
107 pub sequence: u64,
109 pub name: String,
111 pub summary: String,
113 pub elapsed: String,
115 pub state_changed: Option<bool>,
117}
118
119#[derive(Debug, Clone)]
121pub struct ActionLogOverlay {
122 pub title: String,
124 pub entries: Vec<ActionLogDisplayEntry>,
126 pub selected: usize,
128 pub scroll_offset: usize,
130}
131
132impl ActionLogOverlay {
133 pub fn from_log(log: &ActionLog, title: impl Into<String>) -> Self {
135 let entries: Vec<_> = log
136 .entries_rev()
137 .map(|e| ActionLogDisplayEntry {
138 sequence: e.sequence,
139 name: e.name.to_string(),
140 summary: e.summary.clone(),
141 elapsed: e.elapsed_display(),
142 state_changed: e.state_changed,
143 })
144 .collect();
145
146 Self {
147 title: title.into(),
148 entries,
149 selected: 0,
150 scroll_offset: 0,
151 }
152 }
153
154 pub fn scroll_up(&mut self) {
156 if self.selected > 0 {
157 self.selected -= 1;
158 }
159 }
160
161 pub fn scroll_down(&mut self) {
163 if self.selected + 1 < self.entries.len() {
164 self.selected += 1;
165 }
166 }
167
168 pub fn scroll_to_top(&mut self) {
170 self.selected = 0;
171 }
172
173 pub fn scroll_to_bottom(&mut self) {
175 if !self.entries.is_empty() {
176 self.selected = self.entries.len() - 1;
177 }
178 }
179
180 pub fn page_up(&mut self, page_size: usize) {
182 self.selected = self.selected.saturating_sub(page_size);
183 }
184
185 pub fn page_down(&mut self, page_size: usize) {
187 self.selected = (self.selected + page_size).min(self.entries.len().saturating_sub(1));
188 }
189}
190
191#[derive(Debug, Default)]
210pub struct DebugTableBuilder {
211 rows: Vec<DebugTableRow>,
212 cell_preview: Option<CellPreview>,
213}
214
215impl DebugTableBuilder {
216 pub fn new() -> Self {
218 Self::default()
219 }
220
221 pub fn section(mut self, title: impl Into<String>) -> Self {
223 self.rows.push(DebugTableRow::Section(title.into()));
224 self
225 }
226
227 pub fn entry(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
229 self.rows.push(DebugTableRow::Entry {
230 key: key.into(),
231 value: value.into(),
232 });
233 self
234 }
235
236 pub fn push_section(&mut self, title: impl Into<String>) {
238 self.rows.push(DebugTableRow::Section(title.into()));
239 }
240
241 pub fn push_entry(&mut self, key: impl Into<String>, value: impl Into<String>) {
243 self.rows.push(DebugTableRow::Entry {
244 key: key.into(),
245 value: value.into(),
246 });
247 }
248
249 pub fn cell_preview(mut self, preview: CellPreview) -> Self {
251 self.cell_preview = Some(preview);
252 self
253 }
254
255 pub fn set_cell_preview(&mut self, preview: CellPreview) {
257 self.cell_preview = Some(preview);
258 }
259
260 pub fn finish(self, title: impl Into<String>) -> DebugTableOverlay {
262 DebugTableOverlay {
263 title: title.into(),
264 rows: self.rows,
265 cell_preview: self.cell_preview,
266 }
267 }
268
269 pub fn finish_inspect(self, title: impl Into<String>) -> DebugOverlay {
271 DebugOverlay::Inspect(self.finish(title))
272 }
273
274 pub fn finish_state(self, title: impl Into<String>) -> DebugOverlay {
276 DebugOverlay::State(self.finish(title))
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_builder_basic() {
286 let table = DebugTableBuilder::new()
287 .section("Test")
288 .entry("key1", "value1")
289 .entry("key2", "value2")
290 .finish("Test Table");
291
292 assert_eq!(table.title, "Test Table");
293 assert_eq!(table.rows.len(), 3);
294 assert!(table.cell_preview.is_none());
295 }
296
297 #[test]
298 fn test_builder_multiple_sections() {
299 let table = DebugTableBuilder::new()
300 .section("Section 1")
301 .entry("a", "1")
302 .section("Section 2")
303 .entry("b", "2")
304 .finish("Multi-Section");
305
306 assert_eq!(table.rows.len(), 4);
307
308 match &table.rows[0] {
309 DebugTableRow::Section(s) => assert_eq!(s, "Section 1"),
310 _ => panic!("Expected section"),
311 }
312 match &table.rows[2] {
313 DebugTableRow::Section(s) => assert_eq!(s, "Section 2"),
314 _ => panic!("Expected section"),
315 }
316 }
317
318 #[test]
319 fn test_overlay_kinds() {
320 let table = DebugTableBuilder::new().finish("Test");
321
322 let inspect = DebugOverlay::Inspect(table.clone());
323 assert_eq!(inspect.kind(), "inspect");
324 assert!(inspect.table().is_some());
325 assert!(inspect.action_log().is_none());
326
327 let state = DebugOverlay::State(table);
328 assert_eq!(state.kind(), "state");
329
330 let action_log = ActionLogOverlay {
331 title: "Test".to_string(),
332 entries: vec![],
333 selected: 0,
334 scroll_offset: 0,
335 };
336 let log_overlay = DebugOverlay::ActionLog(action_log);
337 assert_eq!(log_overlay.kind(), "action_log");
338 assert!(log_overlay.table().is_none());
339 assert!(log_overlay.action_log().is_some());
340 }
341
342 #[test]
343 fn test_action_log_overlay_scrolling() {
344 let mut overlay = ActionLogOverlay {
345 title: "Test".to_string(),
346 entries: vec![
347 ActionLogDisplayEntry {
348 sequence: 0,
349 name: "A".to_string(),
350 summary: "A".to_string(),
351 elapsed: "0ms".to_string(),
352 state_changed: None,
353 },
354 ActionLogDisplayEntry {
355 sequence: 1,
356 name: "B".to_string(),
357 summary: "B".to_string(),
358 elapsed: "1ms".to_string(),
359 state_changed: Some(true),
360 },
361 ActionLogDisplayEntry {
362 sequence: 2,
363 name: "C".to_string(),
364 summary: "C".to_string(),
365 elapsed: "2ms".to_string(),
366 state_changed: Some(false),
367 },
368 ],
369 selected: 0,
370 scroll_offset: 0,
371 };
372
373 assert_eq!(overlay.selected, 0);
374
375 overlay.scroll_down();
376 assert_eq!(overlay.selected, 1);
377
378 overlay.scroll_down();
379 assert_eq!(overlay.selected, 2);
380
381 overlay.scroll_down(); assert_eq!(overlay.selected, 2);
383
384 overlay.scroll_up();
385 assert_eq!(overlay.selected, 1);
386
387 overlay.scroll_to_top();
388 assert_eq!(overlay.selected, 0);
389
390 overlay.scroll_to_bottom();
391 assert_eq!(overlay.selected, 2);
392 }
393}