ratatui/widgets/stateful_widget_ref.rs
1use ratatui_core::widgets::StatefulWidget;
2
3use crate::buffer::Buffer;
4use crate::layout::Rect;
5
6/// A `StatefulWidgetRef` is a trait that allows rendering a stateful widget by reference.
7///
8/// This is the stateful equivalent of `WidgetRef`. It is useful when you need to store a reference
9/// to a stateful widget and render it later. It also allows you to render boxed stateful widgets.
10///
11/// This trait was introduced in Ratatui 0.26.0. It is currently marked as unstable as we are still
12/// evaluating the API and may make changes in the future. See
13/// <https://github.com/ratatui/ratatui/issues/1287> for more information.
14///
15/// A blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget`
16/// is provided. Most of the time you will want to implement `StatefulWidget` against a reference to
17/// the widget instead of implementing `StatefulWidgetRef` directly.
18///
19/// See the documentation for [`WidgetRef`] for more information on boxed widgets. See the
20/// documentation for [`StatefulWidget`] for more information on stateful widgets.
21///
22/// For comprehensive information about widget implementation patterns, rendering, and usage,
23/// see the [`widgets`] module documentation.
24///
25/// [`widgets`]: crate::widgets
26///
27/// # Examples
28///
29/// ```rust
30/// # #[cfg(feature = "unstable-widget-ref")] {
31/// use ratatui::widgets::StatefulWidgetRef;
32/// use ratatui_core::buffer::Buffer;
33/// use ratatui_core::layout::Rect;
34/// use ratatui_core::style::Stylize;
35/// use ratatui_core::text::Line;
36/// use ratatui_core::widgets::{StatefulWidget, Widget};
37///
38/// struct PersonalGreeting;
39///
40/// impl StatefulWidgetRef for PersonalGreeting {
41/// type State = String;
42/// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
43/// Line::raw(format!("Hello {}", state)).render(area, buf);
44/// }
45/// }
46///
47/// impl StatefulWidget for PersonalGreeting {
48/// type State = String;
49/// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
50/// (&self).render_ref(area, buf, state);
51/// }
52/// }
53///
54/// fn render(area: Rect, buf: &mut Buffer) {
55/// let widget = PersonalGreeting;
56/// let mut state = "world".to_string();
57/// widget.render(area, buf, &mut state);
58/// }
59/// # }
60/// ```
61#[instability::unstable(feature = "widget-ref")]
62pub trait StatefulWidgetRef {
63 /// State associated with the stateful widget.
64 ///
65 /// If you don't need this then you probably want to implement [`WidgetRef`] instead.
66 ///
67 /// [`WidgetRef`]: super::WidgetRef
68 type State: ?Sized;
69 /// Draws the current state of the widget in the given buffer. That is the only method required
70 /// to implement a custom stateful widget.
71 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
72}
73
74/// Blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget`.
75///
76/// This allows you to render a stateful widget by reference.
77impl<W, State: ?Sized> StatefulWidgetRef for &W
78where
79 for<'a> &'a W: StatefulWidget<State = State>,
80{
81 type State = State;
82 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
83 self.render(area, buf, state);
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use alloc::borrow::ToOwned;
90 use alloc::boxed::Box;
91 use alloc::format;
92 use alloc::string::{String, ToString};
93
94 use rstest::{fixture, rstest};
95
96 use super::*;
97 use crate::buffer::Buffer;
98 use crate::layout::Rect;
99 use crate::text::Line;
100 use crate::widgets::Widget;
101
102 #[fixture]
103 fn buf() -> Buffer {
104 Buffer::empty(Rect::new(0, 0, 20, 1))
105 }
106
107 #[fixture]
108 fn state() -> String {
109 "world".to_string()
110 }
111
112 struct PersonalGreeting;
113
114 impl StatefulWidget for &PersonalGreeting {
115 type State = String;
116 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
117 Line::from(format!("Hello {state}")).render(area, buf);
118 }
119 }
120
121 #[rstest]
122 fn render_ref(mut buf: Buffer, mut state: String) {
123 let widget = &PersonalGreeting;
124 widget.render_ref(buf.area, &mut buf, &mut state);
125 assert_eq!(buf, Buffer::with_lines(["Hello world "]));
126 }
127
128 #[rstest]
129 fn box_render_ref(mut buf: Buffer, mut state: String) {
130 let widget = Box::new(&PersonalGreeting);
131 widget.render_ref(buf.area, &mut buf, &mut state);
132 assert_eq!(buf, Buffer::with_lines(["Hello world "]));
133 }
134
135 #[rstest]
136 fn render_stateful_widget_ref_with_unsized_state(mut buf: Buffer) {
137 struct Bytes;
138
139 impl StatefulWidgetRef for Bytes {
140 type State = [u8];
141 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
142 let slice = core::str::from_utf8(state).unwrap();
143 Line::from(format!("Bytes: {slice}")).render(area, buf);
144 }
145 }
146 let widget = Bytes;
147 let state = b"hello";
148 widget.render_ref(buf.area, &mut buf, &mut state.clone());
149 assert_eq!(buf, Buffer::with_lines(["Bytes: hello "]));
150 }
151
152 #[rstest]
153 fn render_stateful_widget_with_unsized_state(mut buf: Buffer) {
154 struct Bytes;
155 impl StatefulWidget for &Bytes {
156 type State = [u8];
157 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
158 let slice = core::str::from_utf8(state).unwrap();
159 Line::from(format!("Bytes: {slice}")).render(area, buf);
160 }
161 }
162 let widget = &Bytes;
163 let mut state = b"hello".to_owned();
164 let state = state.as_mut_slice();
165 widget.render_ref(buf.area, &mut buf, state);
166 assert_eq!(buf, Buffer::with_lines(["Bytes: hello "]));
167 }
168}