Skip to main content

paragraph/
paragraph.rs

1//! Shows a popup containing a fixed-size scrollable paragraph.
2//!
3//! Run with `cargo run -p tui-popup --example paragraph --features crossterm`.
4//!
5//! `Paragraph` does not report its desired popup size, so the example wraps it in
6//! `KnownSizeWrapper`. The wrapper gives `Popup` a stable width and height while the paragraph
7//! keeps its own scroll offset.
8//!
9//! Controls:
10//! - `j` / `Down`: scroll down
11//! - `k` / `Up`: scroll up
12//! - `q` / `Esc`: quit
13
14use color_eyre::Result;
15use lipsum::lipsum;
16use ratatui::Frame;
17use ratatui::crossterm::event::{self, KeyCode};
18use ratatui::prelude::{Rect, Span, Style, Stylize, Text};
19use ratatui::widgets::{Paragraph, Wrap};
20use tui_popup::{KnownSizeWrapper, Popup};
21
22fn main() -> Result<()> {
23    color_eyre::install()?;
24    ratatui::run(|terminal| App::default().run(terminal))
25}
26
27#[derive(Default)]
28struct App {
29    should_exit: bool,
30    lorem_ipsum: String,
31    scroll: u16,
32}
33
34impl App {
35    fn run(&mut self, terminal: &mut ratatui::DefaultTerminal) -> Result<()> {
36        self.lorem_ipsum = lipsum(2000);
37        while !self.should_exit {
38            terminal.draw(|frame| self.render(frame))?;
39            self.handle_events()?;
40        }
41        Ok(())
42    }
43
44    fn render(&self, frame: &mut Frame) {
45        let area = frame.area();
46        self.render_background(frame, area);
47        self.render_popup(frame);
48    }
49
50    fn render_background(&self, frame: &mut Frame, area: Rect) {
51        let text = Text::raw(&self.lorem_ipsum);
52        let paragraph = Paragraph::new(text).wrap(Wrap { trim: false }).dark_gray();
53        frame.render_widget(paragraph, area);
54    }
55
56    fn render_popup(&self, frame: &mut Frame) {
57        let lines: Text = (0..10).map(|i| Span::raw(format!("Line {i}"))).collect();
58        let paragraph = Paragraph::new(lines).scroll((self.scroll, 0));
59        // Popup needs KnownSize for placement; Paragraph owns the scroll offset but not a desired
60        // size.
61        let wrapper = KnownSizeWrapper {
62            inner: paragraph,
63            width: 21,
64            height: 5,
65        };
66        let popup = Popup::new(wrapper)
67            .title("scroll: ↑/↓ quit: Esc")
68            .style(Style::new().white().on_blue());
69        frame.render_widget(popup, frame.area());
70    }
71
72    fn handle_events(&mut self) -> Result<()> {
73        if let Some(key) = event::read()?.as_key_press_event() {
74            match key.code {
75                KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true,
76                KeyCode::Char('j') | KeyCode::Down => self.scroll_down(),
77                KeyCode::Char('k') | KeyCode::Up => self.scroll_up(),
78                _ => {}
79            }
80        }
81        Ok(())
82    }
83
84    const fn scroll_up(&mut self) {
85        self.scroll = self.scroll.saturating_sub(1);
86    }
87
88    const fn scroll_down(&mut self) {
89        self.scroll = self.scroll.saturating_add(1);
90    }
91}