ratatui/terminal/
frame.rs

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