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}