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 scroll_offset_for(&self, visible_rows: usize) -> usize {
205 if visible_rows == 0 {
206 return 0;
207 }
208 if self.selected >= visible_rows {
209 self.selected - visible_rows + 1
210 } else {
211 0
212 }
213 }
214
215 pub fn get_selected(&self) -> Option<&ActionLogDisplayEntry> {
217 self.entries.get(self.selected)
218 }
219
220 pub fn selected_detail(&self) -> Option<ActionDetailOverlay> {
222 self.get_selected().map(|entry| ActionDetailOverlay {
223 sequence: entry.sequence,
224 name: entry.name.clone(),
225 params: entry.params.clone(),
226 elapsed: entry.elapsed.clone(),
227 })
228 }
229}
230
231#[derive(Debug, Default)]
250pub struct DebugTableBuilder {
251 rows: Vec<DebugTableRow>,
252 cell_preview: Option<CellPreview>,
253}
254
255impl DebugTableBuilder {
256 pub fn new() -> Self {
258 Self::default()
259 }
260
261 pub fn section(mut self, title: impl Into<String>) -> Self {
263 self.rows.push(DebugTableRow::Section(title.into()));
264 self
265 }
266
267 pub fn entry(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
269 self.rows.push(DebugTableRow::Entry {
270 key: key.into(),
271 value: value.into(),
272 });
273 self
274 }
275
276 pub fn push_section(&mut self, title: impl Into<String>) {
278 self.rows.push(DebugTableRow::Section(title.into()));
279 }
280
281 pub fn push_entry(&mut self, key: impl Into<String>, value: impl Into<String>) {
283 self.rows.push(DebugTableRow::Entry {
284 key: key.into(),
285 value: value.into(),
286 });
287 }
288
289 pub fn cell_preview(mut self, preview: CellPreview) -> Self {
291 self.cell_preview = Some(preview);
292 self
293 }
294
295 pub fn set_cell_preview(&mut self, preview: CellPreview) {
297 self.cell_preview = Some(preview);
298 }
299
300 pub fn finish(self, title: impl Into<String>) -> DebugTableOverlay {
302 DebugTableOverlay {
303 title: title.into(),
304 rows: self.rows,
305 cell_preview: self.cell_preview,
306 }
307 }
308
309 pub fn finish_inspect(self, title: impl Into<String>) -> DebugOverlay {
311 DebugOverlay::Inspect(self.finish(title))
312 }
313
314 pub fn finish_state(self, title: impl Into<String>) -> DebugOverlay {
316 DebugOverlay::State(self.finish(title))
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_builder_basic() {
326 let table = DebugTableBuilder::new()
327 .section("Test")
328 .entry("key1", "value1")
329 .entry("key2", "value2")
330 .finish("Test Table");
331
332 assert_eq!(table.title, "Test Table");
333 assert_eq!(table.rows.len(), 3);
334 assert!(table.cell_preview.is_none());
335 }
336
337 #[test]
338 fn test_builder_multiple_sections() {
339 let table = DebugTableBuilder::new()
340 .section("Section 1")
341 .entry("a", "1")
342 .section("Section 2")
343 .entry("b", "2")
344 .finish("Multi-Section");
345
346 assert_eq!(table.rows.len(), 4);
347
348 match &table.rows[0] {
349 DebugTableRow::Section(s) => assert_eq!(s, "Section 1"),
350 _ => panic!("Expected section"),
351 }
352 match &table.rows[2] {
353 DebugTableRow::Section(s) => assert_eq!(s, "Section 2"),
354 _ => panic!("Expected section"),
355 }
356 }
357
358 #[test]
359 fn test_overlay_kinds() {
360 let table = DebugTableBuilder::new().finish("Test");
361
362 let inspect = DebugOverlay::Inspect(table.clone());
363 assert_eq!(inspect.kind(), "inspect");
364 assert!(inspect.table().is_some());
365 assert!(inspect.action_log().is_none());
366
367 let state = DebugOverlay::State(table);
368 assert_eq!(state.kind(), "state");
369
370 let action_log = ActionLogOverlay {
371 title: "Test".to_string(),
372 entries: vec![],
373 selected: 0,
374 scroll_offset: 0,
375 };
376 let log_overlay = DebugOverlay::ActionLog(action_log);
377 assert_eq!(log_overlay.kind(), "action_log");
378 assert!(log_overlay.table().is_none());
379 assert!(log_overlay.action_log().is_some());
380 }
381
382 #[test]
383 fn test_action_log_overlay_scrolling() {
384 let mut overlay = ActionLogOverlay {
385 title: "Test".to_string(),
386 entries: vec![
387 ActionLogDisplayEntry {
388 sequence: 0,
389 name: "A".to_string(),
390 params: "".to_string(),
391 elapsed: "0ms".to_string(),
392 },
393 ActionLogDisplayEntry {
394 sequence: 1,
395 name: "B".to_string(),
396 params: "x: 1".to_string(),
397 elapsed: "1ms".to_string(),
398 },
399 ActionLogDisplayEntry {
400 sequence: 2,
401 name: "C".to_string(),
402 params: "y: 2".to_string(),
403 elapsed: "2ms".to_string(),
404 },
405 ],
406 selected: 0,
407 scroll_offset: 0,
408 };
409
410 assert_eq!(overlay.selected, 0);
411
412 overlay.scroll_down();
413 assert_eq!(overlay.selected, 1);
414
415 overlay.scroll_down();
416 assert_eq!(overlay.selected, 2);
417
418 overlay.scroll_down(); assert_eq!(overlay.selected, 2);
420
421 overlay.scroll_up();
422 assert_eq!(overlay.selected, 1);
423
424 overlay.scroll_to_top();
425 assert_eq!(overlay.selected, 0);
426
427 overlay.scroll_to_bottom();
428 assert_eq!(overlay.selected, 2);
429 }
430}