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}