Skip to main content

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/// You usually get a `Frame` from the closure argument of [`Terminal::draw`] /
8/// [`Terminal::try_draw`]. For manual rendering, use
9/// [`Terminal::get_frame`](crate::terminal::Terminal::get_frame).
10///
11/// A `Frame` is used to render widgets into Ratatui's current buffer and request the cursor state
12/// for the end of the render pass.
13///
14/// The changes drawn to the frame are applied only to the current [`Buffer`]. After the closure
15/// returns, the current buffer is compared to the previous buffer and only the changed cells are
16/// sent to the backend. This avoids drawing redundant cells.
17///
18/// [`Buffer`]: crate::buffer::Buffer
19/// [`Terminal::draw`]: crate::terminal::Terminal::draw
20/// [`Terminal::try_draw`]: crate::terminal::Terminal::try_draw
21#[derive(Debug, Hash)]
22pub struct Frame<'a> {
23    /// Where should the cursor be after drawing this frame?
24    ///
25    /// If `None`, the cursor is hidden at the end of the render pass. If `Some((x, y))`, the
26    /// cursor is shown and placed at `(x, y)` after the frame's buffer diff has been applied to
27    /// the backend.
28    pub(crate) cursor_position: Option<Position>,
29
30    /// The area of the viewport
31    pub(crate) viewport_area: Rect,
32
33    /// The buffer that is used to draw the current frame
34    pub(crate) buffer: &'a mut Buffer,
35
36    /// The frame count indicating the sequence number of this frame.
37    pub(crate) count: usize,
38}
39
40/// `CompletedFrame` represents the state of the terminal after the last successful
41/// [`Terminal::draw`] / [`Terminal::try_draw`] render pass has been applied. Therefore, it is only
42/// valid until the next successful draw call.
43///
44/// This lifetime follows Ratatui's double-buffering model: the next render pass swaps buffers via
45/// [`Terminal::swap_buffers`], so the previously completed buffer is no longer the current output.
46///
47/// [`Terminal::draw`]: crate::terminal::Terminal::draw
48/// [`Terminal::swap_buffers`]: crate::terminal::Terminal::swap_buffers
49/// [`Terminal::try_draw`]: crate::terminal::Terminal::try_draw
50#[derive(Debug, Clone, Eq, PartialEq, Hash)]
51pub struct CompletedFrame<'a> {
52    /// The buffer that was used to draw the last frame.
53    pub buffer: &'a Buffer,
54    /// The size of the last frame.
55    pub area: Rect,
56    /// The frame count indicating the sequence number of this frame.
57    pub count: usize,
58}
59
60impl Frame<'_> {
61    /// Returns 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, ignore that event's dimensions for
66    /// calculations performed during the current render callback and use this value instead. It is
67    /// the area of the buffer that is actually being rendered for this pass.
68    pub const fn area(&self) -> Rect {
69        self.viewport_area
70    }
71
72    /// Returns the area of the current frame.
73    ///
74    /// This is guaranteed not to change during rendering, so may be called multiple times.
75    ///
76    /// If your app listens for a resize event from the backend, ignore that event's dimensions for
77    /// calculations performed during the current render callback and use this value instead. It is
78    /// the area of the buffer that is actually being rendered for this pass.
79    #[deprecated = "use `area()` instead"]
80    pub const fn size(&self) -> Rect {
81        self.viewport_area
82    }
83
84    /// Render a [`Widget`] to the current buffer using [`Widget::render`].
85    ///
86    /// Usually the area argument is the size of the current frame or a sub-area of the current
87    /// frame (which can be obtained using [`Layout`] to split the total area).
88    ///
89    /// Rendering writes directly into the current frame buffer. If multiple widgets cover the same
90    /// cells, later renders win for those cells.
91    ///
92    /// # Example
93    ///
94    /// ```rust
95    /// # use ratatui_core::{backend::TestBackend, terminal::Terminal};
96    /// # let backend = TestBackend::new(5, 5);
97    /// # let mut terminal = Terminal::new(backend).unwrap();
98    /// # let mut frame = terminal.get_frame();
99    /// use ratatui_core::layout::Rect;
100    ///
101    /// let area = Rect::new(0, 0, 5, 5);
102    /// frame.render_widget("Hello", area);
103    /// ```
104    ///
105    /// [`Layout`]: crate::layout::Layout
106    pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
107        widget.render(area, self.buffer);
108    }
109
110    /// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
111    ///
112    /// Usually the area argument is the size of the current frame or a sub-area of the current
113    /// frame (which can be obtained using [`Layout`] to split the total area).
114    ///
115    /// The last argument should be an instance of the [`StatefulWidget::State`] associated to the
116    /// given [`StatefulWidget`].
117    ///
118    /// Like [`Frame::render_widget`], this writes directly into the current frame buffer. The
119    /// widget owns how it interprets and mutates the provided state.
120    ///
121    /// # Example
122    ///
123    /// ```rust
124    /// # use ratatui_core::{backend::TestBackend, buffer::Buffer, layout::Rect, terminal::Terminal};
125    /// # let backend = TestBackend::new(5, 5);
126    /// # let mut terminal = Terminal::new(backend).unwrap();
127    /// # let mut frame = terminal.get_frame();
128    /// use ratatui_core::widgets::StatefulWidget;
129    ///
130    /// struct DemoWidget;
131    ///
132    /// impl StatefulWidget for DemoWidget {
133    ///     type State = bool;
134    ///
135    ///     fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
136    ///         let symbol = if *state { "Y" } else { "N" };
137    ///         buf[(area.x, area.y)].set_symbol(symbol);
138    ///     }
139    /// }
140    ///
141    /// let mut state = true;
142    /// let area = Rect::new(0, 0, 5, 5);
143    /// frame.render_stateful_widget(DemoWidget, area, &mut state);
144    /// ```
145    ///
146    /// [`Layout`]: crate::layout::Layout
147    pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
148    where
149        W: StatefulWidget,
150    {
151        widget.render(area, self.buffer, state);
152    }
153
154    /// After this frame is rendered, make the cursor visible and put it at the specified `(x, y)`
155    /// coordinates. If this method is not called, the cursor will be hidden.
156    ///
157    /// The cursor is applied after Ratatui flushes the frame's buffer diff to the backend.
158    ///
159    /// Note that this will interfere with calls to [`Terminal::hide_cursor`],
160    /// [`Terminal::show_cursor`], and [`Terminal::set_cursor_position`]. Pick one of the APIs and
161    /// stick with it.
162    ///
163    /// [`Terminal::hide_cursor`]: crate::terminal::Terminal::hide_cursor
164    /// [`Terminal::show_cursor`]: crate::terminal::Terminal::show_cursor
165    /// [`Terminal::set_cursor_position`]: crate::terminal::Terminal::set_cursor_position
166    pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) {
167        self.cursor_position = Some(position.into());
168    }
169
170    /// After this frame is rendered, make the cursor visible and put it at the specified `(x, y)`
171    /// coordinates. If this method is not called, the cursor will be hidden.
172    ///
173    /// Note that this will interfere with calls to [`Terminal::hide_cursor`],
174    /// [`Terminal::show_cursor`], and [`Terminal::set_cursor_position`]. Pick one of the APIs and
175    /// stick with it.
176    ///
177    /// [`Terminal::hide_cursor`]: crate::terminal::Terminal::hide_cursor
178    /// [`Terminal::show_cursor`]: crate::terminal::Terminal::show_cursor
179    /// [`Terminal::set_cursor_position`]: crate::terminal::Terminal::set_cursor_position
180    #[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
181    pub fn set_cursor(&mut self, x: u16, y: u16) {
182        self.set_cursor_position(Position { x, y });
183    }
184
185    /// Gets the buffer that this `Frame` draws into as a mutable reference.
186    ///
187    /// This is an escape hatch for direct buffer manipulation. Normal applications should prefer
188    /// the widget rendering methods so layout and rendering intent stay visible at the call site.
189    ///
190    /// Use this when tests, custom widgets, or specialized integrations need direct cell access
191    /// during a render pass.
192    ///
193    /// Changes written here are not visible on the backend until the render pass is applied by
194    /// [`Terminal::flush`](crate::terminal::Terminal::flush) or a full
195    /// [`Terminal::draw`](crate::terminal::Terminal::draw) /
196    /// [`Terminal::try_draw`](crate::terminal::Terminal::try_draw) pass.
197    ///
198    /// # Example
199    ///
200    /// ```rust
201    /// # use ratatui_core::{backend::TestBackend, terminal::Terminal};
202    /// # let backend = TestBackend::new(5, 1);
203    /// # let mut terminal = Terminal::new(backend).unwrap();
204    /// # let mut frame = terminal.get_frame();
205    /// frame.buffer_mut()[(0, 0)].set_symbol("h");
206    /// ```
207    pub const fn buffer_mut(&mut self) -> &mut Buffer {
208        self.buffer
209    }
210
211    /// Returns the current frame count.
212    ///
213    /// This method provides access to the frame count, which is a sequence number indicating
214    /// how many frames have been rendered up to (but not including) this one. It can be used
215    /// for purposes such as animation, performance tracking, or debugging.
216    ///
217    /// Each time a frame has been rendered, this count is incremented,
218    /// providing a consistent way to reference the order and number of frames processed by the
219    /// terminal. When count reaches its maximum value (`usize::MAX`), it wraps around to zero.
220    ///
221    /// This count is particularly useful when dealing with dynamic content or animations where the
222    /// state of the display changes over time. By tracking the frame count, developers can
223    /// synchronize updates or changes to the content with the rendering process.
224    ///
225    /// # Examples
226    ///
227    /// ```rust
228    /// # use ratatui_core::{backend::TestBackend, terminal::Terminal};
229    /// # let backend = TestBackend::new(5, 5);
230    /// # let mut terminal = Terminal::new(backend).unwrap();
231    /// # let mut frame = terminal.get_frame();
232    /// let current_count = frame.count();
233    /// println!("Current frame count: {}", current_count);
234    /// ```
235    pub const fn count(&self) -> usize {
236        self.count
237    }
238}