From a41c97b413b28d0db6d1ea09dcc1d5b8556148b1 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Mon, 18 Nov 2024 14:19:21 -0800 Subject: [PATCH] chore: move unstable widget refs to ratatui (#1491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- BREAKING-CHANGES.md | 15 ++ Cargo.lock | 1 - bacon.toml | 123 +++++++++++---- ratatui-core/Cargo.toml | 11 -- ratatui-core/src/style/stylize.rs | 141 +++++++++--------- ratatui-core/src/text/line.rs | 32 ++-- ratatui-core/src/text/span.rs | 8 +- ratatui-core/src/text/text.rs | 8 +- ratatui-core/src/widgets.rs | 4 - ratatui-core/src/widgets/widget.rs | 17 ++- ratatui-widgets/Cargo.toml | 9 +- ratatui-widgets/src/barchart.rs | 10 +- ratatui-widgets/src/block.rs | 14 +- ratatui-widgets/src/calendar.rs | 10 +- ratatui-widgets/src/canvas.rs | 10 +- ratatui-widgets/src/chart.rs | 10 +- ratatui-widgets/src/clear.rs | 12 +- ratatui-widgets/src/gauge.rs | 18 +-- ratatui-widgets/src/list/rendering.rs | 26 ++-- ratatui-widgets/src/paragraph.rs | 10 +- ratatui-widgets/src/sparkline.rs | 10 +- ratatui-widgets/src/table.rs | 22 +-- ratatui-widgets/src/table/cell.rs | 4 +- ratatui-widgets/src/tabs.rs | 10 +- ratatui/Cargo.toml | 6 +- ratatui/src/terminal/frame.rs | 4 +- ratatui/src/widgets.rs | 7 +- .../src/widgets/stateful_widget_ref.rs | 71 ++++----- .../src/widgets/widget_ref.rs | 54 +++---- 29 files changed, 348 insertions(+), 329 deletions(-) rename {ratatui-core => ratatui}/src/widgets/stateful_widget_ref.rs (53%) rename {ratatui-core => ratatui}/src/widgets/widget_ref.rs (86%) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1d1c609d..075a5b06 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 9cecc1dd..37903824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2129,7 +2129,6 @@ dependencies = [ "compact_str", "document-features", "indoc", - "instability", "itertools 0.13.0", "lru", "palette", diff --git a/bacon.toml b/bacon.toml index 5bfaaea9..72fdc9fe 100644 --- a/bacon.toml +++ b/bacon.toml @@ -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" diff --git a/ratatui-core/Cargo.toml b/ratatui-core/Cargo.toml index 1f21ce75..f86a2396 100644 --- a/ratatui-core/Cargo.toml +++ b/ratatui-core/Cargo.toml @@ -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" diff --git a/ratatui-core/src/style/stylize.rs b/ratatui-core/src/style/stylize.rs index 59f464a4..d24460fc 100644 --- a/ratatui-core/src/style/stylize.rs +++ b/ratatui-core/src/style/stylize.rs @@ -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); } } diff --git a/ratatui-core/src/text/line.rs b/ratatui-core/src/text/line.rs index 93cc2399..2e64e961 100755 --- a/ratatui-core/src/text/line.rs +++ b/ratatui-core/src/text/line.rs @@ -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> 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])); } diff --git a/ratatui-core/src/text/span.rs b/ratatui-core/src/text/span.rs index a0b3f1eb..5aca29b8 100644 --- a/ratatui-core/src/text/span.rs +++ b/ratatui-core/src/text/span.rs @@ -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; diff --git a/ratatui-core/src/text/text.rs b/ratatui-core/src/text/text.rs index 2ff562b7..50d959b7 100755 --- a/ratatui-core/src/text/text.rs +++ b/ratatui-core/src/text/text.rs @@ -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()) { diff --git a/ratatui-core/src/widgets.rs b/ratatui-core/src/widgets.rs index 87c9835f..1a5f0652 100644 --- a/ratatui-core/src/widgets.rs +++ b/ratatui-core/src/widgets.rs @@ -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; diff --git a/ratatui-core/src/widgets/widget.rs b/ratatui-core/src/widgets/widget.rs index 8ec1222e..19a101bb 100644 --- a/ratatui-core/src/widgets/widget.rs +++ b/ratatui-core/src/widgets/widget.rs @@ -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 Widget for Option { + fn render(self, area: Rect, buf: &mut Buffer) { + if let Some(widget) = self { + widget.render(area, buf); + } } } diff --git a/ratatui-widgets/Cargo.toml b/ratatui-widgets/Cargo.toml index 94fdef69..31da3090 100644 --- a/ratatui-widgets/Cargo.toml +++ b/ratatui-widgets/Cargo.toml @@ -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 diff --git a/ratatui-widgets/src/barchart.rs b/ratatui-widgets/src/barchart.rs index 65b845ec..faa26365 100644 --- a/ratatui-widgets/src/barchart.rs +++ b/ratatui-widgets/src/barchart.rs @@ -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 { diff --git a/ratatui-widgets/src/block.rs b/ratatui-widgets/src/block.rs index 1dedca0d..421d3c53 100644 --- a/ratatui-widgets/src/block.rs +++ b/ratatui-widgets/src/block.rs @@ -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); diff --git a/ratatui-widgets/src/calendar.rs b/ratatui-widgets/src/calendar.rs index 0dc5e8cd..325f02b4 100644 --- a/ratatui-widgets/src/calendar.rs +++ b/ratatui-widgets/src/calendar.rs @@ -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 Widget for Monthly<'_, DS> { fn render(self, area: Rect, buf: &mut Buffer) { - self.render_ref(area, buf); + Widget::render(&self, area, buf); } } -impl WidgetRef for Monthly<'_, DS> { - fn render_ref(&self, area: Rect, buf: &mut Buffer) { - self.block.render_ref(area, buf); +impl 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); } diff --git a/ratatui-widgets/src/canvas.rs b/ratatui-widgets/src/canvas.rs index 4b54cb4d..45f97fc2 100644 --- a/ratatui-widgets/src/canvas.rs +++ b/ratatui-widgets/src/canvas.rs @@ -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 WidgetRef for Canvas<'_, F> +impl 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; diff --git a/ratatui-widgets/src/chart.rs b/ratatui-widgets/src/chart.rs index c461151a..2997a57d 100644 --- a/ratatui-widgets/src/chart.rs +++ b/ratatui-widgets/src/chart.rs @@ -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; diff --git a/ratatui-widgets/src/clear.rs b/ratatui-widgets/src/clear.rs index ce325f1f..d5559336 100644 --- a/ratatui-widgets/src/clear.rs +++ b/ratatui-widgets/src/clear.rs @@ -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(); diff --git a/ratatui-widgets/src/gauge.rs b/ratatui-widgets/src/gauge.rs index 94c5410d..4de0deac 100644 --- a/ratatui-widgets/src/gauge.rs +++ b/ratatui-widgets/src/gauge.rs @@ -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; diff --git a/ratatui-widgets/src/list/rendering.rs b/ratatui-widgets/src/list/rendering.rs index 6f224d48..e6456af7 100644 --- a/ratatui-widgets/src/list/rendering.rs +++ b/ratatui-widgets/src/list/rendering.rs @@ -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: diff --git a/ratatui-widgets/src/paragraph.rs b/ratatui-widgets/src/paragraph.rs index 6a31e066..e835f737 100644 --- a/ratatui-widgets/src/paragraph.rs +++ b/ratatui-widgets/src/paragraph.rs @@ -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); } diff --git a/ratatui-widgets/src/sparkline.rs b/ratatui-widgets/src/sparkline.rs index dde86087..dc3c7330 100644 --- a/ratatui-widgets/src/sparkline.rs +++ b/ratatui-widgets/src/sparkline.rs @@ -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); } diff --git a/ratatui-widgets/src/table.rs b/ratatui-widgets/src/table.rs index 26a9a294..a8f79a92 100644 --- a/ratatui-widgets/src/table.rs +++ b/ratatui-widgets/src/table.rs @@ -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( diff --git a/ratatui-widgets/src/table/cell.rs b/ratatui-widgets/src/table/cell.rs index c9d98bd2..3270a264 100644 --- a/ratatui-widgets/src/table/cell.rs +++ b/ratatui-widgets/src/table/cell.rs @@ -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); } } diff --git a/ratatui-widgets/src/tabs.rs b/ratatui-widgets/src/tabs.rs index 989f387f..ff4a8223 100644 --- a/ratatui-widgets/src/tabs.rs +++ b/ratatui-widgets/src/tabs.rs @@ -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); } diff --git a/ratatui/Cargo.toml b/ratatui/Cargo.toml index be6b05a9..ee3b1d79 100644 --- a/ratatui/Cargo.toml +++ b/ratatui/Cargo.toml @@ -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 = [] diff --git a/ratatui/src/terminal/frame.rs b/ratatui/src/terminal/frame.rs index acece5a9..41e5f11f 100644 --- a/ratatui/src/terminal/frame.rs +++ b/ratatui/src/terminal/frame.rs @@ -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)] diff --git a/ratatui/src/widgets.rs b/ratatui/src/widgets.rs index 5cf076f9..ec5f0102 100644 --- a/ratatui/src/widgets.rs +++ b/ratatui/src/widgets.rs @@ -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; diff --git a/ratatui-core/src/widgets/stateful_widget_ref.rs b/ratatui/src/widgets/stateful_widget_ref.rs similarity index 53% rename from ratatui-core/src/widgets/stateful_widget_ref.rs rename to ratatui/src/widgets/stateful_widget_ref.rs index ab5e9560..35f4337a 100644 --- a/ratatui-core/src/widgets/stateful_widget_ref.rs +++ b/ratatui/src/widgets/stateful_widget_ref.rs @@ -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 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 +/// 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 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 StatefulWidgetRef for &W +where + for<'a> &'a W: StatefulWidget, +{ + 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 "])); } diff --git a/ratatui-core/src/widgets/widget_ref.rs b/ratatui/src/widgets/widget_ref.rs similarity index 86% rename from ratatui-core/src/widgets/widget_ref.rs rename to ratatui/src/widgets/widget_ref.rs index 0c249f2b..78cfde3a 100644 --- a/ratatui-core/src/widgets/widget_ref.rs +++ b/ratatui/src/widgets/widget_ref.rs @@ -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 Widget for &W { - fn render(self, area: Rect, buf: &mut Buffer) { - self.render_ref(area, buf); +impl 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, @@ -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 = Box::new(Greeting); + let widget: Box = 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> = vec![Box::new(Greeting), Box::new(Farewell)]; + let widgets: Vec> = 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 = None; + let widget: Option<&Greeting> = None; widget.render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines([" "])); }