tui_dispatch_debug/debug/
table.rs1use 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 params_detail: String,
131 pub elapsed: String,
133}
134
135#[derive(Debug, Clone)]
137pub struct ActionLogOverlay {
138 pub title: String,
140 pub entries: Vec<ActionLogDisplayEntry>,
142 pub selected: usize,
144 pub scroll_offset: usize,
146}
147
148impl ActionLogOverlay {
149 pub fn from_log(log: &ActionLog, title: impl Into<String>) -> Self {
151 let entries: Vec<_> = log
152 .entries_rev()
153 .map(|e| ActionLogDisplayEntry {
154 sequence: e.sequence,
155 name: e.name.to_string(),
156 params: e.params.clone(),
157 params_detail: e.params_pretty.clone(),
158 elapsed: e.elapsed.clone(),
159 })
160 .collect();
161
162 Self {
163 title: title.into(),
164 entries,
165 selected: 0,
166 scroll_offset: 0,
167 }
168 }
169
170 pub fn scroll_up(&mut self) {
172 if self.selected > 0 {
173 self.selected -= 1;
174 }
175 }
176
177 pub fn scroll_down(&mut self) {
179 if self.selected + 1 < self.entries.len() {
180 self.selected += 1;
181 }
182 }
183
184 pub fn scroll_to_top(&mut self) {
186 self.selected = 0;
187 }
188
189 pub fn scroll_to_bottom(&mut self) {
191 if !self.entries.is_empty() {
192 self.selected = self.entries.len() - 1;
193 }
194 }
195
196 pub fn page_up(&mut self, page_size: usize) {
198 self.selected = self.selected.saturating_sub(page_size);
199 }
200
201 pub fn page_down(&mut self, page_size: usize) {
203 self.selected = (self.selected + page_size).min(self.entries.len().saturating_sub(1));
204 }
205
206 pub fn scroll_offset_for(&self, visible_rows: usize) -> usize {
208 if visible_rows == 0 {
209 return 0;
210 }
211 if self.selected >= visible_rows {
212 self.selected - visible_rows + 1
213 } else {
214 0
215 }
216 }
217
218 pub fn get_selected(&self) -> Option<&ActionLogDisplayEntry> {
220 self.entries.get(self.selected)
221 }
222
223 pub fn selected_detail(&self) -> Option<ActionDetailOverlay> {
225 self.get_selected().map(|entry| ActionDetailOverlay {
226 sequence: entry.sequence,
227 name: entry.name.clone(),
228 params: entry.params_detail.clone(),
229 elapsed: entry.elapsed.clone(),
230 })
231 }
232}
233
234#[derive(Debug, Default)]
253pub struct DebugTableBuilder {
254 rows: Vec<DebugTableRow>,
255 cell_preview: Option<CellPreview>,
256}
257
258impl DebugTableBuilder {
259 pub fn new() -> Self {
261 Self::default()
262 }
263
264 pub fn section(mut self, title: impl Into<String>) -> Self {
266 self.rows.push(DebugTableRow::Section(title.into()));
267 self
268 }
269
270 pub fn entry(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
272 self.rows.push(DebugTableRow::Entry {
273 key: key.into(),
274 value: value.into(),
275 });
276 self
277 }
278
279 pub fn push_section(&mut self, title: impl Into<String>) {
281 self.rows.push(DebugTableRow::Section(title.into()));
282 }
283
284 pub fn push_entry(&mut self, key: impl Into<String>, value: impl Into<String>) {
286 self.rows.push(DebugTableRow::Entry {
287 key: key.into(),
288 value: value.into(),
289 });
290 }
291
292 pub fn cell_preview(mut self, preview: CellPreview) -> Self {
294 self.cell_preview = Some(preview);
295 self
296 }
297
298 pub fn set_cell_preview(&mut self, preview: CellPreview) {
300 self.cell_preview = Some(preview);
301 }
302
303 pub fn finish(self, title: impl Into<String>) -> DebugTableOverlay {
305 DebugTableOverlay {
306 title: title.into(),
307 rows: self.rows,
308 cell_preview: self.cell_preview,
309 }
310 }
311
312 pub fn finish_inspect(self, title: impl Into<String>) -> DebugOverlay {
314 DebugOverlay::Inspect(self.finish(title))
315 }
316
317 pub fn finish_state(self, title: impl Into<String>) -> DebugOverlay {
319 DebugOverlay::State(self.finish(title))
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_builder_basic() {
329 let table = DebugTableBuilder::new()
330 .section("Test")
331 .entry("key1", "value1")
332 .entry("key2", "value2")
333 .finish("Test Table");
334
335 assert_eq!(table.title, "Test Table");
336 assert_eq!(table.rows.len(), 3);
337 assert!(table.cell_preview.is_none());
338 }
339
340 #[test]
341 fn test_builder_multiple_sections() {
342 let table = DebugTableBuilder::new()
343 .section("Section 1")
344 .entry("a", "1")
345 .section("Section 2")
346 .entry("b", "2")
347 .finish("Multi-Section");
348
349 assert_eq!(table.rows.len(), 4);
350
351 match &table.rows[0] {
352 DebugTableRow::Section(s) => assert_eq!(s, "Section 1"),
353 _ => panic!("Expected section"),
354 }
355 match &table.rows[2] {
356 DebugTableRow::Section(s) => assert_eq!(s, "Section 2"),
357 _ => panic!("Expected section"),
358 }
359 }
360
361 #[test]
362 fn test_overlay_kinds() {
363 let table = DebugTableBuilder::new().finish("Test");
364
365 let inspect = DebugOverlay::Inspect(table.clone());
366 assert_eq!(inspect.kind(), "inspect");
367 assert!(inspect.table().is_some());
368 assert!(inspect.action_log().is_none());
369
370 let state = DebugOverlay::State(table);
371 assert_eq!(state.kind(), "state");
372
373 let action_log = ActionLogOverlay {
374 title: "Test".to_string(),
375 entries: vec![],
376 selected: 0,
377 scroll_offset: 0,
378 };
379 let log_overlay = DebugOverlay::ActionLog(action_log);
380 assert_eq!(log_overlay.kind(), "action_log");
381 assert!(log_overlay.table().is_none());
382 assert!(log_overlay.action_log().is_some());
383 }
384
385 #[test]
386 fn test_action_log_overlay_scrolling() {
387 let mut overlay = ActionLogOverlay {
388 title: "Test".to_string(),
389 entries: vec![
390 ActionLogDisplayEntry {
391 sequence: 0,
392 name: "A".to_string(),
393 params: "".to_string(),
394 params_detail: "".to_string(),
395 elapsed: "0ms".to_string(),
396 },
397 ActionLogDisplayEntry {
398 sequence: 1,
399 name: "B".to_string(),
400 params: "x: 1".to_string(),
401 params_detail: "x: 1".to_string(),
402 elapsed: "1ms".to_string(),
403 },
404 ActionLogDisplayEntry {
405 sequence: 2,
406 name: "C".to_string(),
407 params: "y: 2".to_string(),
408 params_detail: "y: 2".to_string(),
409 elapsed: "2ms".to_string(),
410 },
411 ],
412 selected: 0,
413 scroll_offset: 0,
414 };
415
416 assert_eq!(overlay.selected, 0);
417
418 overlay.scroll_down();
419 assert_eq!(overlay.selected, 1);
420
421 overlay.scroll_down();
422 assert_eq!(overlay.selected, 2);
423
424 overlay.scroll_down(); assert_eq!(overlay.selected, 2);
426
427 overlay.scroll_up();
428 assert_eq!(overlay.selected, 1);
429
430 overlay.scroll_to_top();
431 assert_eq!(overlay.selected, 0);
432
433 overlay.scroll_to_bottom();
434 assert_eq!(overlay.selected, 2);
435 }
436}