Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(widgets): implement Widget for Widget refs #833

Merged
merged 6 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/colors_rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crossterm::{
ExecutableCommand,
};
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
use ratatui::{prelude::*, widgets::*};
use ratatui::prelude::*;

#[derive(Debug, Default)]
struct App {
Expand Down
2 changes: 1 addition & 1 deletion examples/demo2/colors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use palette::{IntoColor, Okhsv, Srgb};
use ratatui::{prelude::*, widgets::*};
use ratatui::prelude::*;

/// A widget that renders a color swatch of RGB colors.
///
Expand Down
2 changes: 1 addition & 1 deletion src/layout/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,7 @@ mod tests {
assert_buffer_eq,
layout::flex::Flex,
prelude::{Constraint::*, *},
widgets::{Paragraph, Widget},
widgets::Paragraph,
};

/// Test that the given constraints applied to the given area result in the expected layout.
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ pub use crate::{
symbols::{self, Marker},
terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport},
text::{self, Line, Masked, Span, Text},
widgets::{block::BlockExt, StatefulWidget, Widget},
joshka marked this conversation as resolved.
Show resolved Hide resolved
};
10 changes: 2 additions & 8 deletions src/terminal/frame.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
prelude::*,
widgets::{StatefulWidget, Widget},
};
use crate::prelude::*;

/// A consistent view into the terminal state for rendering a single frame.
///
Expand Down Expand Up @@ -74,10 +71,7 @@ impl Frame<'_> {
/// ```
///
/// [`Layout`]: crate::layout::Layout
pub fn render_widget<W>(&mut self, widget: W, area: Rect)
where
W: Widget,
{
pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
widget.render(area, self.buffer);
}

Expand Down
18 changes: 17 additions & 1 deletion src/text/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,22 @@ impl<'a> From<Line<'a>> for String {
}

impl Widget for Line<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Widget::render(&self, area, buf);
}
}

/// Implement [`Widget`] for [`Option<Line>`] to simplify the common case of having an optional
/// [`Line`] field in a widget.
impl Widget for &Option<Line<'_>> {
fn render(self, area: Rect, buf: &mut Buffer) {
if let Some(line) = self {
line.render(area, buf);
}
}
}

impl Widget for &Line<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
buf.set_style(area, self.style);
Expand All @@ -418,7 +434,7 @@ impl Widget for Line<'_> {
None => 0,
};
let mut x = area.left().saturating_add(offset);
for span in self.spans {
for span in self.spans.iter() {
let span_width = span.width() as u16;
let span_area = Rect {
x,
Expand Down
16 changes: 16 additions & 0 deletions src/text/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,22 @@ impl<'a> Styled for Span<'a> {
}

impl Widget for Span<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Widget::render(&self, area, buf);
}
}

/// Implement [`Widget`] for [`Option<Span>`] to simplify the common case of having an optional
/// [`Span`] field in a widget.
impl Widget for &Option<Span<'_>> {
fn render(self, area: Rect, buf: &mut Buffer) {
if let Some(span) = self {
span.render(area, buf);
}
}
}

impl Widget for &Span<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let Rect {
x: mut current_x,
Expand Down
20 changes: 18 additions & 2 deletions src/text/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,26 @@ impl std::fmt::Display for Text<'_> {
}
}

impl<'a> Widget for Text<'a> {
impl Widget for Text<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Widget::render(&self, area, buf);
}
}

/// Implement [`Widget`] for [`Option<Text>`] to simplify the common case of having an optional
/// [`Text`] field in a widget.
impl Widget for &Option<Text<'_>> {
fn render(self, area: Rect, buf: &mut Buffer) {
if let Some(text) = self {
text.render(area, buf);
}
}
}

impl Widget for &Text<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
for (line, row) in self.lines.into_iter().zip(area.rows()) {
for (line, row) in self.lines.iter().zip(area.rows()) {
let line_width = line.width() as u16;

let x_offset = match (self.alignment, line.alignment) {
Expand Down
54 changes: 53 additions & 1 deletion src/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,59 @@ pub use self::{
};
use crate::{buffer::Buffer, layout::Rect};

/// Base requirements for a Widget
/// A `Widget` is a type that can be drawn on a [`Buffer`] in a given [`Rect`].
///
/// Prior to Ratatui 0.26.0, widgets generally were created for each frame as they were consumed
/// during rendering. This meant that they were not meant to be stored but used as *commands* to
/// draw common figures in the UI.
///
/// Starting with Ratatui 0.26.0, the `Widget` trait was more universally implemented on &T instead
/// of just T. This means that widgets can be stored and reused across frames without having to
/// clone or recreate them.
///
/// # Examples
///
/// ```rust,no_run
/// use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
///
/// terminal.draw(|frame| {
/// frame.render_widget(Clear, frame.size());
/// });
/// ```
///
/// Rendering a widget by reference:
///
/// ```rust
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
/// // this variable could instead be a value stored in a struct and reused across frames
/// let paragraph = Paragraph::new("Hello world!");
///
/// terminal.draw(|frame| {
/// frame.render_widget(&paragraph, frame.size());
/// });
/// ```
///
/// It's common to render widgets inside other widgets:
///
/// ```rust
/// use ratatui::{prelude::*, widgets::*};
///
/// struct MyWidget;
///
/// impl Widget for &MyWidget {
/// fn render(self, area: Rect, buf: &mut Buffer) {
/// Block::default()
/// .title("My Widget")
/// .borders(Borders::ALL)
/// .render(area, buf);
/// // ...
/// }
/// }
/// ```
pub trait Widget {
joshka marked this conversation as resolved.
Show resolved Hide resolved
/// Draws the current state of the widget in the given buffer. That is the only method required
/// to implement a custom widget.
Expand Down
29 changes: 11 additions & 18 deletions src/widgets/barchart.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
#![warn(missing_docs)]
use crate::prelude::*;
use crate::{prelude::*, widgets::Block};

mod bar;
mod bar_group;

pub use bar::Bar;
pub use bar_group::BarGroup;

use super::{Block, Widget};

/// A chart showing values as [bars](Bar).
///
/// Here is a possible `BarChart` output.
Expand Down Expand Up @@ -36,6 +34,9 @@ use super::{Block, Widget};
/// The chart can have a [`Direction`] (by default the bars are [`Vertical`](Direction::Vertical)).
/// This is set using [`BarChart::direction`].
///
/// Note: this is the only widget that doesn't implement `Widget` for `&T` because the current
/// implementation modifies the internal state of self. This will be fixed in the future.
///
Valentin271 marked this conversation as resolved.
Show resolved Hide resolved
/// # Examples
///
/// The following example creates a `BarChart` with two groups of bars.
Expand Down Expand Up @@ -391,15 +392,6 @@ impl<'a> BarChart<'a> {
}
}

/// renders the block if there is one and updates the area to the inner area
fn render_block(&mut self, area: &mut Rect, buf: &mut Buffer) {
if let Some(block) = self.block.take() {
let inner_area = block.inner(*area);
block.render(*area, buf);
*area = inner_area
}
}

fn render_horizontal(self, buf: &mut Buffer, area: Rect) {
// get the longest label
let label_size = self
Expand Down Expand Up @@ -586,19 +578,20 @@ impl<'a> BarChart<'a> {
}
}

impl<'a> Widget for BarChart<'a> {
fn render(mut self, mut area: Rect, buf: &mut Buffer) {
impl Widget for BarChart<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);

self.render_block(&mut area, buf);
self.block.render(area, buf);
let inner = self.block.inner_if_some(area);

if area.is_empty() || self.data.is_empty() || self.bar_width == 0 {
if inner.is_empty() || self.data.is_empty() || self.bar_width == 0 {
return;
}

match self.direction {
Direction::Horizontal => self.render_horizontal(buf, area),
Direction::Vertical => self.render_vertical(buf, area),
Direction::Horizontal => self.render_horizontal(buf, inner),
Direction::Vertical => self.render_vertical(buf, inner),
}
}
}
Expand Down
11 changes: 4 additions & 7 deletions src/widgets/barchart/bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,21 @@ impl<'a> Bar<'a> {
/// bar width, then the value is split into 2 parts. the first part is rendered in the bar
/// using value_style. The second part is rendered outside the bar using bar_style
pub(super) fn render_value_with_different_styles(
self,
&self,
buf: &mut Buffer,
area: Rect,
bar_length: usize,
default_value_style: Style,
bar_style: Style,
) {
let text = if let Some(text) = self.text_value {
text
} else {
self.value.to_string()
};
let value = self.value.to_string();
let text = self.text_value.as_ref().unwrap_or(&value);

if !text.is_empty() {
let style = default_value_style.patch(self.value_style);
// Since the value may be longer than the bar itself, we need to use 2 different styles
// while rendering. Render the first part with the default value style
buf.set_stringn(area.x, area.y, &text, bar_length, style);
buf.set_stringn(area.x, area.y, text, bar_length, style);
// render the second part with the bar_style
if text.len() > bar_length {
let (first, second) = text.split_at(bar_length);
Expand Down
6 changes: 1 addition & 5 deletions src/widgets/barchart/bar_group.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use super::Bar;
use crate::{
prelude::{Alignment, Buffer, Rect},
style::Style,
text::Line,
};
use crate::prelude::*;

/// A group of bars to be shown by the Barchart.
///
Expand Down
55 changes: 43 additions & 12 deletions src/widgets/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@

use strum::{Display, EnumString};

use crate::{
prelude::*,
symbols::border,
widgets::{Borders, Widget},
};
use crate::{prelude::*, symbols::border, widgets::Borders};

mod padding;
pub mod title;
Expand Down Expand Up @@ -520,7 +516,35 @@ impl<'a> Block<'a> {
self.padding = padding;
self
}
}

impl Widget for Block<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Widget::render(&self, area, buf);
}
}

/// Implement [`Widget`] for [`Option<Block>`] to simplify the common case of having an optional
/// [`Block`] field in a widget.
impl Widget for &Option<Block<'_>> {
fn render(self, area: Rect, buf: &mut Buffer) {
if let Some(block) = self {
block.render(area, buf);
}
}
}

impl Widget for &Block<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.is_empty() {
return;
}
self.render_borders(area, buf);
self.render_titles(area, buf);
}
}

impl Block<'_> {
fn render_borders(&self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let symbols = self.border_set;
Expand Down Expand Up @@ -703,13 +727,20 @@ impl<'a> Block<'a> {
}
}

impl<'a> Widget for Block<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.area() == 0 {
return;
}
self.render_borders(area, buf);
self.render_titles(area, buf);
/// An extension trait for [`Block`] that provides some convenience methods.
///
/// This is implemented for [`Option<Block>`](Option) to simplify the common case of having a
/// widget with an optional block.
pub trait BlockExt {
/// Return the inner area of the block if it is `Some`. Otherwise, returns `area`.
///
/// This is a useful convenience method for widgets that have an `Option<Block>` field
fn inner_if_some(&self, area: Rect) -> Rect;
}

impl BlockExt for Option<Block<'_>> {
fn inner_if_some(&self, area: Rect) -> Rect {
self.as_ref().map_or(area, |block| block.inner(area))
}
}

Expand Down