mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +00:00
feat: add WidgetRef and StatefulWidgetRef traits (#903)
The Widget trait consumes self, which makes it impossible to use in a boxed context. Previously we implemented the Widget trait for &T, but this was not enough to render a boxed widget. We now have a new trait called `WidgetRef` that allows rendering a widget by reference. This trait is useful when you want to store a reference to one or more widgets and render them later. Additionaly this makes it possible to render boxed widgets where the type is not known at compile time (e.g. in a composite layout with multiple panes of different types). This change also adds a new trait called `StatefulWidgetRef` which is the stateful equivalent of `WidgetRef`. Both new traits are gated behind the `unstable-widget-ref` feature flag as we may change the exact name / approach a little on this based on further discussion. Blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` and `StatefulWidget` for `&W` where `W` implements `StatefulWidgetRef` is provided. This allows you to render a widget by reference and a stateful widget by reference. A blanket implementation of `WidgetRef` for `Option<W>` where `W` implements `WidgetRef` is provided. This makes it easier to render child widgets that are optional without the boilerplate of unwrapping the option. Previously several widgets implemented this manually. This commits expands the pattern to apply to all widgets. ```rust struct Parent { child: Option<Child>, } impl WidgetRef for Parent { fn render_ref(&self, area: Rect, buf: &mut Buffer) { self.child.render_ref(area, buf); } } ``` ```rust 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"])); ```
This commit is contained in:
parent
652dc469ea
commit
c8dd87918d
19 changed files with 483 additions and 132 deletions
|
@ -100,7 +100,7 @@ underline-color = ["dep:crossterm"]
|
|||
#! The following features are unstable and may change in the future:
|
||||
|
||||
## Enable all unstable features.
|
||||
unstable = ["unstable-rendered-line-info"]
|
||||
unstable = ["unstable-rendered-line-info", "unstable-widget-ref"]
|
||||
|
||||
## Enables the [`Paragraph::line_count`](crate::widgets::Paragraph::line_count)
|
||||
## [`Paragraph::line_width`](crate::widgets::Paragraph::line_width) methods
|
||||
|
@ -108,6 +108,10 @@ unstable = ["unstable-rendered-line-info"]
|
|||
## See [Issue 293](https://github.com/ratatui-org/ratatui/issues/293) for more details.
|
||||
unstable-rendered-line-info = []
|
||||
|
||||
## Enables the `WidgetRef` and `StatefulWidgetRef` traits which are experimental and may change in
|
||||
## the future.
|
||||
unstable-widget-ref = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html
|
||||
|
|
|
@ -23,6 +23,7 @@ pub use crate::backend::CrosstermBackend;
|
|||
pub use crate::backend::TermionBackend;
|
||||
#[cfg(feature = "termwiz")]
|
||||
pub use crate::backend::TermwizBackend;
|
||||
pub(crate) use crate::widgets::{StatefulWidgetRef, WidgetRef};
|
||||
pub use crate::{
|
||||
backend::{self, Backend},
|
||||
buffer::{self, Buffer},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::prelude::*;
|
||||
use crate::{prelude::*, widgets::WidgetRef};
|
||||
|
||||
/// A consistent view into the terminal state for rendering a single frame.
|
||||
///
|
||||
|
@ -75,6 +75,27 @@ impl Frame<'_> {
|
|||
widget.render(area, self.buffer);
|
||||
}
|
||||
|
||||
/// Render a [`WidgetRef`] to the current buffer using [`WidgetRef::render_ref`].
|
||||
///
|
||||
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
||||
/// frame (which can be obtained using [`Layout`] to split the total area).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// let block = Block::default();
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_widget_ref(block, area);
|
||||
/// ```
|
||||
#[stability::unstable(feature = "widget-ref")]
|
||||
pub fn render_widget_ref<W: WidgetRef>(&mut self, widget: W, area: Rect) {
|
||||
widget.render_ref(area, self.buffer);
|
||||
}
|
||||
|
||||
/// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
|
||||
///
|
||||
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
||||
|
@ -83,7 +104,7 @@ impl Frame<'_> {
|
|||
/// The last argument should be an instance of the [`StatefulWidget::State`] associated to the
|
||||
/// given [`StatefulWidget`].
|
||||
///
|
||||
/// # Examples
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
|
@ -104,6 +125,35 @@ impl Frame<'_> {
|
|||
widget.render(area, self.buffer, state);
|
||||
}
|
||||
|
||||
/// Render a [`StatefulWidgetRef`] to the current buffer using
|
||||
/// [`StatefulWidgetRef::render_ref`].
|
||||
///
|
||||
/// Usually the area argument is the size of the current frame or a sub-area of the current
|
||||
/// frame (which can be obtained using [`Layout`] to split the total area).
|
||||
///
|
||||
/// The last argument should be an instance of the [`StatefulWidgetRef::State`] associated to
|
||||
/// the given [`StatefulWidgetRef`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// # let mut frame = terminal.get_frame();
|
||||
/// let mut state = ListState::default().with_selected(Some(1));
|
||||
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
|
||||
/// let area = Rect::new(0, 0, 5, 5);
|
||||
/// frame.render_stateful_widget_ref(list, area, &mut state);
|
||||
/// ```
|
||||
#[stability::unstable(feature = "widget-ref")]
|
||||
pub fn render_stateful_widget_ref<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
|
||||
where
|
||||
W: StatefulWidgetRef,
|
||||
{
|
||||
widget.render_ref(area, self.buffer, state);
|
||||
}
|
||||
|
||||
/// After drawing this frame, make the cursor visible and put it at the specified (x, y)
|
||||
/// coordinates. If this method is not called, the cursor will be hidden.
|
||||
///
|
||||
|
|
|
@ -445,22 +445,12 @@ impl<'a> From<Line<'a>> for String {
|
|||
|
||||
impl Widget for Line<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`Widget`] for [`Option<Line>`] to simplify the common case of having an optional
|
||||
/// [`Line`] field in a widget.
|
||||
impl Widget for &Option<Line<'_>> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if let Some(line) = self {
|
||||
line.render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Line<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Line<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let area = area.intersection(buf.area);
|
||||
buf.set_style(area, self.style);
|
||||
let width = self.width() as u16;
|
||||
|
|
|
@ -337,22 +337,12 @@ impl<'a> Styled for Span<'a> {
|
|||
|
||||
impl Widget for Span<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`Widget`] for [`Option<Span>`] to simplify the common case of having an optional
|
||||
/// [`Span`] field in a widget.
|
||||
impl Widget for &Option<Span<'_>> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if let Some(span) = self {
|
||||
span.render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Span<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Span<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let Rect {
|
||||
x: mut current_x,
|
||||
y,
|
||||
|
|
|
@ -444,22 +444,12 @@ impl std::fmt::Display for Text<'_> {
|
|||
|
||||
impl Widget for Text<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`Widget`] for [`Option<Text>`] to simplify the common case of having an optional
|
||||
/// [`Text`] field in a widget.
|
||||
impl Widget for &Option<Text<'_>> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if let Some(text) = self {
|
||||
text.render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Text<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Text<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
for (line, row) in self.iter().zip(area.rows()) {
|
||||
let line_width = line.width() as u16;
|
||||
|
|
370
src/widgets.rs
370
src/widgets.rs
|
@ -59,9 +59,16 @@ use crate::{buffer::Buffer, layout::Rect};
|
|||
/// 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, the `Widget` trait was more universally implemented on &T instead
|
||||
/// of just T. This means that widgets can be stored and reused across frames without having to
|
||||
/// clone or recreate them.
|
||||
/// Starting with Ratatui 0.26.0, we added a new [`WidgetRef`] trait and implemented this on all the
|
||||
/// internal widgets. This allows you to store a reference to a widget and render it later. It 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.
|
||||
///
|
||||
/// The `Widget` trait can still be implemented, however, it is recommended to implement `WidgetRef`
|
||||
/// and add an implementation of `Widget` that calls `WidgetRef::render_ref`. This pattern should be
|
||||
/// used where backwards compatibility is required (all the internal widgets use this approach).
|
||||
///
|
||||
/// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -75,20 +82,6 @@ use crate::{buffer::Buffer, layout::Rect};
|
|||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Rendering a widget by reference:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
|
||||
/// # let backend = TestBackend::new(5, 5);
|
||||
/// # let mut terminal = Terminal::new(backend).unwrap();
|
||||
/// // this variable could instead be a value stored in a struct and reused across frames
|
||||
/// let paragraph = Paragraph::new("Hello world!");
|
||||
///
|
||||
/// terminal.draw(|frame| {
|
||||
/// frame.render_widget(¶graph, frame.size());
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// It's common to render widgets inside other widgets:
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -96,20 +89,18 @@ use crate::{buffer::Buffer, layout::Rect};
|
|||
///
|
||||
/// struct MyWidget;
|
||||
///
|
||||
/// impl Widget for &MyWidget {
|
||||
/// impl Widget for MyWidget {
|
||||
/// fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
/// Block::default()
|
||||
/// .title("My Widget")
|
||||
/// .borders(Borders::ALL)
|
||||
/// .render(area, buf);
|
||||
/// // ...
|
||||
/// 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);
|
||||
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
|
||||
|
@ -227,3 +218,334 @@ pub trait StatefulWidget {
|
|||
type State;
|
||||
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.
|
||||
/// Implementors should prefer to implement this over the `Widget` trait and add an implementation
|
||||
/// of `Widget` that calls `WidgetRef::render_ref` where backwards compatibility is required.
|
||||
///
|
||||
/// 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
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// 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);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[stability::unstable(feature = "widget-ref")]
|
||||
pub trait WidgetRef {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// 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. Implemetors should prefer to implement this over the `StatefulWidget` trait and add an
|
||||
/// implementation of `StatefulWidget` that calls `StatefulWidgetRef::render_ref` where backwards
|
||||
/// compatibility is required.
|
||||
///
|
||||
/// 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
|
||||
/// use ratatui::{prelude::*, widgets::*};
|
||||
///
|
||||
/// 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);
|
||||
/// # }
|
||||
/// ```
|
||||
#[stability::unstable(feature = "widget-ref")]
|
||||
pub trait StatefulWidgetRef {
|
||||
type State;
|
||||
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::prelude::*;
|
||||
|
||||
struct Greeting;
|
||||
struct Farewell;
|
||||
struct PersonalGreeting;
|
||||
|
||||
impl Widget for Greeting {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for Greeting {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Line::from("Hello").render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Farewell {
|
||||
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::from("Goodbye").right_aligned().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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn buf() -> Buffer {
|
||||
Buffer::empty(Rect::new(0, 0, 20, 1))
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn widget_render(mut buf: Buffer) {
|
||||
let widget = Greeting;
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn widget_ref_render(mut buf: Buffer) {
|
||||
let widget = Greeting;
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
/// This test is to ensure that the blanket implementation of `Widget` for `&W` where `W`
|
||||
/// implements `WidgetRef` works as expected.
|
||||
#[rstest]
|
||||
fn widget_blanket_render(mut buf: Buffer) {
|
||||
let widget = &Greeting;
|
||||
widget.render(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn widget_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 widget_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()
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn stateful_widget_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 stateful_widget_ref_render(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 stateful_widget_box_render(mut buf: Buffer, mut state: String) {
|
||||
let widget = Box::new(PersonalGreeting);
|
||||
widget.render(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn widget_option_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 widget_option_render_ref_none(mut buf: Buffer) {
|
||||
let widget: Option<Greeting> = None;
|
||||
widget.render_ref(buf.area, &mut buf);
|
||||
assert_eq!(buf, Buffer::with_lines([" "]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -576,15 +576,15 @@ impl BarChart<'_> {
|
|||
|
||||
impl Widget for BarChart<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
(&self).render(area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &BarChart<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for BarChart<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
|
||||
if inner.is_empty() || self.data.is_empty() || self.bar_width == 0 {
|
||||
|
|
|
@ -520,22 +520,12 @@ impl<'a> Block<'a> {
|
|||
|
||||
impl Widget for Block<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`Widget`] for [`Option<Block>`] to simplify the common case of having an optional
|
||||
/// [`Block`] field in a widget.
|
||||
impl Widget for &Option<Block<'_>> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if let Some(block) = self {
|
||||
block.render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Block<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Block<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
if area.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -116,13 +116,13 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
|||
|
||||
impl<DS: DateStyler> Widget for Monthly<'_, DS> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl<DS: DateStyler> Widget for &Monthly<'_, DS> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.block.render(area, buf);
|
||||
impl<DS: DateStyler> WidgetRef for Monthly<'_, DS> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
self.render_monthly(inner, buf);
|
||||
}
|
||||
|
|
|
@ -692,16 +692,16 @@ where
|
|||
F: Fn(&mut Context),
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Widget for &Canvas<'_, F>
|
||||
impl<F> WidgetRef for Canvas<'_, F>
|
||||
where
|
||||
F: Fn(&mut Context),
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.block.render(area, buf);
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.block.render_ref(area, buf);
|
||||
let canvas_area = self.block.inner_if_some(area);
|
||||
if canvas_area.is_empty() {
|
||||
return;
|
||||
|
|
|
@ -919,15 +919,15 @@ impl<'a> Chart<'a> {
|
|||
|
||||
impl Widget for Chart<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Chart<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Chart<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let chart_area = self.block.inner_if_some(area);
|
||||
if chart_area.is_empty() {
|
||||
return;
|
||||
|
|
|
@ -26,12 +26,12 @@ pub struct Clear;
|
|||
|
||||
impl Widget for Clear {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Clear {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Clear {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
buf.get_mut(x, y).reset();
|
||||
|
|
|
@ -157,14 +157,14 @@ impl<'a> Gauge<'a> {
|
|||
|
||||
impl Widget for Gauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Gauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Gauge<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
self.render_gague(inner, buf);
|
||||
}
|
||||
|
@ -352,14 +352,14 @@ impl<'a> LineGauge<'a> {
|
|||
|
||||
impl Widget for LineGauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &LineGauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for LineGauge<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let gauge_area = self.block.inner_if_some(area);
|
||||
if gauge_area.is_empty() {
|
||||
return;
|
||||
|
|
|
@ -814,15 +814,14 @@ impl<'a> List<'a> {
|
|||
|
||||
impl Widget for List<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = ListState::default();
|
||||
StatefulWidget::render(&self, area, buf, &mut state);
|
||||
WidgetRef::render_ref(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &List<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for List<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = ListState::default();
|
||||
StatefulWidget::render(self, area, buf, &mut state);
|
||||
StatefulWidgetRef::render_ref(self, area, buf, &mut state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -830,16 +829,24 @@ impl StatefulWidget for List<'_> {
|
|||
type State = ListState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
StatefulWidget::render(&self, area, buf, state);
|
||||
StatefulWidgetRef::render_ref(&self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: remove this when StatefulWidgetRef is stabilized and replace with the blanket impl
|
||||
impl StatefulWidget for &List<'_> {
|
||||
type State = ListState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidgetRef for List<'_> {
|
||||
type State = ListState;
|
||||
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let list_area = self.block.inner_if_some(area);
|
||||
|
||||
if list_area.is_empty() || self.items.is_empty() {
|
||||
|
|
|
@ -320,14 +320,14 @@ impl<'a> Paragraph<'a> {
|
|||
|
||||
impl Widget for Paragraph<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Paragraph<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Paragraph<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
self.render_paragraph(inner, buf);
|
||||
}
|
||||
|
|
|
@ -155,13 +155,13 @@ impl<'a> Styled for Sparkline<'a> {
|
|||
|
||||
impl Widget for Sparkline<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Sparkline<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.block.render(area, buf);
|
||||
impl WidgetRef for Sparkline<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
self.render_sparkline(inner, buf);
|
||||
}
|
||||
|
|
|
@ -565,13 +565,12 @@ impl<'a> Table<'a> {
|
|||
|
||||
impl Widget for Table<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TableState::default();
|
||||
StatefulWidget::render(self, area, buf, &mut state);
|
||||
WidgetRef::render_ref(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Table<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Table<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TableState::default();
|
||||
StatefulWidget::render(self, area, buf, &mut state);
|
||||
}
|
||||
|
@ -585,12 +584,20 @@ impl StatefulWidget for Table<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: remove this when StatefulWidgetRef is stabilized and replace with the blanket impl
|
||||
impl StatefulWidget for &Table<'_> {
|
||||
type State = TableState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
StatefulWidgetRef::render_ref(self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidgetRef for Table<'_> {
|
||||
type State = TableState;
|
||||
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let table_area = self.block.inner_if_some(area);
|
||||
if table_area.is_empty() {
|
||||
return;
|
||||
|
|
|
@ -248,14 +248,14 @@ impl<'a> Styled for Tabs<'a> {
|
|||
|
||||
impl Widget for Tabs<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
self.render_ref(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Tabs<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl WidgetRef for Tabs<'_> {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
self.block.render(area, buf);
|
||||
self.block.render_ref(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
self.render_tabs(inner, buf);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue