Skip to main content

ane/frontend/tui/
list_dialog.rs

1use ratatui::{
2    Frame,
3    layout::Rect,
4    style::{Color, Modifier, Style},
5    text::{Line, Span},
6    widgets::{Block, Borders, Clear, List, ListItem, ListState},
7};
8
9use crate::commands::chord_engine::types::ListItem as ChordListItem;
10
11#[derive(Debug, Clone)]
12pub struct ListDialog {
13    pub items: Vec<ChordListItem>,
14    pub selected: usize,
15    pub scroll_offset: usize,
16}
17
18impl ListDialog {
19    pub fn new(items: Vec<ChordListItem>) -> Self {
20        Self {
21            items,
22            selected: 0,
23            scroll_offset: 0,
24        }
25    }
26
27    pub fn move_up(&mut self) {
28        if self.selected > 0 {
29            self.selected -= 1;
30        }
31    }
32
33    pub fn move_down(&mut self) {
34        if !self.items.is_empty() && self.selected < self.items.len() - 1 {
35            self.selected += 1;
36        }
37    }
38}
39
40pub fn render(frame: &mut Frame, dialog: &ListDialog) {
41    let area = frame.area();
42    let width = (area.width * 3 / 4).max(30).min(area.width);
43    let height = (area.height * 3 / 4).max(10).min(area.height);
44    let x = (area.width.saturating_sub(width)) / 2;
45    let y = (area.height.saturating_sub(height)) / 2;
46    let dialog_area = Rect::new(x, y, width, height);
47
48    frame.render_widget(Clear, dialog_area);
49
50    if dialog.items.is_empty() {
51        let block = Block::default()
52            .title(" List Results ")
53            .borders(Borders::ALL)
54            .style(Style::default().fg(Color::White).bg(Color::DarkGray));
55        let inner = block.inner(dialog_area);
56        frame.render_widget(block, dialog_area);
57        let msg = List::new(vec![ListItem::new("No results")]);
58        frame.render_widget(msg, inner);
59        return;
60    }
61
62    let block = Block::default()
63        .title(" List Results ")
64        .borders(Borders::ALL)
65        .style(Style::default().fg(Color::White).bg(Color::DarkGray));
66    let inner = block.inner(dialog_area);
67    frame.render_widget(block, dialog_area);
68
69    let visible_height = inner.height as usize;
70    let items: Vec<ListItem> = dialog
71        .items
72        .iter()
73        .enumerate()
74        .map(|(i, item)| {
75            let line_num = format!("{:>4}:{:<3}", item.line + 1, item.col + 1);
76            let style = if i == dialog.selected {
77                Style::default()
78                    .fg(Color::Black)
79                    .bg(Color::Cyan)
80                    .add_modifier(Modifier::BOLD)
81            } else {
82                Style::default().fg(Color::White)
83            };
84            ListItem::new(Line::from(vec![
85                Span::styled(
86                    line_num,
87                    Style::default().fg(if i == dialog.selected {
88                        Color::Black
89                    } else {
90                        Color::DarkGray
91                    }),
92                ),
93                Span::raw("  "),
94                Span::styled(&item.val, style),
95            ]))
96            .style(style)
97        })
98        .collect();
99
100    let mut list_state = ListState::default();
101    list_state.select(Some(dialog.selected));
102
103    let list = List::new(items).highlight_style(
104        Style::default()
105            .fg(Color::Black)
106            .bg(Color::Cyan)
107            .add_modifier(Modifier::BOLD),
108    );
109
110    // Adjust offset for scrolling
111    let _ = visible_height;
112    frame.render_stateful_widget(list, inner, &mut list_state);
113}