chore: move unstable widget refs to ratatui (#1491)

These are less stable than the non-ref traits as we have not yet
committed to the exact API. This change moves them to ratatui from
ratatui-core.

To facilitate this:
- implementations of WidgetRef for all internal widgets are removed and
  replaced with implementations of Widget for references to those
  widgets.
- Widget is now implemented for Option<W> where W: Widget, allowing for
  rendering of optional widgets.
- The blanket implementation of Widget for WidgetRef is reversed, to be
  a blanket implementation of WidgetRef for all &W where W: Widget.

BREAKING CHANGE: implementations of WidgetRef no longer have a blanket
implementation of Widget, so Widgets should generally implement the
Widget trait on a reference to the widget rather than implementing
WidgetRef directly. This has the advantage of not requiring unstable
features to be enabled.

Part of: https://github.com/ratatui/ratatui/issues/1388

Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
This commit is contained in:
Josh McKinney 2024-11-18 14:19:21 -08:00 committed by GitHub
parent 46902f5587
commit a41c97b413
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 348 additions and 329 deletions

View file

@ -76,6 +76,21 @@ This is a quick summary of the sections below:
## Unreleased (0.30.0)
### `WidgetRef` no longer has a blanket implementation of Widget
Previously there was a blanket implementation of Widget for WidgetRef. This has been reversed to
instead be a blanket implementation of WidgetRef for all &W where W: Widget. Any widgets that
previously implemented WidgetRef directly should now instead implement Widget for a reference to the
type.
```diff
-impl WidgetRef for Foo {
- fn render_ref(&self, area: Rect, buf: &mut Buffer)
+impl Widget for &Foo {
+ fn render(self, area: Rect, buf: &mut Buffer)
}
```
### The `From` impls for backend types are now replaced with more specific traits [#1464]
[#1464]: https://github.com/ratatui/ratatui/pull/1464

1
Cargo.lock generated
View file

@ -2129,7 +2129,6 @@ dependencies = [
"compact_str",
"document-features",
"indoc",
"instability",
"itertools 0.13.0",
"lru",
"palette",

View file

@ -12,54 +12,96 @@ command = ["cargo", "check", "--all-features", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--all-features", "--color", "always"]
command = [
"cargo",
"check",
"--all-targets",
"--all-features",
"--color",
"always",
]
need_stdout = false
[jobs.check-crossterm]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "crossterm"]
command = [
"cargo",
"check",
"--color",
"always",
"--all-targets",
"--no-default-features",
"--features",
"crossterm",
]
need_stdout = false
[jobs.check-termion]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termion"]
command = [
"cargo",
"check",
"--color",
"always",
"--all-targets",
"--no-default-features",
"--features",
"termion",
]
need_stdout = false
[jobs.check-termwiz]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termwiz"]
command = [
"cargo",
"check",
"--color",
"always",
"--all-targets",
"--no-default-features",
"--features",
"termwiz",
]
need_stdout = false
[jobs.clippy]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
command = ["cargo", "clippy", "--all-targets", "--color", "always"]
need_stdout = false
[jobs.test]
command = [
"cargo", "test",
"cargo",
"test",
"--all-features",
"--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
"--color",
"always",
"--",
"--color",
"always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.test-unit]
command = [
"cargo", "test",
"cargo",
"test",
"--lib",
"--all-features",
"--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
"--color",
"always",
"--",
"--color",
"always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.doc]
command = [
"cargo", "+nightly", "doc",
"-Zunstable-options", "-Zrustdoc-scrape-examples",
"cargo",
"+nightly",
"doc",
"-Zunstable-options",
"-Zrustdoc-scrape-examples",
"--all-features",
"--color", "always",
"--color",
"always",
"--no-deps",
]
env.RUSTDOCFLAGS = "--cfg docsrs"
@ -69,10 +111,14 @@ need_stdout = false
# to the previous job
[jobs.doc-open]
command = [
"cargo", "+nightly", "doc",
"-Zunstable-options", "-Zrustdoc-scrape-examples",
"cargo",
"+nightly",
"doc",
"-Zunstable-options",
"-Zrustdoc-scrape-examples",
"--all-features",
"--color", "always",
"--color",
"always",
"--no-deps",
"--open",
]
@ -82,19 +128,40 @@ on_success = "job:doc" # so that we don't open the browser at each change
[jobs.coverage]
command = [
"cargo", "llvm-cov",
"--lcov", "--output-path", "target/lcov.info",
"cargo",
"llvm-cov",
"--lcov",
"--output-path",
"target/lcov.info",
"--all-features",
"--color", "always",
"--color",
"always",
]
[jobs.coverage-unit-tests-only]
command = [
"cargo", "llvm-cov",
"--lcov", "--output-path", "target/lcov.info",
"cargo",
"llvm-cov",
"--lcov",
"--output-path",
"target/lcov.info",
"--lib",
"--all-features",
"--color", "always",
"--color",
"always",
]
[jobs.hack]
command = [
"cargo",
"hack",
"test",
"--lib",
"--each-feature",
# "--all-targets",
"--workspace",
"--color",
"always",
]
# You may define here keybindings that would be specific to
@ -102,7 +169,7 @@ command = [
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
ctrl-h = "job:hack"
ctrl-c = "job:check-crossterm"
ctrl-t = "job:check-termion"
ctrl-w = "job:check-termwiz"

View file

@ -29,16 +29,6 @@ underline-color = []
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
## enable all unstable features.
unstable = ["unstable-widget-ref"]
## enables the [`WidgetRef`] and [`StatefulWidgetRef`] traits which are experimental and may change
## in the future.
##
## [`WidgetRef`]: widgets::WidgetRef
## [`StatefulWidgetRef`]: widgets::StatefulWidgetRef
unstable-widget-ref = []
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
@ -48,7 +38,6 @@ bitflags = "2.3"
cassowary = "0.3"
compact_str = "0.8.0"
document-features = { workspace = true, optional = true }
instability.workspace = true
indoc.workspace = true
itertools.workspace = true
lru = "0.12.0"

View file

@ -501,80 +501,75 @@ mod tests {
}
#[rstest]
#[case(ColorDebugKind::Foreground, Color::Black, ".black()")]
#[case(ColorDebugKind::Foreground, Color::Red, ".red()")]
#[case(ColorDebugKind::Foreground, Color::Green, ".green()")]
#[case(ColorDebugKind::Foreground, Color::Yellow, ".yellow()")]
#[case(ColorDebugKind::Foreground, Color::Blue, ".blue()")]
#[case(ColorDebugKind::Foreground, Color::Magenta, ".magenta()")]
#[case(ColorDebugKind::Foreground, Color::Cyan, ".cyan()")]
#[case(ColorDebugKind::Foreground, Color::Gray, ".gray()")]
#[case(ColorDebugKind::Foreground, Color::DarkGray, ".dark_gray()")]
#[case(ColorDebugKind::Foreground, Color::LightRed, ".light_red()")]
#[case(ColorDebugKind::Foreground, Color::LightGreen, ".light_green()")]
#[case(ColorDebugKind::Foreground, Color::LightYellow, ".light_yellow()")]
#[case(ColorDebugKind::Foreground, Color::LightBlue, ".light_blue()")]
#[case(ColorDebugKind::Foreground, Color::LightMagenta, ".light_magenta()")]
#[case(ColorDebugKind::Foreground, Color::LightCyan, ".light_cyan()")]
#[case(ColorDebugKind::Foreground, Color::White, ".white()")]
#[case(
ColorDebugKind::Foreground,
Color::Indexed(10),
".fg(Color::Indexed(10))"
)]
#[case(
ColorDebugKind::Foreground,
Color::Rgb(255, 0, 0),
".fg(Color::Rgb(255, 0, 0))"
)]
#[case(ColorDebugKind::Background, Color::Black, ".on_black()")]
#[case(ColorDebugKind::Background, Color::Red, ".on_red()")]
#[case(ColorDebugKind::Background, Color::Green, ".on_green()")]
#[case(ColorDebugKind::Background, Color::Yellow, ".on_yellow()")]
#[case(ColorDebugKind::Background, Color::Blue, ".on_blue()")]
#[case(ColorDebugKind::Background, Color::Magenta, ".on_magenta()")]
#[case(ColorDebugKind::Background, Color::Cyan, ".on_cyan()")]
#[case(ColorDebugKind::Background, Color::Gray, ".on_gray()")]
#[case(ColorDebugKind::Background, Color::DarkGray, ".on_dark_gray()")]
#[case(ColorDebugKind::Background, Color::LightRed, ".on_light_red()")]
#[case(ColorDebugKind::Background, Color::LightGreen, ".on_light_green()")]
#[case(ColorDebugKind::Background, Color::LightYellow, ".on_light_yellow()")]
#[case(ColorDebugKind::Background, Color::LightBlue, ".on_light_blue()")]
#[case(ColorDebugKind::Background, Color::LightMagenta, ".on_light_magenta()")]
#[case(ColorDebugKind::Background, Color::LightCyan, ".on_light_cyan()")]
#[case(ColorDebugKind::Background, Color::White, ".on_white()")]
#[case(
ColorDebugKind::Background,
Color::Indexed(10),
".bg(Color::Indexed(10))"
)]
#[case(
ColorDebugKind::Background,
Color::Rgb(255, 0, 0),
".bg(Color::Rgb(255, 0, 0))"
)]
#[case(Color::Black, ".black()")]
#[case(Color::Red, ".red()")]
#[case(Color::Green, ".green()")]
#[case(Color::Yellow, ".yellow()")]
#[case(Color::Blue, ".blue()")]
#[case(Color::Magenta, ".magenta()")]
#[case(Color::Cyan, ".cyan()")]
#[case(Color::Gray, ".gray()")]
#[case(Color::DarkGray, ".dark_gray()")]
#[case(Color::LightRed, ".light_red()")]
#[case(Color::LightGreen, ".light_green()")]
#[case(Color::LightYellow, ".light_yellow()")]
#[case(Color::LightBlue, ".light_blue()")]
#[case(Color::LightMagenta, ".light_magenta()")]
#[case(Color::LightCyan, ".light_cyan()")]
#[case(Color::White, ".white()")]
#[case(Color::Indexed(10), ".fg(Color::Indexed(10))")]
#[case(Color::Rgb(255, 0, 0), ".fg(Color::Rgb(255, 0, 0))")]
fn stylize_debug_foreground(#[case] color: Color, #[case] expected: &str) {
let debug = color.stylize_debug(ColorDebugKind::Foreground);
assert_eq!(format!("{debug:?}"), expected);
}
#[rstest]
#[case(Color::Black, ".on_black()")]
#[case(Color::Red, ".on_red()")]
#[case(Color::Green, ".on_green()")]
#[case(Color::Yellow, ".on_yellow()")]
#[case(Color::Blue, ".on_blue()")]
#[case(Color::Magenta, ".on_magenta()")]
#[case(Color::Cyan, ".on_cyan()")]
#[case(Color::Gray, ".on_gray()")]
#[case(Color::DarkGray, ".on_dark_gray()")]
#[case(Color::LightRed, ".on_light_red()")]
#[case(Color::LightGreen, ".on_light_green()")]
#[case(Color::LightYellow, ".on_light_yellow()")]
#[case(Color::LightBlue, ".on_light_blue()")]
#[case(Color::LightMagenta, ".on_light_magenta()")]
#[case(Color::LightCyan, ".on_light_cyan()")]
#[case(Color::White, ".on_white()")]
#[case(Color::Indexed(10), ".bg(Color::Indexed(10))")]
#[case(Color::Rgb(255, 0, 0), ".bg(Color::Rgb(255, 0, 0))")]
fn stylize_debug_background(#[case] color: Color, #[case] expected: &str) {
let debug = color.stylize_debug(ColorDebugKind::Background);
assert_eq!(format!("{debug:?}"), expected);
}
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Black,
".underline_color(Color::Black)"
)]
#[cfg(feature = "underline-color")]
#[case(ColorDebugKind::Underline, Color::Red, ".underline_color(Color::Red)")]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Green,
".underline_color(Color::Green)"
)]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Yellow,
".underline_color(Color::Yellow)"
)]
fn stylize_debug(#[case] kind: ColorDebugKind, #[case] color: Color, #[case] expected: &str) {
let debug = color.stylize_debug(kind);
#[rstest]
#[case(Color::Black, ".underline_color(Color::Black)")]
#[case(Color::Red, ".underline_color(Color::Red)")]
#[case(Color::Green, ".underline_color(Color::Green)")]
#[case(Color::Yellow, ".underline_color(Color::Yellow)")]
#[case(Color::Blue, ".underline_color(Color::Blue)")]
#[case(Color::Magenta, ".underline_color(Color::Magenta)")]
#[case(Color::Cyan, ".underline_color(Color::Cyan)")]
#[case(Color::Gray, ".underline_color(Color::Gray)")]
#[case(Color::DarkGray, ".underline_color(Color::DarkGray)")]
#[case(Color::LightRed, ".underline_color(Color::LightRed)")]
#[case(Color::LightGreen, ".underline_color(Color::LightGreen)")]
#[case(Color::LightYellow, ".underline_color(Color::LightYellow)")]
#[case(Color::LightBlue, ".underline_color(Color::LightBlue)")]
#[case(Color::LightMagenta, ".underline_color(Color::LightMagenta)")]
#[case(Color::LightCyan, ".underline_color(Color::LightCyan)")]
#[case(Color::White, ".underline_color(Color::White)")]
#[case(Color::Indexed(10), ".underline_color(Color::Indexed(10))")]
#[case(Color::Rgb(255, 0, 0), ".underline_color(Color::Rgb(255, 0, 0))")]
fn stylize_debug_underline(#[case] color: Color, #[case] expected: &str) {
let debug = color.stylize_debug(ColorDebugKind::Underline);
assert_eq!(format!("{debug:?}"), expected);
}
}

View file

@ -9,7 +9,7 @@ use crate::{
layout::{Alignment, Rect},
style::{Style, Styled},
text::{Span, StyledGrapheme, Text},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
/// A line of text, consisting of one or more [`Span`]s.
@ -683,21 +683,19 @@ impl<'a> Extend<Span<'a>> for Line<'a> {
impl Widget for Line<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Line<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Line<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_with_alignment(area, buf, None);
}
}
impl Line<'_> {
/// An internal implementation method for `WidgetRef::render_ref`
///
/// Allows the parent widget to define a default alignment, to be
/// used if `Line::alignment` is `None`.
/// An internal implementation method for `Widget::render` that allows the parent widget to
/// define a default alignment, to be used if `Line::alignment` is `None`.
pub(crate) fn render_with_alignment(
&self,
area: Rect,
@ -749,7 +747,7 @@ fn render_spans(spans: &[Span], mut area: Rect, buf: &mut Buffer, span_skip_widt
if area.is_empty() {
break;
}
span.render_ref(area, buf);
span.render(area, buf);
let span_width = u16::try_from(span_width).unwrap_or(u16::MAX);
area = area.indent_x(span_width);
}
@ -1321,7 +1319,7 @@ mod tests {
"🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得する"
);
let mut buf = Buffer::empty(Rect::new(0, 0, 83, 1));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([
"🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得 "
]));
@ -1358,7 +1356,7 @@ mod tests {
) {
let line = Line::from("1234🦀7890").alignment(alignment);
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
@ -1411,7 +1409,7 @@ mod tests {
};
let line = Line::from(value).centered();
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
@ -1429,7 +1427,7 @@ mod tests {
// Fill buffer with stuff to ensure the output is indeed padded
let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::new("X"));
let area = Rect::new(2, 0, 6, 1);
line.render_ref(area, &mut buf);
line.render(area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
@ -1447,7 +1445,7 @@ mod tests {
let area = Rect::new(0, 0, buf_width, 1);
// Fill buffer with stuff to ensure the output is indeed padded
let mut buf = Buffer::filled(area, Cell::new("X"));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
@ -1478,7 +1476,7 @@ mod tests {
fn render_truncates_flag(#[case] buf_width: u16, #[case] expected: &str) {
let line = Line::from("🇺🇸1234");
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
@ -1502,7 +1500,7 @@ mod tests {
assert!(line.width() >= min_width);
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
@ -1526,7 +1524,7 @@ mod tests {
assert!(line.width() >= min_width);
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
line.render_ref(buf.area, &mut buf);
line.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}

View file

@ -8,7 +8,7 @@ use crate::{
layout::Rect,
style::{Style, Styled},
text::{Line, StyledGrapheme},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
/// Represents a part of a line that is contiguous and where all characters share the same style.
@ -418,12 +418,12 @@ impl<'a> Styled for Span<'a> {
impl Widget for Span<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Span<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Span<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
if area.is_empty() {
return;

View file

@ -6,7 +6,7 @@ use crate::{
layout::{Alignment, Rect},
style::{Style, Styled},
text::{Line, Span},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
/// A string split over one or more lines.
@ -730,12 +730,12 @@ impl fmt::Display for Text<'_> {
impl Widget for Text<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf)
}
}
impl WidgetRef for Text<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Text<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
buf.set_style(area, self.style);
for (line, line_area) in self.iter().zip(area.rows()) {

View file

@ -3,10 +3,6 @@
//! render UI elements on the screen.
pub use self::{stateful_widget::StatefulWidget, widget::Widget};
#[instability::unstable(feature = "widget-ref")]
pub use self::{stateful_widget_ref::StatefulWidgetRef, widget_ref::WidgetRef};
mod stateful_widget;
mod stateful_widget_ref;
mod widget;
mod widget_ref;

View file

@ -1,5 +1,4 @@
use super::WidgetRef;
use crate::{buffer::Buffer, layout::Rect};
use crate::{buffer::Buffer, layout::Rect, style::Style};
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
///
@ -11,7 +10,7 @@ use crate::{buffer::Buffer, layout::Rect};
/// 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
/// 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.
@ -72,7 +71,7 @@ pub trait Widget {
/// drawing the text to the screen.
impl Widget for &str {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
}
}
@ -82,7 +81,15 @@ impl Widget for &str {
/// 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);
buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new());
}
}
impl<W: Widget> Widget for Option<W> {
fn render(self, area: Rect, buf: &mut Buffer) {
if let Some(widget) = self {
widget.render(area, buf);
}
}
}

View file

@ -15,9 +15,6 @@ edition.workspace = true
rust-version.workspace = true
[features]
## enables `unstable-widget-ref` as default
default = ["unstable-widget-ref"]
## enables serialization and deserialization of style and color types using the [`serde`] crate.
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "ratatui-core/serde"]
@ -32,11 +29,7 @@ all-widgets = ["calendar"]
calendar = ["dep:time"]
## Enable all unstable features.
unstable = ["unstable-rendered-line-info", "unstable-widget-ref"]
## enables the [`WidgetRef`](ratatui_core::widgets::WidgetRef) and
## [`StatefulWidgetRef`](ratatui_core::widgets::StatefulWidgetRef) traits which are experimental and may change
unstable-widget-ref = ["ratatui-core/unstable-widget-ref"]
unstable = ["unstable-rendered-line-info"]
## Enables the [`Paragraph::line_count`](paragraph::Paragraph::line_count)
## [`Paragraph::line_width`](paragraph::Paragraph::line_width) methods

View file

@ -6,7 +6,7 @@ use ratatui_core::{
style::{Style, Styled},
symbols::{self},
text::Line,
widgets::{Widget, WidgetRef},
widgets::Widget,
};
pub use self::{bar::Bar, bar_group::BarGroup};
@ -600,15 +600,15 @@ impl BarChart<'_> {
impl Widget for BarChart<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for BarChart<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &BarChart<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.block.render_ref(area, buf);
self.block.as_ref().render(area, buf);
let inner = self.block.inner_if_some(area);
if inner.is_empty() || self.data.is_empty() || self.bar_width == 0 {

View file

@ -12,7 +12,7 @@ use ratatui_core::{
style::{Style, Styled},
symbols::border,
text::Line,
widgets::{Widget, WidgetRef},
widgets::Widget,
};
pub use self::{
@ -603,12 +603,12 @@ impl<'a> Block<'a> {
impl Widget for Block<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Block<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Block<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
if area.is_empty() {
return;
@ -744,7 +744,7 @@ impl Block<'_> {
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.render_ref(title_area, buf);
title.render(title_area, buf);
// bump the width of the titles area to the left
titles_area.width = titles_area
@ -785,7 +785,7 @@ impl Block<'_> {
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.render_ref(title_area, buf);
title.render(title_area, buf);
// bump the titles area to the right and reduce its width
titles_area.x = titles_area.x.saturating_add(title_width + 1);
@ -808,7 +808,7 @@ impl Block<'_> {
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.render_ref(title_area, buf);
title.render(title_area, buf);
// bump the titles area to the right and reduce its width
titles_area.x = titles_area.x.saturating_add(title_width + 1);

View file

@ -15,7 +15,7 @@ use ratatui_core::{
layout::{Alignment, Constraint, Layout, Rect},
style::Style,
text::{Line, Span},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
use time::{Date, Duration, OffsetDateTime};
@ -136,13 +136,13 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
impl<DS: DateStyler> Widget for Monthly<'_, DS> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl<DS: DateStyler> WidgetRef for Monthly<'_, DS> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
self.block.render_ref(area, buf);
impl<DS: DateStyler> Widget for &Monthly<'_, DS> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.block.as_ref().render(area, buf);
let inner = self.block.inner_if_some(area);
self.render_monthly(inner, buf);
}

View file

@ -22,7 +22,7 @@ use ratatui_core::{
style::{Color, Style},
symbols::{self, Marker},
text::Line as TextLine,
widgets::{Widget, WidgetRef},
widgets::Widget,
};
pub use self::{
@ -747,16 +747,16 @@ where
F: Fn(&mut Context),
{
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl<F> WidgetRef for Canvas<'_, F>
impl<F> Widget for &Canvas<'_, F>
where
F: Fn(&mut Context),
{
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
self.block.render_ref(area, buf);
fn render(self, area: Rect, buf: &mut Buffer) {
self.block.as_ref().render(area, buf);
let canvas_area = self.block.inner_if_some(area);
if canvas_area.is_empty() {
return;

View file

@ -7,7 +7,7 @@ use ratatui_core::{
style::{Color, Style, Styled},
symbols::{self},
text::Line,
widgets::{Widget, WidgetRef},
widgets::Widget,
};
use strum::{Display, EnumString};
@ -971,16 +971,16 @@ impl<'a> Chart<'a> {
impl Widget for Chart<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Chart<'_> {
impl Widget for &Chart<'_> {
#[allow(clippy::too_many_lines)]
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.block.render_ref(area, buf);
self.block.as_ref().render(area, buf);
let chart_area = self.block.inner_if_some(area);
let Some(layout) = self.layout(chart_area) else {
return;

View file

@ -1,9 +1,5 @@
//! The [`Clear`] widget allows you to clear a certain area to allow overdrawing (e.g. for popups).
use ratatui_core::{
buffer::Buffer,
layout::Rect,
widgets::{Widget, WidgetRef},
};
use ratatui_core::{buffer::Buffer, layout::Rect, widgets::Widget};
/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
///
@ -35,12 +31,12 @@ pub struct Clear;
impl Widget for Clear {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Clear {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Clear {
fn render(self, area: Rect, buf: &mut Buffer) {
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
buf[(x, y)].reset();

View file

@ -5,7 +5,7 @@ use ratatui_core::{
style::{Color, Style, Styled},
symbols::{self},
text::{Line, Span},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
use crate::block::{Block, BlockExt};
@ -153,14 +153,14 @@ impl<'a> Gauge<'a> {
impl Widget for Gauge<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Gauge<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Gauge<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.block.render_ref(area, buf);
self.block.as_ref().render(area, buf);
let inner = self.block.inner_if_some(area);
self.render_gauge(inner, buf);
}
@ -386,14 +386,14 @@ impl<'a> LineGauge<'a> {
impl Widget for LineGauge<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for LineGauge<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &LineGauge<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.block.render_ref(area, buf);
self.block.as_ref().render(area, buf);
let gauge_area = self.block.inner_if_some(area);
if gauge_area.is_empty() {
return;

View file

@ -1,7 +1,7 @@
use ratatui_core::{
buffer::Buffer,
layout::Rect,
widgets::{StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
widgets::{StatefulWidget, Widget},
};
use unicode_width::UnicodeWidthStr;
@ -12,14 +12,14 @@ use crate::{
impl Widget for List<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
WidgetRef::render_ref(&self, area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for List<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &List<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let mut state = ListState::default();
StatefulWidgetRef::render_ref(self, area, buf, &mut state);
StatefulWidget::render(self, area, buf, &mut state);
}
}
@ -27,24 +27,16 @@ 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);
StatefulWidget::render(&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_ref(area, buf);
self.block.as_ref().render(area, buf);
let list_area = self.block.inner_if_some(area);
if list_area.is_empty() {
@ -113,7 +105,7 @@ impl StatefulWidgetRef for List<'_> {
} else {
row_area
};
item.content.render_ref(item_area, buf);
Widget::render(&item.content, item_area, buf);
for j in 0..item.content.height() {
// if the item is selected, we need to display the highlight symbol:

View file

@ -5,7 +5,7 @@ use ratatui_core::{
layout::{Alignment, Position, Rect},
style::{Style, Styled},
text::{Line, StyledGrapheme, Text},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
use unicode_width::UnicodeWidthStr;
@ -419,14 +419,14 @@ impl<'a> Paragraph<'a> {
impl Widget for Paragraph<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Paragraph<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Paragraph<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.block.render_ref(area, buf);
self.block.as_ref().render(area, buf);
let inner = self.block.inner_if_some(area);
self.render_paragraph(inner, buf);
}

View file

@ -6,7 +6,7 @@ use ratatui_core::{
layout::Rect,
style::{Style, Styled},
symbols::{self},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
use strum::{Display, EnumString};
@ -326,13 +326,13 @@ impl<'a> Styled for Sparkline<'a> {
impl Widget for Sparkline<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Sparkline<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
self.block.render_ref(area, buf);
impl Widget for &Sparkline<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.block.as_ref().render(area, buf);
let inner = self.block.inner_if_some(area);
self.render_sparkline(inner, buf);
}

View file

@ -7,7 +7,7 @@ use ratatui_core::{
layout::{Constraint, Flex, Layout, Rect},
style::{Style, Styled},
text::Text,
widgets::{StatefulWidget, StatefulWidgetRef, Widget, WidgetRef},
widgets::{StatefulWidget, Widget},
};
pub use self::{cell::Cell, highlight_spacing::HighlightSpacing, row::Row, state::TableState};
@ -752,12 +752,12 @@ impl<'a> Table<'a> {
impl Widget for Table<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
WidgetRef::render_ref(&self, area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Table<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Table<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let mut state = TableState::default();
StatefulWidget::render(self, area, buf, &mut state);
}
@ -771,20 +771,12 @@ 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_ref(area, buf);
self.block.as_ref().render(area, buf);
let table_area = self.block.inner_if_some(area);
if table_area.is_empty() {
return;
@ -910,7 +902,7 @@ impl Table<'_> {
..row_area
};
buf.set_style(selection_area, row.style);
highlight_symbol.render_ref(selection_area, buf);
highlight_symbol.render(selection_area, buf);
};
for ((x, width), cell) in columns_widths.iter().zip(row.cells.iter()) {
cell.render(

View file

@ -3,7 +3,7 @@ use ratatui_core::{
layout::Rect,
style::{Style, Styled},
text::Text,
widgets::WidgetRef,
widgets::Widget,
};
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
@ -163,7 +163,7 @@ impl<'a> Cell<'a> {
impl Cell<'_> {
pub(crate) fn render(&self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.content.render_ref(area, buf);
Widget::render(&self.content, area, buf);
}
}

View file

@ -6,7 +6,7 @@ use ratatui_core::{
style::{Modifier, Style, Styled},
symbols::{self},
text::{Line, Span},
widgets::{Widget, WidgetRef},
widgets::Widget,
};
use crate::block::{Block, BlockExt};
@ -364,14 +364,14 @@ impl<'a> Styled for Tabs<'a> {
impl Widget for Tabs<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
Widget::render(&self, area, buf);
}
}
impl WidgetRef for Tabs<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Tabs<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
self.block.render_ref(area, buf);
self.block.as_ref().render(area, buf);
let inner = self.block.inner_if_some(area);
self.render_tabs(inner, buf);
}

View file

@ -21,8 +21,8 @@ indoc = "2"
instability.workspace = true
itertools.workspace = true
palette = { version = "0.7.6", optional = true }
ratatui-core = { workspace = true, features = ["unstable-widget-ref"] }
ratatui-widgets = { workspace = true, features = ["unstable-widget-ref"] }
ratatui-core = { workspace = true }
ratatui-widgets = { workspace = true }
serde = { workspace = true, optional = true }
strum.workspace = true
termwiz = { version = "0.22.0", optional = true }
@ -163,7 +163,7 @@ unstable-rendered-line-info = ["ratatui-widgets/unstable-rendered-line-info"]
##
## [`WidgetRef`]: widgets::WidgetRef
## [`StatefulWidgetRef`]: widgets::StatefulWidgetRef
unstable-widget-ref = ["ratatui-core/unstable-widget-ref"]
unstable-widget-ref = []
## Enables getting access to backends' writers.
unstable-backend-writer = []

View file

@ -113,7 +113,7 @@ impl Frame<'_> {
///
/// let block = Block::new();
/// let area = Rect::new(0, 0, 5, 5);
/// frame.render_widget_ref(block, area);
/// frame.render_widget_ref(&block, area);
/// # }
/// ```
#[allow(clippy::needless_pass_by_value)]
@ -181,7 +181,7 @@ impl 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);
/// frame.render_stateful_widget_ref(&list, area, &mut state);
/// # }
/// ```
#[allow(clippy::needless_pass_by_value)]

View file

@ -29,8 +29,6 @@
//! [`Canvas`]: crate::widgets::canvas::Canvas
pub use ratatui_core::widgets::{StatefulWidget, Widget};
#[instability::unstable(feature = "widget-ref")]
pub use ratatui_core::widgets::{StatefulWidgetRef, WidgetRef};
// TODO remove this module once title etc. are gone
pub use ratatui_widgets::block;
#[cfg(feature = "widget-calendar")]
@ -51,3 +49,8 @@ pub use ratatui_widgets::{
table::{Cell, HighlightSpacing, Row, Table, TableState},
tabs::Tabs,
};
#[instability::unstable(feature = "widget-ref")]
pub use {stateful_widget_ref::StatefulWidgetRef, widget_ref::WidgetRef};
mod stateful_widget_ref;
mod widget_ref;

View file

@ -1,31 +1,34 @@
use ratatui_core::widgets::StatefulWidget;
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
/// This is the stateful equivalent of `WidgetRef`. It is useful when you need 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.
/// This trait was introduced in Ratatui 0.26.0. 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.
/// A blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget`
/// is provided. Most of the time you will want to implement `StatefulWidget` against a reference to
/// the widget instead of implementing `StatefulWidgetRef` directly.
///
/// See the documentation for [`WidgetRef`] for more information on boxed widgets.
/// See the documentation for [`StatefulWidget`] for more information on stateful widgets.
/// 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::widgets::StatefulWidgetRef;
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// style::Stylize,
/// text::Line,
/// widgets::{StatefulWidget, StatefulWidgetRef, Widget},
/// widgets::{StatefulWidget, Widget},
/// };
///
/// struct PersonalGreeting;
@ -64,20 +67,18 @@ pub trait StatefulWidgetRef {
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);
// }
// }
/// Blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget`.
///
/// This allows you to render a stateful widget by reference.
impl<W, State> StatefulWidgetRef for &W
where
for<'a> &'a W: StatefulWidget<State = State>,
{
type State = State;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.render(area, buf, state);
}
}
#[cfg(test)]
mod tests {
@ -98,35 +99,23 @@ mod tests {
struct PersonalGreeting;
impl StatefulWidgetRef for PersonalGreeting {
impl StatefulWidget for &PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
fn render(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;
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);
fn box_render_ref(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

@ -24,12 +24,8 @@ use crate::{buffer::Buffer, layout::Rect, style::Style};
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
/// use ratatui::widgets::WidgetRef;
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
///
/// struct Greeting;
///
@ -85,9 +81,12 @@ pub trait 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);
impl<W> WidgetRef for &W
where
for<'a> &'a W: Widget,
{
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
self.render(area, buf);
}
}
@ -127,12 +126,8 @@ impl WidgetRef for String {
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui_core::{
/// buffer::Buffer,
/// layout::Rect,
/// text::Line,
/// widgets::{Widget, WidgetRef},
/// };
/// use ratatui::widgets::WidgetRef;
/// use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
///
/// struct Parent {
/// child: Option<Child>,
@ -177,44 +172,37 @@ mod tests {
struct Farewell;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
impl Widget for &Greeting {
fn render(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) {
impl Widget for &Farewell {
fn render(self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
/// Ensure that the blanket implementation of `WidgetRef` for `&W` where `W` implements
/// `Widget` works as expected.
#[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);
widget.render_ref(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);
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)];
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(&Greeting), Box::new(&Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
}
@ -223,14 +211,14 @@ mod tests {
#[rstest]
fn render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
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;
let widget: Option<&Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}