ratatui_core/terminal/
frame.rs

1use crate::buffer::Buffer;
2use crate::layout::{Position, Rect};
3use crate::widgets::{StatefulWidget, Widget};
4
5/// A consistent view into the terminal state for rendering a single frame.
6///
7/// This is obtained via the closure argument of [`Terminal::draw`]. It is used to render widgets
8/// to the terminal and control the cursor position.
9///
10/// The changes drawn to the frame are applied only to the current [`Buffer`]. After the closure
11/// returns, the current buffer is compared to the previous buffer and only the changes are applied
12/// to the terminal. This avoids drawing redundant cells.
13///
14/// [`Buffer`]: crate::buffer::Buffer
15/// [`Terminal::draw`]: crate::terminal::Terminal::draw
16#[derive(Debug, Hash)]
17pub struct Frame<'a> {
18    /// Where should the cursor be after drawing this frame?
19    ///
20    /// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x,
21    /// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`.
22    pub(crate) cursor_position: Option<Position>,
23
24    /// The area of the viewport
25    pub(crate) viewport_area: Rect,
26
27    /// The buffer that is used to draw the current frame
28    pub(crate) buffer: &'a mut Buffer,
29
30    /// The frame count indicating the sequence number of this frame.
31    pub(crate) count: usize,
32}
33
34/// `CompletedFrame` represents the state of the terminal after all changes performed in the last
35/// [`Terminal::draw`] call have been applied. Therefore, it is only valid until the next call to
36/// [`Terminal::draw`].
37///
38/// [`Terminal::draw`]: crate::terminal::Terminal::draw
39#[derive(Debug, Clone, Eq, PartialEq, Hash)]
40pub struct CompletedFrame<'a> {
41    /// The buffer that was used to draw the last frame.
42    pub buffer: &'a Buffer,
43    /// The size of the last frame.
44    pub area: Rect,
45    /// The frame count indicating the sequence number of this frame.
46    pub count: usize,
47}
48
49impl Frame<'_> {
50    /// The area of the current frame
51    ///
52    /// This is guaranteed not to change during rendering, so may be called multiple times.
53    ///
54    /// If your app listens for a resize event from the backend, it should ignore the values from
55    /// the event for any calculations that are used to render the current frame and use this value
56    /// instead as this is the area of the buffer that is used to render the current frame.
57    pub const fn area(&self) -> Rect {
58        self.viewport_area
59    }
60
61    /// The area of the current frame
62    ///
63    /// This is guaranteed not to change during rendering, so may be called multiple times.
64    ///
65    /// If your app listens for a resize event from the backend, it should ignore the values from
66    /// the event for any calculations that are used to render the current frame and use this value
67    /// instead as this is the area of the buffer that is used to render the current frame.
68    #[deprecated = "use `area()` instead"]
69    pub const fn size(&self) -> Rect {
70        self.viewport_area
71    }
72
73    /// Render a [`Widget`] to the current buffer using [`Widget::render`].
74    ///
75    /// Usually the area argument is the size of the current frame or a sub-area of the current
76    /// frame (which can be obtained using [`Layout`] to split the total area).
77    ///
78    /// # Example
79    ///
80    /// ```rust,ignore
81    /// # use ratatui::{backend::TestBackend, Terminal};
82    /// # let backend = TestBackend::new(5, 5);
83    /// # let mut terminal = Terminal::new(backend).unwrap();
84    /// # let mut frame = terminal.get_frame();
85    /// use ratatui::{layout::Rect, widgets::Block};
86    ///
87    /// let block = Block::new();
88    /// let area = Rect::new(0, 0, 5, 5);
89    /// frame.render_widget(block, area);
90    /// ```
91    ///
92    /// [`Layout`]: crate::layout::Layout
93    pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
94        widget.render(area, self.buffer);
95    }
96
97    /// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
98    ///
99    /// Usually the area argument is the size of the current frame or a sub-area of the current
100    /// frame (which can be obtained using [`Layout`] to split the total area).
101    ///
102    /// The last argument should be an instance of the [`StatefulWidget::State`] associated to the
103    /// given [`StatefulWidget`].
104    ///
105    /// # Example
106    ///
107    /// ```rust,ignore
108    /// # use ratatui::{backend::TestBackend, Terminal};
109    /// # let backend = TestBackend::new(5, 5);
110    /// # let mut terminal = Terminal::new(backend).unwrap();
111    /// # let mut frame = terminal.get_frame();
112    /// use ratatui::{
113    ///     layout::Rect,
114    ///     widgets::{List, ListItem, ListState},
115    /// };
116    ///
117    /// let mut state = ListState::default().with_selected(Some(1));
118    /// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
119    /// let area = Rect::new(0, 0, 5, 5);
120    /// frame.render_stateful_widget(list, area, &mut state);
121    /// ```
122    ///
123    /// [`Layout`]: crate::layout::Layout
124    pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
125    where
126        W: StatefulWidget,
127    {
128        widget.render(area, self.buffer, state);
129    }
130
131    /// After drawing this frame, make the cursor visible and put it at the specified (x, y)
132    /// coordinates. If this method is not called, the cursor will be hidden.
133    ///
134    /// Note that this will interfere with calls to [`Terminal::hide_cursor`],
135    /// [`Terminal::show_cursor`], and [`Terminal::set_cursor_position`]. Pick one of the APIs and
136    /// stick with it.
137    ///
138    /// [`Terminal::hide_cursor`]: crate::terminal::Terminal::hide_cursor
139    /// [`Terminal::show_cursor`]: crate::terminal::Terminal::show_cursor
140    /// [`Terminal::set_cursor_position`]: crate::terminal::Terminal::set_cursor_position
141    pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) {
142        self.cursor_position = Some(position.into());
143    }
144
145    /// After drawing this frame, make the cursor visible and put it at the specified (x, y)
146    /// coordinates. If this method is not called, the cursor will be hidden.
147    ///
148    /// Note that this will interfere with calls to [`Terminal::hide_cursor`],
149    /// [`Terminal::show_cursor`], and [`Terminal::set_cursor_position`]. Pick one of the APIs and
150    /// stick with it.
151    ///
152    /// [`Terminal::hide_cursor`]: crate::terminal::Terminal::hide_cursor
153    /// [`Terminal::show_cursor`]: crate::terminal::Terminal::show_cursor
154    /// [`Terminal::set_cursor_position`]: crate::terminal::Terminal::set_cursor_position
155    #[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
156    pub fn set_cursor(&mut self, x: u16, y: u16) {
157        self.set_cursor_position(Position { x, y });
158    }
159
160    /// Gets the buffer that this `Frame` draws into as a mutable reference.
161    pub const fn buffer_mut(&mut self) -> &mut Buffer {
162        self.buffer
163    }
164
165    /// Returns the current frame count.
166    ///
167    /// This method provides access to the frame count, which is a sequence number indicating
168    /// how many frames have been rendered up to (but not including) this one. It can be used
169    /// for purposes such as animation, performance tracking, or debugging.
170    ///
171    /// Each time a frame has been rendered, this count is incremented,
172    /// providing a consistent way to reference the order and number of frames processed by the
173    /// terminal. When count reaches its maximum value (`usize::MAX`), it wraps around to zero.
174    ///
175    /// This count is particularly useful when dealing with dynamic content or animations where the
176    /// state of the display changes over time. By tracking the frame count, developers can
177    /// synchronize updates or changes to the content with the rendering process.
178    ///
179    /// # Examples
180    ///
181    /// ```rust,ignore
182    /// # use ratatui::{backend::TestBackend, Terminal};
183    /// # let backend = TestBackend::new(5, 5);
184    /// # let mut terminal = Terminal::new(backend).unwrap();
185    /// # let mut frame = terminal.get_frame();
186    /// let current_count = frame.count();
187    /// println!("Current frame count: {}", current_count);
188    /// ```
189    pub const fn count(&self) -> usize {
190        self.count
191    }
192}