refactor: move {Stateful,}Widget{,Ref} types into individual files (#1479)

This is a preparatory refactoring for modularization. No user visible
changes.
This commit is contained in:
Josh McKinney 2024-11-12 04:34:48 -08:00 committed by GitHub
parent 02c8c9373e
commit e461b724a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 715 additions and 687 deletions

View file

@ -1,693 +1,12 @@
#![warn(missing_docs)]
//! The `widgets` module contains the `Widget` and `StatefulWidget` traits, which are used to
//! render UI elements on the screen.
use crate::{buffer::Buffer, layout::Rect, style::Style};
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
///
/// Prior to Ratatui 0.26.0, widgets generally were created for each frame as they were consumed
/// during rendering. This meant that they were not meant to be stored but used as *commands* to
/// draw common figures in the UI.
///
/// Starting with Ratatui 0.26.0, all the internal widgets implement Widget for a reference to
/// themselves. This allows you to store a reference to a widget and render it later. Widget crates
/// should consider also doing this to allow for more flexibility in how widgets are used.
///
/// In Ratatui 0.26.0, we also added an unstable [`WidgetRef`] trait and implemented this on all the
/// internal widgets. In addition to the above benefit of rendering references to widgets, this also
/// allows you to render boxed widgets. This is useful when you want to store a collection of
/// widgets with different types. You can then iterate over the collection and render each widget.
/// See <https://github.com/ratatui/ratatui/issues/1287> for more information.
///
/// In general where you expect a widget to immutably work on its data, we recommended to implement
/// `Widget` for a reference to the widget (`impl Widget for &MyWidget`). If you need to store state
/// between draw calls, implement `StatefulWidget` if you want the Widget to be immutable, or
/// implement `Widget` for a mutable reference to the widget (`impl Widget for &mut MyWidget`) if
/// you want the widget to be mutable. The mutable widget pattern is used infrequently in apps, but
/// can be quite useful.
///
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
/// Widget is also implemented for `&str` and `String` types.
///
/// # Examples
///
/// ```rust,ignore
/// use ratatui::{
/// backend::TestBackend,
/// widgets::{Clear, Widget},
/// Terminal,
/// };
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
///
/// terminal.draw(|frame| {
/// frame.render_widget(Clear, frame.area());
/// });
/// ```
///
/// It's common to render widgets inside other widgets:
///
/// ```rust
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
///
/// struct MyWidget;
///
/// impl Widget for MyWidget {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Hello").render(area, buf);
/// }
/// }
/// ```
pub trait Widget {
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom widget.
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized;
}
/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
/// between two draw calls.
///
/// Most widgets can be drawn directly based on the input parameters. However, some features may
/// require some kind of associated state to be implemented.
///
/// For example, the `List` widget can highlight the item currently selected. This can be translated
/// in an offset, which is the number of elements to skip in order to have the selected item within
/// the viewport currently allocated to this widget. The widget can therefore only provide the
/// following behavior: whenever the selected item is out of the viewport scroll to a predefined
/// position (making the selected item the last viewable item or the one in the middle for example).
/// Nonetheless, if the widget has access to the last computed offset then it can implement a
/// natural scrolling experience where the last offset is reused until the selected item is out of
/// the viewport.
///
/// ## Examples
///
/// ```rust,ignore
/// use std::io;
///
/// use ratatui::{
/// backend::TestBackend,
/// widgets::{List, ListItem, ListState, StatefulWidget, Widget},
/// Terminal,
/// };
///
/// // Let's say we have some events to display.
/// struct Events {
/// // `items` is the state managed by your application.
/// items: Vec<String>,
/// // `state` is the state that can be modified by the UI. It stores the index of the selected
/// // item as well as the offset computed during the previous draw call (used to implement
/// // natural scrolling).
/// state: ListState,
/// }
///
/// impl Events {
/// fn new(items: Vec<String>) -> Events {
/// Events {
/// items,
/// state: ListState::default(),
/// }
/// }
///
/// pub fn set_items(&mut self, items: Vec<String>) {
/// self.items = items;
/// // We reset the state as the associated items have changed. This effectively reset
/// // the selection as well as the stored offset.
/// self.state = ListState::default();
/// }
///
/// // Select the next item. This will not be reflected until the widget is drawn in the
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
/// pub fn next(&mut self) {
/// let i = match self.state.selected() {
/// Some(i) => {
/// if i >= self.items.len() - 1 {
/// 0
/// } else {
/// i + 1
/// }
/// }
/// None => 0,
/// };
/// self.state.select(Some(i));
/// }
///
/// // Select the previous item. This will not be reflected until the widget is drawn in the
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
/// pub fn previous(&mut self) {
/// let i = match self.state.selected() {
/// Some(i) => {
/// if i == 0 {
/// self.items.len() - 1
/// } else {
/// i - 1
/// }
/// }
/// None => 0,
/// };
/// self.state.select(Some(i));
/// }
///
/// // Unselect the currently selected item if any. The implementation of `ListState` makes
/// // sure that the stored offset is also reset.
/// pub fn unselect(&mut self) {
/// self.state.select(None);
/// }
/// }
///
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
///
/// let mut events = Events::new(vec![String::from("Item 1"), String::from("Item 2")]);
///
/// loop {
/// terminal.draw(|f| {
/// // The items managed by the application are transformed to something
/// // that is understood by ratatui.
/// let items: Vec<ListItem> = events
/// .items
/// .iter()
/// .map(|i| ListItem::new(i.as_str()))
/// .collect();
/// // The `List` widget is then built with those items.
/// let list = List::new(items);
/// // Finally the widget is rendered using the associated state. `events.state` is
/// // effectively the only thing that we will "remember" from this draw call.
/// f.render_stateful_widget(list, f.size(), &mut events.state);
/// });
///
/// // In response to some input events or an external http request or whatever:
/// events.next();
/// }
/// ```
pub trait StatefulWidget {
/// State associated with the stateful widget.
///
/// If you don't need this then you probably want to implement [`Widget`] instead.
type State;
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom stateful widget.
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
}
/// A `WidgetRef` is a trait that allows rendering a widget by reference.
///
/// This trait is useful when you want to store a reference to a widget and render it later. It also
/// allows you to render boxed widgets.
///
/// Boxed widgets allow you to store widgets with a type that is not known at compile time. This is
/// useful when you want to store a collection of widgets with different types. You can then iterate
/// over the collection and render each widget.
///
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal widgets. It
/// is currently marked as unstable as we are still evaluating the API and may make changes in the
/// future. See <https://github.com/ratatui/ratatui/issues/1287> for more information.
///
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
///
/// A blanket implementation of `WidgetRef` for `Option<W>` where `W` implements `WidgetRef` is
/// provided. This is a convenience approach to make it easier to attach child widgets to parent
/// widgets. It allows you to render an optional widget by reference.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
///
/// struct Greeting;
///
/// struct Farewell;
///
/// impl WidgetRef for Greeting {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Hello").render(area, buf);
/// }
/// }
///
/// /// Only needed for backwards compatibility
/// impl Widget for Greeting {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// self.render_ref(area, buf);
/// }
/// }
///
/// impl WidgetRef for Farewell {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Goodbye").right_aligned().render(area, buf);
/// }
/// }
///
/// /// Only needed for backwards compatibility
/// impl Widget for Farewell {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// self.render_ref(area, buf);
/// }
/// }
///
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let greeting = Greeting;
/// let farewell = Farewell;
///
/// // these calls do not consume the widgets, so they can be used again later
/// greeting.render_ref(area, buf);
/// farewell.render_ref(area, buf);
///
/// // a collection of widgets with different types
/// let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(greeting), Box::new(farewell)];
/// for widget in widgets {
/// widget.render_ref(area, buf);
/// }
/// # }
/// # }
/// ```
pub use self::{stateful_widget::StatefulWidget, widget::Widget};
#[instability::unstable(feature = "widget-ref")]
pub trait WidgetRef {
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom widget.
fn render_ref(&self, area: Rect, buf: &mut Buffer);
}
pub use self::{stateful_widget_ref::StatefulWidgetRef, widget_ref::WidgetRef};
/// This allows you to render a widget by reference.
impl<W: WidgetRef> Widget for &W {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
/// A blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
///
/// This is a convenience implementation that makes it easy to attach child widgets to parent
/// widgets. It allows you to render an optional widget by reference.
///
/// The internal widgets use this pattern to render the optional `Block` widgets that are included
/// on most widgets.
/// Blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
///
/// struct Parent {
/// child: Option<Child>,
/// }
///
/// struct Child;
///
/// impl WidgetRef for Child {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Hello from child").render(area, buf);
/// }
/// }
///
/// impl WidgetRef for Parent {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// self.child.render_ref(area, buf);
/// }
/// }
/// # }
/// ```
impl<W: WidgetRef> WidgetRef for Option<W> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
if let Some(widget) = self {
widget.render_ref(area, buf);
}
}
}
/// A `StatefulWidgetRef` is a trait that allows rendering a stateful widget by reference.
///
/// This is the stateful equivalent of `WidgetRef`. It is useful when you want to store a reference
/// to a stateful widget and render it later. It also allows you to render boxed stateful widgets.
///
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal stateful
/// widgets. It is currently marked as unstable as we are still evaluating the API and may make
/// changes in the future. See <https://github.com/ratatui/ratatui/issues/1287> for more
/// information.
///
/// A blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`
/// is provided.
///
/// See the documentation for [`WidgetRef`] for more information on boxed widgets.
/// See the documentation for [`StatefulWidget`] for more information on stateful widgets.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// style::Stylize,
/// text::Line,
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
/// };
///
/// struct PersonalGreeting;
///
/// impl StatefulWidgetRef for PersonalGreeting {
/// type State = String;
/// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
/// Line::raw(format!("Hello {}", state)).render(area, buf);
/// }
/// }
///
/// impl StatefulWidget for PersonalGreeting {
/// type State = String;
/// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
/// (&self).render_ref(area, buf, state);
/// }
/// }
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// let widget = PersonalGreeting;
/// let mut state = "world".to_string();
/// widget.render(area, buf, &mut state);
/// }
/// # }
/// ```
#[instability::unstable(feature = "widget-ref")]
pub trait StatefulWidgetRef {
/// State associated with the stateful widget.
///
/// If you don't need this then you probably want to implement [`WidgetRef`] instead.
type State;
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom stateful widget.
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
}
// Note: while StatefulWidgetRef is marked as unstable, the blanket implementation of StatefulWidget
// cannot be implemented as W::State is effectively pub(crate) and not accessible from outside the
// crate. Once stabilized, this blanket implementation can be added and the specific implementations
// on Table and List can be removed.
//
// /// Blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`.
// ///
// /// This allows you to render a stateful widget by reference.
// impl<W: StatefulWidgetRef> StatefulWidget for &W {
// type State = W::State;
// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
// StatefulWidgetRef::render_ref(self, area, buf, state);
// }
// }
/// Renders a string slice as a widget.
///
/// This implementation allows a string slice (`&str`) to act as a widget, meaning it can be drawn
/// onto a [`Buffer`] in a specified [`Rect`]. The slice represents a static string which can be
/// rendered by reference, thereby avoiding the need for string cloning or ownership transfer when
/// drawing the text to the screen.
impl Widget for &str {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
/// Provides the ability to render a string slice by reference.
///
/// This trait implementation ensures that a string slice, which is an immutable view over a
/// `String`, can be drawn on demand without requiring ownership of the string itself. It utilizes
/// the default text style when rendering onto the provided [`Buffer`] at the position defined by
/// [`Rect`].
impl WidgetRef for &str {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
}
}
/// Renders a `String` object as a widget.
///
/// This implementation enables an owned `String` to be treated as a widget, which can be rendered
/// on a [`Buffer`] within the bounds of a given [`Rect`].
impl Widget for String {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
/// Provides the ability to render a `String` by reference.
///
/// This trait allows for a `String` to be rendered onto the [`Buffer`], similarly using the default
/// style settings. It ensures that an owned `String` can be rendered efficiently by reference,
/// without the need to give up ownership of the underlying text.
impl WidgetRef for String {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
}
}
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::text::Line;
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
mod widget {
use super::*;
struct Greeting;
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer) {
let widget = Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
}
mod widget_ref {
use super::*;
struct Greeting;
struct Farewell;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
impl WidgetRef for Farewell {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer) {
let widget = Greeting;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
/// Ensure that the blanket implementation of `Widget` for `&W` where `W` implements
/// `WidgetRef` works as expected.
#[rstest]
fn blanket_render(mut buf: Buffer) {
let widget = &Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn box_render_ref(mut buf: Buffer) {
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn vec_box_render(mut buf: Buffer) {
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
}
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
}
}
#[fixture]
fn state() -> String {
"world".to_string()
}
mod stateful_widget {
use super::*;
struct PersonalGreeting;
impl StatefulWidget for PersonalGreeting {
type State = String;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
}
mod stateful_widget_ref {
use super::*;
struct PersonalGreeting;
impl StatefulWidgetRef for PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W
// where W implements StatefulWidgetRef is added. (see the comment in the blanket
// implementation for more).
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W`
// where /// `W` implements `StatefulWidgetRef` works as expected.
// #[rstest]
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
// let widget = &PersonalGreeting;
// widget.render(buf.area, &mut buf, &mut state);
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
// }
#[rstest]
fn box_render_render(mut buf: Buffer, mut state: String) {
let widget = Box::new(PersonalGreeting);
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
}
mod option_widget_ref {
use super::*;
struct Greeting;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_ref_none(mut buf: Buffer) {
let widget: Option<Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}
}
mod str {
use super::*;
#[rstest]
fn render(mut buf: Buffer) {
"hello world".render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_area(mut buf: Buffer) {
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
"hello world, just hello".render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref(mut buf: Buffer) {
"hello world".render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render(mut buf: Buffer) {
Some("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render_ref(mut buf: Buffer) {
Some("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}
mod string {
use super::*;
#[rstest]
fn render(mut buf: Buffer) {
String::from("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_area(mut buf: Buffer) {
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
String::from("hello world, just hello").render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref(mut buf: Buffer) {
String::from("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render(mut buf: Buffer) {
Some(String::from("hello world")).render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render_ref(mut buf: Buffer) {
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}
}
mod stateful_widget;
mod stateful_widget_ref;
mod widget;
mod widget_ref;

View file

@ -0,0 +1,162 @@
use crate::{buffer::Buffer, layout::Rect};
/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
/// between two draw calls.
///
/// Most widgets can be drawn directly based on the input parameters. However, some features may
/// require some kind of associated state to be implemented.
///
/// For example, the `List` widget can highlight the item currently selected. This can be translated
/// in an offset, which is the number of elements to skip in order to have the selected item within
/// the viewport currently allocated to this widget. The widget can therefore only provide the
/// following behavior: whenever the selected item is out of the viewport scroll to a predefined
/// position (making the selected item the last viewable item or the one in the middle for example).
/// Nonetheless, if the widget has access to the last computed offset then it can implement a
/// natural scrolling experience where the last offset is reused until the selected item is out of
/// the viewport.
///
/// ## Examples
///
/// ```rust,ignore
/// use std::io;
///
/// use ratatui::{
/// backend::TestBackend,
/// widgets::{List, ListItem, ListState, StatefulWidget, Widget},
/// Terminal,
/// };
///
/// // Let's say we have some events to display.
/// struct Events {
/// // `items` is the state managed by your application.
/// items: Vec<String>,
/// // `state` is the state that can be modified by the UI. It stores the index of the selected
/// // item as well as the offset computed during the previous draw call (used to implement
/// // natural scrolling).
/// state: ListState,
/// }
///
/// impl Events {
/// fn new(items: Vec<String>) -> Events {
/// Events {
/// items,
/// state: ListState::default(),
/// }
/// }
///
/// pub fn set_items(&mut self, items: Vec<String>) {
/// self.items = items;
/// // We reset the state as the associated items have changed. This effectively reset
/// // the selection as well as the stored offset.
/// self.state = ListState::default();
/// }
///
/// // Select the next item. This will not be reflected until the widget is drawn in the
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
/// pub fn next(&mut self) {
/// let i = match self.state.selected() {
/// Some(i) => {
/// if i >= self.items.len() - 1 {
/// 0
/// } else {
/// i + 1
/// }
/// }
/// None => 0,
/// };
/// self.state.select(Some(i));
/// }
///
/// // Select the previous item. This will not be reflected until the widget is drawn in the
/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
/// pub fn previous(&mut self) {
/// let i = match self.state.selected() {
/// Some(i) => {
/// if i == 0 {
/// self.items.len() - 1
/// } else {
/// i - 1
/// }
/// }
/// None => 0,
/// };
/// self.state.select(Some(i));
/// }
///
/// // Unselect the currently selected item if any. The implementation of `ListState` makes
/// // sure that the stored offset is also reset.
/// pub fn unselect(&mut self) {
/// self.state.select(None);
/// }
/// }
///
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
///
/// let mut events = Events::new(vec![String::from("Item 1"), String::from("Item 2")]);
///
/// loop {
/// terminal.draw(|f| {
/// // The items managed by the application are transformed to something
/// // that is understood by ratatui.
/// let items: Vec<ListItem> = events
/// .items
/// .iter()
/// .map(|i| ListItem::new(i.as_str()))
/// .collect();
/// // The `List` widget is then built with those items.
/// let list = List::new(items);
/// // Finally the widget is rendered using the associated state. `events.state` is
/// // effectively the only thing that we will "remember" from this draw call.
/// f.render_stateful_widget(list, f.size(), &mut events.state);
/// });
///
/// // In response to some input events or an external http request or whatever:
/// events.next();
/// }
/// ```
pub trait StatefulWidget {
/// State associated with the stateful widget.
///
/// If you don't need this then you probably want to implement [`Widget`] instead.
///
/// [`Widget`]: super::Widget
type State;
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom stateful widget.
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
}
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
#[fixture]
fn state() -> String {
"world".to_string()
}
struct PersonalGreeting;
impl StatefulWidget for PersonalGreeting {
type State = String;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
}

View file

@ -0,0 +1,133 @@
use crate::{buffer::Buffer, layout::Rect};
/// A `StatefulWidgetRef` is a trait that allows rendering a stateful widget by reference.
///
/// This is the stateful equivalent of `WidgetRef`. It is useful when you want to store a reference
/// to a stateful widget and render it later. It also allows you to render boxed stateful widgets.
///
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal stateful
/// widgets. It is currently marked as unstable as we are still evaluating the API and may make
/// changes in the future. See <https://github.com/ratatui/ratatui/issues/1287> for more
/// information.
///
/// A blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`
/// is provided.
///
/// See the documentation for [`WidgetRef`] for more information on boxed widgets.
/// See the documentation for [`StatefulWidget`] for more information on stateful widgets.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// style::Stylize,
/// text::Line,
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
/// };
///
/// struct PersonalGreeting;
///
/// impl StatefulWidgetRef for PersonalGreeting {
/// type State = String;
/// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
/// Line::raw(format!("Hello {}", state)).render(area, buf);
/// }
/// }
///
/// impl StatefulWidget for PersonalGreeting {
/// type State = String;
/// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
/// (&self).render_ref(area, buf, state);
/// }
/// }
///
/// fn render(area: Rect, buf: &mut Buffer) {
/// let widget = PersonalGreeting;
/// let mut state = "world".to_string();
/// widget.render(area, buf, &mut state);
/// }
/// # }
/// ```
#[instability::unstable(feature = "widget-ref")]
pub trait StatefulWidgetRef {
/// State associated with the stateful widget.
///
/// If you don't need this then you probably want to implement [`WidgetRef`] instead.
///
/// [`WidgetRef`]: super::WidgetRef
type State;
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom stateful widget.
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
}
// Note: while StatefulWidgetRef is marked as unstable, the blanket implementation of StatefulWidget
// cannot be implemented as W::State is effectively pub(crate) and not accessible from outside the
// crate. Once stabilized, this blanket implementation can be added and the specific implementations
// on Table and List can be removed.
//
// /// Blanket implementation of `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef`.
// ///
// /// This allows you to render a stateful widget by reference.
// impl<W: StatefulWidgetRef> StatefulWidget for &W {
// type State = W::State;
// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
// StatefulWidgetRef::render_ref(self, area, buf, state);
// }
// }
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
#[fixture]
fn state() -> String {
"world".to_string()
}
struct PersonalGreeting;
impl StatefulWidgetRef for PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W
// where W implements StatefulWidgetRef is added. (see the comment in the blanket
// implementation for more).
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W`
// where /// `W` implements `StatefulWidgetRef` works as expected.
// #[rstest]
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
// let widget = &PersonalGreeting;
// widget.render(buf.area, &mut buf, &mut state);
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
// }
#[rstest]
fn box_render_render(mut buf: Buffer, mut state: String) {
let widget = Box::new(PersonalGreeting);
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
}

View file

@ -0,0 +1,153 @@
use super::WidgetRef;
use crate::{buffer::Buffer, layout::Rect};
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
///
/// Prior to Ratatui 0.26.0, widgets generally were created for each frame as they were consumed
/// during rendering. This meant that they were not meant to be stored but used as *commands* to
/// draw common figures in the UI.
///
/// Starting with Ratatui 0.26.0, all the internal widgets implement Widget for a reference to
/// themselves. This allows you to store a reference to a widget and render it later. Widget crates
/// should consider also doing this to allow for more flexibility in how widgets are used.
///
/// In Ratatui 0.26.0, we also added an unstable [`WidgetRef`] trait and implemented this on all the
/// internal widgets. In addition to the above benefit of rendering references to widgets, this also
/// allows you to render boxed widgets. This is useful when you want to store a collection of
/// widgets with different types. You can then iterate over the collection and render each widget.
/// See <https://github.com/ratatui/ratatui/issues/1287> for more information.
///
/// In general where you expect a widget to immutably work on its data, we recommended to implement
/// `Widget` for a reference to the widget (`impl Widget for &MyWidget`). If you need to store state
/// between draw calls, implement `StatefulWidget` if you want the Widget to be immutable, or
/// implement `Widget` for a mutable reference to the widget (`impl Widget for &mut MyWidget`) if
/// you want the widget to be mutable. The mutable widget pattern is used infrequently in apps, but
/// can be quite useful.
///
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
/// Widget is also implemented for `&str` and `String` types.
///
/// # Examples
///
/// ```rust,ignore
/// use ratatui::{
/// backend::TestBackend,
/// widgets::{Clear, Widget},
/// Terminal,
/// };
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
///
/// terminal.draw(|frame| {
/// frame.render_widget(Clear, frame.area());
/// });
/// ```
///
/// It's common to render widgets inside other widgets:
///
/// ```rust
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
///
/// struct MyWidget;
///
/// impl Widget for MyWidget {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Hello").render(area, buf);
/// }
/// }
/// ```
pub trait Widget {
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom widget.
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized;
}
/// Renders a string slice as a widget.
///
/// This implementation allows a string slice (`&str`) to act as a widget, meaning it can be drawn
/// onto a [`Buffer`] in a specified [`Rect`]. The slice represents a static string which can be
/// rendered by reference, thereby avoiding the need for string cloning or ownership transfer when
/// drawing the text to the screen.
impl Widget for &str {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
/// Renders a `String` object as a widget.
///
/// This implementation enables an owned `String` to be treated as a widget, which can be rendered
/// on a [`Buffer`] within the bounds of a given [`Rect`].
impl Widget for String {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::{buffer::Buffer, layout::Rect, text::Line};
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
struct Greeting;
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer) {
let widget = Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_str(mut buf: Buffer) {
"hello world".render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_str_truncate(mut buf: Buffer) {
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
"hello world, just hello".render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_option_str(mut buf: Buffer) {
Some("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_string(mut buf: Buffer) {
String::from("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_string_truncate(mut buf: Buffer) {
let area = Rect::new(buf.area.x, buf.area.y, 11, buf.area.height);
String::from("hello world, just hello").render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_option_string(mut buf: Buffer) {
Some(String::from("hello world")).render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}

View file

@ -0,0 +1,261 @@
use super::Widget;
use crate::{buffer::Buffer, layout::Rect, style::Style};
/// A `WidgetRef` is a trait that allows rendering a widget by reference.
///
/// This trait is useful when you want to store a reference to a widget and render it later. It also
/// allows you to render boxed widgets.
///
/// Boxed widgets allow you to store widgets with a type that is not known at compile time. This is
/// useful when you want to store a collection of widgets with different types. You can then iterate
/// over the collection and render each widget.
///
/// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal widgets. It
/// is currently marked as unstable as we are still evaluating the API and may make changes in the
/// future. See <https://github.com/ratatui/ratatui/issues/1287> for more information.
///
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
///
/// A blanket implementation of `WidgetRef` for `Option<W>` where `W` implements `WidgetRef` is
/// provided. This is a convenience approach to make it easier to attach child widgets to parent
/// widgets. It allows you to render an optional widget by reference.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
///
/// struct Greeting;
///
/// struct Farewell;
///
/// impl WidgetRef for Greeting {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Hello").render(area, buf);
/// }
/// }
///
/// /// Only needed for backwards compatibility
/// impl Widget for Greeting {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// self.render_ref(area, buf);
/// }
/// }
///
/// impl WidgetRef for Farewell {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Goodbye").right_aligned().render(area, buf);
/// }
/// }
///
/// /// Only needed for backwards compatibility
/// impl Widget for Farewell {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// self.render_ref(area, buf);
/// }
/// }
///
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let greeting = Greeting;
/// let farewell = Farewell;
///
/// // these calls do not consume the widgets, so they can be used again later
/// greeting.render_ref(area, buf);
/// farewell.render_ref(area, buf);
///
/// // a collection of widgets with different types
/// let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(greeting), Box::new(farewell)];
/// for widget in widgets {
/// widget.render_ref(area, buf);
/// }
/// # }
/// # }
/// ```
#[instability::unstable(feature = "widget-ref")]
pub trait WidgetRef {
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom widget.
fn render_ref(&self, area: Rect, buf: &mut Buffer);
}
/// This allows you to render a widget by reference.
impl<W: WidgetRef> Widget for &W {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
/// Provides the ability to render a string slice by reference.
///
/// This trait implementation ensures that a string slice, which is an immutable view over a
/// `String`, can be drawn on demand without requiring ownership of the string itself. It utilizes
/// the default text style when rendering onto the provided [`Buffer`] at the position defined by
/// [`Rect`].
impl WidgetRef for &str {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
}
}
/// Provides the ability to render a `String` by reference.
///
/// This trait allows for a `String` to be rendered onto the [`Buffer`], similarly using the default
/// style settings. It ensures that an owned `String` can be rendered efficiently by reference,
/// without the need to give up ownership of the underlying text.
impl WidgetRef for String {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
}
}
/// A blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
///
/// This is a convenience implementation that makes it easy to attach child widgets to parent
/// widgets. It allows you to render an optional widget by reference.
///
/// The internal widgets use this pattern to render the optional `Block` widgets that are included
/// on most widgets.
/// Blanket implementation of `WidgetExt` for `Option<W>` where `W` implements `WidgetRef`.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
///
/// struct Parent {
/// child: Option<Child>,
/// }
///
/// struct Child;
///
/// impl WidgetRef for Child {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// Line::raw("Hello from child").render(area, buf);
/// }
/// }
///
/// impl WidgetRef for Parent {
/// fn render_ref(&self, area: Rect, buf: &mut Buffer) {
/// self.child.render_ref(area, buf);
/// }
/// }
/// # }
/// ```
impl<W: WidgetRef> WidgetRef for Option<W> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
if let Some(widget) = self {
widget.render_ref(area, buf);
}
}
}
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::{buffer::Buffer, layout::Rect, text::Line};
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
struct Greeting;
struct Farewell;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
impl WidgetRef for Farewell {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer) {
let widget = Greeting;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
/// Ensure that the blanket implementation of `Widget` for `&W` where `W` implements
/// `WidgetRef` works as expected.
#[rstest]
fn render_widget(mut buf: Buffer) {
let widget = &Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_ref_box(mut buf: Buffer) {
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_ref_box_vec(mut buf: Buffer) {
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
}
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
}
#[rstest]
fn render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_ref_none(mut buf: Buffer) {
let widget: Option<Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}
#[rstest]
fn render_ref_str(mut buf: Buffer) {
"hello world".render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref_option_str(mut buf: Buffer) {
Some("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref_string(mut buf: Buffer) {
String::from("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref_option_string(mut buf: Buffer) {
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}