Skip to main content

widget_grid_selector/
widget_grid_selector.rs

1use std::io;
2
3use {
4    crossterm::{
5        event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
6        execute,
7        terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
8    },
9    matetui::widgets::gridselector::{GridItem, GridSelector, GridSelectorState},
10    ratatui::{
11        layout::{Alignment, Constraint, Direction, Layout, Rect},
12        prelude::CrosstermBackend,
13        style::{Color, Modifier, Style, Stylize},
14        widgets::Paragraph,
15        Frame, Terminal,
16    },
17};
18
19struct Item {
20    name: String,
21    emoji: String,
22}
23
24impl Item {
25    fn new(name: &str, emoji: &str) -> Self {
26        Item {
27            name: name.to_string(),
28            emoji: emoji.to_string(),
29        }
30    }
31}
32
33impl From<Item> for GridItem {
34    fn from(val: Item) -> Self {
35        GridItem::new(format!("{} {}", val.name, val.emoji))
36    }
37}
38
39fn main() -> io::Result<()> {
40    let stdout = io::stdout();
41    let mut stdout = stdout.lock();
42    enable_raw_mode()?;
43    execute!(stdout, EnterAlternateScreen)?;
44    let backend = CrosstermBackend::new(stdout);
45    let mut term = Terminal::new(backend)?;
46
47    // Run the draw loop
48    run(&mut term)?;
49
50    disable_raw_mode()?;
51    execute!(term.backend_mut(), LeaveAlternateScreen)?;
52    term.show_cursor()?;
53    Ok(())
54}
55
56fn run<B>(term: &mut Terminal<B>) -> io::Result<()>
57where
58    B: ratatui::backend::Backend,
59{
60    let items = vec![
61        Item::new("feat", "โœจ"),
62        Item::new("fix", "๐Ÿ›"),
63        Item::new("chore", "๐Ÿงน"),
64        Item::new("docs", "๐Ÿ“š"),
65        Item::new("style", "๐Ÿ’…"),
66        Item::new("refactor", "๐Ÿ”จ"),
67        Item::new("perf", "๐ŸŽ"),
68        Item::new("test", "๐Ÿšจ"),
69        Item::new("build", "๐Ÿ‘ท"),
70        Item::new("ci", "๐Ÿ”ง"),
71        Item::new("revert", "โช"),
72        Item::new("release", "๐Ÿš€"),
73        Item::new("wip", "๐Ÿšง"),
74    ];
75
76    let mut grid_state = GridSelectorState::new(items).columns(5);
77
78    loop {
79        term.draw(|f| draw(f, f.area(), &mut grid_state))?;
80
81        match event::read()? {
82            Event::Key(key) => match key {
83                KeyEvent {
84                    code: KeyCode::Esc, ..
85                } => break,
86                KeyEvent {
87                    code: KeyCode::Right,
88                    kind: KeyEventKind::Press,
89                    ..
90                } => grid_state.move_right(),
91                KeyEvent {
92                    code: KeyCode::Left,
93                    kind: KeyEventKind::Press,
94                    ..
95                } => grid_state.move_left(),
96                KeyEvent {
97                    code: KeyCode::Up,
98                    kind: KeyEventKind::Press,
99                    ..
100                } => grid_state.move_up(),
101                KeyEvent {
102                    code: KeyCode::Down,
103                    kind: KeyEventKind::Press,
104                    ..
105                } => grid_state.move_down(),
106                KeyEvent {
107                    code: KeyCode::Home,
108                    kind: KeyEventKind::Press,
109                    ..
110                } => grid_state.move_to_row_start(),
111                KeyEvent {
112                    code: KeyCode::End,
113                    kind: KeyEventKind::Press,
114                    ..
115                } => grid_state.move_to_row_end(),
116                // selection
117                KeyEvent {
118                    code: KeyCode::Enter,
119                    kind: KeyEventKind::Press,
120                    ..
121                } => grid_state.select(),
122                _ => false,
123            },
124            _ => false,
125        };
126    }
127
128    Ok(())
129}
130
131fn draw(f: &mut Frame<'_>, area: Rect, state: &mut GridSelectorState) {
132    // vertical layout with 2 rows
133    let layout = Layout::default()
134        .direction(Direction::Vertical)
135        .constraints([
136            Constraint::Min(0),
137            Constraint::Length(2),
138            Constraint::Length(1),
139            Constraint::Length(2),
140        ])
141        .split(area);
142
143    f.render_stateful_widget(
144        GridSelector::default().with_selected_color(Color::Magenta),
145        layout[0],
146        state,
147    );
148
149    let selected = state.selected().unwrap_or(GridItem::new("None"));
150    let hovered = state.hovered().unwrap_or(GridItem::new("None"));
151
152    let ps = Paragraph::new(selected)
153        .alignment(Alignment::Center)
154        .alignment(Alignment::Center)
155        .style(Style::default().fg(Color::Magenta));
156
157    let ph = Paragraph::new(hovered)
158        .alignment(Alignment::Center)
159        .style(Style::default().fg(Color::Blue));
160
161    f.render_widget(
162        Paragraph::new("Hovered/Selected")
163            .alignment(Alignment::Center)
164            .add_modifier(Modifier::BOLD),
165        layout[1],
166    );
167    f.render_widget(ph, layout[2]);
168    f.render_widget(ps, layout[3]);
169}