state/
state.rs

1use color_eyre::Result;
2use lipsum::lipsum;
3use ratatui::crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
4use ratatui::prelude::{Constraint, Frame, Layout, Rect, Style, Stylize, Text};
5use ratatui::widgets::{Paragraph, Wrap};
6use ratatui::DefaultTerminal;
7use tui_popup::{Popup, PopupState};
8
9fn main() -> Result<()> {
10    color_eyre::install()?;
11    let terminal = ratatui::init();
12    let result = run(terminal);
13    ratatui::restore();
14    result
15}
16
17fn run(mut terminal: DefaultTerminal) -> Result<()> {
18    let mut state = PopupState::default();
19    let mut exit = false;
20    while !exit {
21        terminal.draw(|frame| draw(frame, &mut state))?;
22        handle_events(&mut state, &mut exit)?;
23    }
24    Ok(())
25}
26
27fn draw(frame: &mut Frame, state: &mut PopupState) {
28    let vertical = Layout::vertical([Constraint::Min(0), Constraint::Length(1)]);
29    let [background_area, status_area] = vertical.areas(frame.area());
30
31    render_background(frame, background_area);
32    render_popup(frame, background_area, state);
33    render_status_bar(frame, status_area, state);
34}
35
36fn render_background(frame: &mut Frame, area: Rect) {
37    let lorem_ipsum = lipsum(area.area() as usize / 5);
38    let background = Paragraph::new(lorem_ipsum)
39        .wrap(Wrap { trim: false })
40        .dark_gray();
41    frame.render_widget(background, area);
42}
43
44fn render_popup(frame: &mut Frame, area: Rect, state: &mut PopupState) {
45    let body = Text::from_iter([
46        "q: exit",
47        "r: reset",
48        "j: move down",
49        "k: move up",
50        "h: move left",
51        "l: move right",
52    ]);
53    let popup = Popup::new(body)
54        .title("Popup")
55        .style(Style::new().white().on_blue());
56    frame.render_stateful_widget(popup, area, state);
57}
58
59/// Status bar at the bottom of the screen
60///
61/// Must be called after rendering the popup widget as it relies on the popup area being set
62fn render_status_bar(frame: &mut Frame, area: Rect, state: &PopupState) {
63    let popup_area = state.area().unwrap_or_default();
64    let text = format!("Popup area: {popup_area:?}");
65    let paragraph = Paragraph::new(text).style(Style::new().white().on_black());
66    frame.render_widget(paragraph, area);
67}
68
69fn handle_events(popup: &mut PopupState, exit: &mut bool) -> Result<()> {
70    match event::read()? {
71        Event::Key(event) if event.kind == KeyEventKind::Press => {
72            handle_key_event(event, popup, exit);
73        }
74        Event::Mouse(event) => popup.handle_mouse_event(event),
75        _ => (),
76    }
77    Ok(())
78}
79
80fn handle_key_event(event: KeyEvent, popup: &mut PopupState, exit: &mut bool) {
81    match event.code {
82        KeyCode::Char('q') | KeyCode::Esc => *exit = true,
83        KeyCode::Char('r') => *popup = PopupState::default(),
84        KeyCode::Char('j') | KeyCode::Down => popup.move_down(1),
85        KeyCode::Char('k') | KeyCode::Up => popup.move_up(1),
86        KeyCode::Char('h') | KeyCode::Left => popup.move_left(1),
87        KeyCode::Char('l') | KeyCode::Right => popup.move_right(1),
88        _ => {}
89    }
90}