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 ActionDetail(ActionDetailOverlay),
65}
66
67#[derive(Debug, Clone)]
69pub struct ActionDetailOverlay {
70 pub sequence: u64,
72 pub name: String,
74 pub params: String,
76 pub elapsed: String,
78}
79
80impl DebugOverlay {
81 pub fn table(&self) -> Option<&DebugTableOverlay> {
83 match self {
84 DebugOverlay::Inspect(table) | DebugOverlay::State(table) => Some(table),
85 DebugOverlay::ActionLog(_) | DebugOverlay::ActionDetail(_) => None,
86 }
87 }
88
89 pub fn action_log(&self) -> Option<&ActionLogOverlay> {
91 match self {
92 DebugOverlay::ActionLog(log) => Some(log),
93 _ => None,
94 }
95 }
96
97 pub fn action_log_mut(&mut self) -> Option<&mut ActionLogOverlay> {
99 match self {
100 DebugOverlay::ActionLog(log) => Some(log),
101 _ => None,
102 }
103 }
104
105 pub fn kind(&self) -> &'static str {
107 match self {
108 DebugOverlay::Inspect(_) => "inspect",
109 DebugOverlay::State(_) => "state",
110 DebugOverlay::ActionLog(_) => "action_log",
111 DebugOverlay::ActionDetail(_) => "action_detail",
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
122pub struct ActionLogDisplayEntry {
123 pub sequence: u64,
125 pub name: String,
127 pub params: String,
129 pub elapsed: String,
131}
132
133#[derive(Debug, Clone)]
135pub struct ActionLogOverlay {
136 pub title: String,
138 pub entries: Vec<ActionLogDisplayEntry>,
140 pub selected: usize,
142 pub scroll_offset: usize,
144}
145
146impl ActionLogOverlay {
147 pub fn from_log(log: &ActionLog, title: impl Into<String>) -> Self {
149 let entries: Vec<_> = log
150 .entries_rev()
151 .map(|e| ActionLogDisplayEntry {
152 sequence: e.sequence,
153 name: e.name.to_string(),
154 params: e.params.clone(),
155 elapsed: e.elapsed.clone(),
156 })
157 .collect();
158
159 Self {
160 title: title.into(),
161 entries,
162 selected: 0,
163 scroll_offset: 0,
164 }
165 }
166
167 pub fn scroll_up(&mut self) {
169 if self.selected > 0 {
170 self.selected -= 1;
171 }
172 }
173
174 pub fn scroll_down(&mut self) {
176 if self.selected + 1 < self.entries.len() {
177 self.selected += 1;
178 }
179 }
180
181 pub fn scroll_to_top(&mut self) {
183 self.selected = 0;
184 }
185
186 pub fn scroll_to_bottom(&mut self) {
188 if !self.entries.is_empty() {
189 self.selected = self.entries.len() - 1;
190 }
191 }
192
193 pub fn page_up(&mut self, page_size: usize) {
195 self.selected = self.selected.saturating_sub(page_size);
196 }
197
198 pub fn page_down(&mut self, page_size: usize) {
200 self.selected = (self.selected + page_size).min(self.entries.len().saturating_sub(1));
201 }
202
203 pub fn get_selected(&self) -> Option<&ActionLogDisplayEntry> {
205 self.entries.get(self.selected)
206 }
207
208 pub fn selected_detail(&self) -> Option<ActionDetailOverlay> {
210 self.get_selected().map(|entry| ActionDetailOverlay {
211 sequence: entry.sequence,
212 name: entry.name.clone(),
213 params: entry.params.clone(),
214 elapsed: entry.elapsed.clone(),
215 })
216 }
217}
218
219#[derive(Debug, Default)]
238pub struct DebugTableBuilder {
239 rows: Vec<DebugTableRow>,
240 cell_preview: Option<CellPreview>,
241}
242
243impl DebugTableBuilder {
244 pub fn new() -> Self {
246 Self::default()
247 }
248
249 pub fn section(mut self, title: impl Into<String>) -> Self {
251 self.rows.push(DebugTableRow::Section(title.into()));
252 self
253 }
254
255 pub fn entry(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
257 self.rows.push(DebugTableRow::Entry {
258 key: key.into(),
259 value: value.into(),
260 });
261 self
262 }
263
264 pub fn push_section(&mut self, title: impl Into<String>) {
266 self.rows.push(DebugTableRow::Section(title.into()));
267 }
268
269 pub fn push_entry(&mut self, key: impl Into<String>, value: impl Into<String>) {
271 self.rows.push(DebugTableRow::Entry {
272 key: key.into(),
273 value: value.into(),
274 });
275 }
276
277 pub fn cell_preview(mut self, preview: CellPreview) -> Self {
279 self.cell_preview = Some(preview);
280 self
281 }
282
283 pub fn set_cell_preview(&mut self, preview: CellPreview) {
285 self.cell_preview = Some(preview);
286 }
287
288 pub fn finish(self, title: impl Into<String>) -> DebugTableOverlay {
290 DebugTableOverlay {
291 title: title.into(),
292 rows: self.rows,
293 cell_preview: self.cell_preview,
294 }
295 }
296
297 pub fn finish_inspect(self, title: impl Into<String>) -> DebugOverlay {
299 DebugOverlay::Inspect(self.finish(title))
300 }
301
302 pub fn finish_state(self, title: impl Into<String>) -> DebugOverlay {
304 DebugOverlay::State(self.finish(title))
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_builder_basic() {
314 let table = DebugTableBuilder::new()
315 .section("Test")
316 .entry("key1", "value1")
317 .entry("key2", "value2")
318 .finish("Test Table");
319
320 assert_eq!(table.title, "Test Table");
321 assert_eq!(table.rows.len(), 3);
322 assert!(table.cell_preview.is_none());
323 }
324
325 #[test]
326 fn test_builder_multiple_sections() {
327 let table = DebugTableBuilder::new()
328 .section("Section 1")
329 .entry("a", "1")
330 .section("Section 2")
331 .entry("b", "2")
332 .finish("Multi-Section");
333
334 assert_eq!(table.rows.len(), 4);
335
336 match &table.rows[0] {
337 DebugTableRow::Section(s) => assert_eq!(s, "Section 1"),
338 _ => panic!("Expected section"),
339 }
340 match &table.rows[2] {
341 DebugTableRow::Section(s) => assert_eq!(s, "Section 2"),
342 _ => panic!("Expected section"),
343 }
344 }
345
346 #[test]
347 fn test_overlay_kinds() {
348 let table = DebugTableBuilder::new().finish("Test");
349
350 let inspect = DebugOverlay::Inspect(table.clone());
351 assert_eq!(inspect.kind(), "inspect");
352 assert!(inspect.table().is_some());
353 assert!(inspect.action_log().is_none());
354
355 let state = DebugOverlay::State(table);
356 assert_eq!(state.kind(), "state");
357
358 let action_log = ActionLogOverlay {
359 title: "Test".to_string(),
360 entries: vec![],
361 selected: 0,
362 scroll_offset: 0,
363 };
364 let log_overlay = DebugOverlay::ActionLog(action_log);
365 assert_eq!(log_overlay.kind(), "action_log");
366 assert!(log_overlay.table().is_none());
367 assert!(log_overlay.action_log().is_some());
368 }
369
370 #[test]
371 fn test_action_log_overlay_scrolling() {
372 let mut overlay = ActionLogOverlay {
373 title: "Test".to_string(),
374 entries: vec![
375 ActionLogDisplayEntry {
376 sequence: 0,
377 name: "A".to_string(),
378 params: "".to_string(),
379 elapsed: "0ms".to_string(),
380 },
381 ActionLogDisplayEntry {
382 sequence: 1,
383 name: "B".to_string(),
384 params: "x: 1".to_string(),
385 elapsed: "1ms".to_string(),
386 },
387 ActionLogDisplayEntry {
388 sequence: 2,
389 name: "C".to_string(),
390 params: "y: 2".to_string(),
391 elapsed: "2ms".to_string(),
392 },
393 ],
394 selected: 0,
395 scroll_offset: 0,
396 };
397
398 assert_eq!(overlay.selected, 0);
399
400 overlay.scroll_down();
401 assert_eq!(overlay.selected, 1);
402
403 overlay.scroll_down();
404 assert_eq!(overlay.selected, 2);
405
406 overlay.scroll_down(); assert_eq!(overlay.selected, 2);
408
409 overlay.scroll_up();
410 assert_eq!(overlay.selected, 1);
411
412 overlay.scroll_to_top();
413 assert_eq!(overlay.selected, 0);
414
415 overlay.scroll_to_bottom();
416 assert_eq!(overlay.selected, 2);
417 }
418}