ratatui_core/widgets/
widget.rs

1use alloc::string::String;
2
3use crate::buffer::Buffer;
4use crate::layout::Rect;
5use crate::style::Style;
6
7/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
8///
9/// For a comprehensive guide to widgets, including trait explanations, implementation patterns,
10/// and available widgets, see the [`widgets`] module documentation.
11///
12/// [`widgets`]: ../../ratatui/widgets/index.html
13///
14/// Prior to Ratatui 0.26.0, widgets generally were created for each frame as they were consumed
15/// during rendering. This meant that they were not meant to be stored but used as *commands* to
16/// draw common figures in the UI.
17///
18/// Starting with Ratatui 0.26.0, all the internal widgets implement Widget for a reference to
19/// themselves. This allows you to store a reference to a widget and render it later. Widget crates
20/// should consider also doing this to allow for more flexibility in how widgets are used.
21///
22/// In Ratatui 0.26.0, we also added an unstable `WidgetRef` trait and implemented this on all the
23/// internal widgets. In addition to the above benefit of rendering references to widgets, this also
24/// allows you to render boxed widgets. This is useful when you want to store a collection of
25/// widgets with different types. You can then iterate over the collection and render each widget.
26/// See <https://github.com/ratatui/ratatui/issues/1287> for more information.
27///
28/// In general where you expect a widget to immutably work on its data, we recommended to implement
29/// `Widget` for a reference to the widget (`impl Widget for &MyWidget`). If you need to store state
30/// between draw calls, implement `StatefulWidget` if you want the Widget to be immutable, or
31/// implement `Widget` for a mutable reference to the widget (`impl Widget for &mut MyWidget`) if
32/// you want the widget to be mutable. The mutable widget pattern is used infrequently in apps, but
33/// can be quite useful.
34///
35/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
36/// Widget is also implemented for `&str` and `String` types.
37///
38/// # Examples
39///
40/// ```rust,ignore
41/// use ratatui::{
42///     backend::TestBackend,
43///     widgets::{Clear, Widget},
44///     Terminal,
45/// };
46/// # let backend = TestBackend::new(5, 5);
47/// # let mut terminal = Terminal::new(backend).unwrap();
48///
49/// terminal.draw(|frame| {
50///     frame.render_widget(Clear, frame.area());
51/// });
52/// ```
53///
54/// It's common to render widgets inside other widgets:
55///
56/// ```rust
57/// use ratatui_core::buffer::Buffer;
58/// use ratatui_core::layout::Rect;
59/// use ratatui_core::text::Line;
60/// use ratatui_core::widgets::Widget;
61///
62/// struct MyWidget;
63///
64/// impl Widget for MyWidget {
65///     fn render(self, area: Rect, buf: &mut Buffer) {
66///         Line::raw("Hello").render(area, buf);
67///     }
68/// }
69/// ```
70pub trait Widget {
71    /// Draws the current state of the widget in the given buffer. That is the only method required
72    /// to implement a custom widget.
73    fn render(self, area: Rect, buf: &mut Buffer)
74    where
75        Self: Sized;
76}
77
78/// Renders a string slice as a widget.
79///
80/// This implementation allows a string slice (`&str`) to act as a widget, meaning it can be drawn
81/// onto a [`Buffer`] in a specified [`Rect`]. The slice represents a static string which can be
82/// rendered by reference, thereby avoiding the need for string cloning or ownership transfer when
83/// drawing the text to the screen.
84impl Widget for &str {
85    fn render(self, area: Rect, buf: &mut Buffer) {
86        buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
87    }
88}
89
90/// Renders a `String` object as a widget.
91///
92/// This implementation enables an owned `String` to be treated as a widget, which can be rendered
93/// on a [`Buffer`] within the bounds of a given [`Rect`].
94impl Widget for String {
95    fn render(self, area: Rect, buf: &mut Buffer) {
96        buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
97    }
98}
99
100impl<W: Widget> Widget for Option<W> {
101    fn render(self, area: Rect, buf: &mut Buffer) {
102        if let Some(widget) = self {
103            widget.render(area, buf);
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use rstest::{fixture, rstest};
111
112    use super::*;
113    use crate::buffer::Buffer;
114    use crate::layout::Rect;
115    use crate::text::Line;
116
117    #[fixture]
118    fn buf() -> Buffer {
119        Buffer::empty(Rect::new(0, 0, 20, 1))
120    }
121
122    struct Greeting;
123
124    impl Widget for Greeting {
125        fn render(self, area: Rect, buf: &mut Buffer) {
126            Line::from("Hello").render(area, buf);
127        }
128    }
129
130    #[rstest]
131    fn render(mut buf: Buffer) {
132        let widget = Greeting;
133        widget.render(buf.area, &mut buf);
134        assert_eq!(buf, Buffer::with_lines(["Hello               "]));
135    }
136
137    #[rstest]
138    fn render_str(mut buf: Buffer) {
139        "hello world".render(buf.area, &mut buf);
140        assert_eq!(buf, Buffer::with_lines(["hello world         "]));
141    }
142
143    #[rstest]
144    fn render_str_truncate(mut buf: Buffer) {
145        let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
146        "hello world, just hello".render(area, &mut buf);
147        assert_eq!(buf, Buffer::with_lines(["hello world         "]));
148    }
149
150    #[rstest]
151    fn render_option_str(mut buf: Buffer) {
152        Some("hello world").render(buf.area, &mut buf);
153        assert_eq!(buf, Buffer::with_lines(["hello world         "]));
154    }
155
156    #[rstest]
157    fn render_string(mut buf: Buffer) {
158        String::from("hello world").render(buf.area, &mut buf);
159        assert_eq!(buf, Buffer::with_lines(["hello world         "]));
160    }
161
162    #[rstest]
163    fn render_string_truncate(mut buf: Buffer) {
164        let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
165        String::from("hello world, just hello").render(area, &mut buf);
166        assert_eq!(buf, Buffer::with_lines(["hello world         "]));
167    }
168
169    #[rstest]
170    fn render_option_string(mut buf: Buffer) {
171        Some(String::from("hello world")).render(buf.area, &mut buf);
172        assert_eq!(buf, Buffer::with_lines(["hello world         "]));
173    }
174}