Skip to main content

state/
state.rs

1//! Demonstrates `PopupState` by moving a popup with keyboard and mouse input.
2//!
3//! Run with `cargo run -p tui-popup --example state --features crossterm`.
4//!
5//! `PopupState` stores the popup area from the last render. The status bar prints that area so you
6//! can see how keyboard moves, mouse drags, and reset commands change the state used by the next
7//! frame.
8//!
9//! Controls:
10//! - `h` / `Left`: move left
11//! - `j` / `Down`: move down
12//! - `k` / `Up`: move up
13//! - `l` / `Right`: move right
14//! - `r`: reset popup state
15//! - `q` / `Esc`: quit
16
17use color_eyre::Result;
18use lipsum::lipsum;
19use ratatui::DefaultTerminal;
20use ratatui::crossterm::event::{self, Event, KeyCode, KeyEvent};
21use ratatui::prelude::{Constraint, Frame, Layout, Rect, Style, Stylize, Text};
22use ratatui::widgets::{Paragraph, Wrap};
23use tui_popup::{Popup, PopupState};
24
25fn main() -> Result<()> {
26    color_eyre::install()?;
27    ratatui::run(run)
28}
29
30fn run(terminal: &mut DefaultTerminal) -> Result<()> {
31    let mut state = PopupState::default();
32    let mut exit = false;
33    while !exit {
34        terminal.draw(|frame| draw(frame, &mut state))?;
35        handle_events(&mut state, &mut exit)?;
36    }
37    Ok(())
38}
39
40fn draw(frame: &mut Frame, state: &mut PopupState) {
41    let vertical = Layout::vertical([Constraint::Min(0), Constraint::Length(1)]);
42    let [background_area, status_area] = vertical.areas(frame.area());
43
44    render_background(frame, background_area);
45    render_popup(frame, background_area, state);
46    render_status_bar(frame, status_area, state);
47}
48
49fn render_background(frame: &mut Frame, area: Rect) {
50    let lorem_ipsum = lipsum(area.area() as usize / 5);
51    let background = Paragraph::new(lorem_ipsum)
52        .wrap(Wrap { trim: false })
53        .dark_gray();
54    frame.render_widget(background, area);
55}
56
57fn render_popup(frame: &mut Frame, area: Rect, state: &mut PopupState) {
58    let body = Text::from_iter([
59        "q: exit",
60        "r: reset",
61        "j: move down",
62        "k: move up",
63        "h: move left",
64        "l: move right",
65    ]);
66    let popup = Popup::new(body)
67        .title("Popup")
68        .style(Style::new().white().on_blue());
69    frame.render_stateful_widget(popup, area, state);
70}
71
72/// Status bar at the bottom of the screen
73///
74/// Must be called after rendering the popup widget as it relies on the popup area being set
75fn render_status_bar(frame: &mut Frame, area: Rect, state: &PopupState) {
76    let popup_area = state.area().unwrap_or_default();
77    let text = format!("Popup area: {popup_area:?}");
78    let paragraph = Paragraph::new(text).style(Style::new().white().on_black());
79    frame.render_widget(paragraph, area);
80}
81
82fn handle_events(popup: &mut PopupState, exit: &mut bool) -> Result<()> {
83    let event = event::read()?;
84    if let Some(key) = event.as_key_press_event() {
85        handle_key_event(key, popup, exit);
86    } else if let Event::Mouse(event) = event {
87        popup.handle_mouse_event(event);
88    }
89    Ok(())
90}
91
92fn handle_key_event(event: KeyEvent, popup: &mut PopupState, exit: &mut bool) {
93    match event.code {
94        KeyCode::Char('q') | KeyCode::Esc => *exit = true,
95        KeyCode::Char('r') => *popup = PopupState::default(),
96        KeyCode::Char('j') | KeyCode::Down => popup.move_down(1),
97        KeyCode::Char('k') | KeyCode::Up => popup.move_up(1),
98        KeyCode::Char('h') | KeyCode::Left => popup.move_left(1),
99        KeyCode::Char('l') | KeyCode::Right => popup.move_right(1),
100        _ => {}
101    }
102}