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}