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}