mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 20:53:19 +00:00
feat(widgets): implement Widget for Widget refs (#833)
Many widgets can be rendered without changing their state. This commit implements The `Widget` trait for references to widgets and changes their implementations to be immutable. This allows us to render widgets without consuming them by passing a ref to the widget when calling `Frame::render_widget()`. ```rust // this might be stored in a struct let paragraph = Paragraph::new("Hello world!"); let [left, right] = area.split(&Layout::horizontal([20, 20])); frame.render_widget(¶graph, left); frame.render_widget(¶graph, right); // we can reuse the widget ``` Implemented for all widgets except BarChart (which has an implementation that modifies the internal state and requires a rewrite to fix. Other widgets will be implemented in follow up commits. Fixes: https://github.com/ratatui-org/ratatui/discussions/164 Replaces PRs: https://github.com/ratatui-org/ratatui/pull/122 and https://github.com/ratatui-org/ratatui/pull/16 Enables: https://github.com/ratatui-org/ratatui/issues/132 Validated as a viable working solution by: https://github.com/ratatui-org/ratatui/pull/836
This commit is contained in:
parent
736605ec88
commit
815757fcbb
29 changed files with 429 additions and 305 deletions
|
@ -23,7 +23,7 @@ use crossterm::{
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
};
|
};
|
||||||
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
|
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct App {
|
struct App {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use palette::{IntoColor, Okhsv, Srgb};
|
use palette::{IntoColor, Okhsv, Srgb};
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::prelude::*;
|
||||||
|
|
||||||
/// A widget that renders a color swatch of RGB colors.
|
/// A widget that renders a color swatch of RGB colors.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1172,7 +1172,7 @@ mod tests {
|
||||||
assert_buffer_eq,
|
assert_buffer_eq,
|
||||||
layout::flex::Flex,
|
layout::flex::Flex,
|
||||||
prelude::{Constraint::*, *},
|
prelude::{Constraint::*, *},
|
||||||
widgets::{Paragraph, Widget},
|
widgets::Paragraph,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Test that the given constraints applied to the given area result in the expected layout.
|
/// Test that the given constraints applied to the given area result in the expected layout.
|
||||||
|
|
|
@ -31,4 +31,5 @@ pub use crate::{
|
||||||
symbols::{self, Marker},
|
symbols::{self, Marker},
|
||||||
terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport},
|
terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport},
|
||||||
text::{self, Line, Masked, Span, Text},
|
text::{self, Line, Masked, Span, Text},
|
||||||
|
widgets::{block::BlockExt, StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::{
|
use crate::prelude::*;
|
||||||
prelude::*,
|
|
||||||
widgets::{StatefulWidget, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A consistent view into the terminal state for rendering a single frame.
|
/// A consistent view into the terminal state for rendering a single frame.
|
||||||
///
|
///
|
||||||
|
@ -74,10 +71,7 @@ impl Frame<'_> {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`Layout`]: crate::layout::Layout
|
/// [`Layout`]: crate::layout::Layout
|
||||||
pub fn render_widget<W>(&mut self, widget: W, area: Rect)
|
pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
|
||||||
where
|
|
||||||
W: Widget,
|
|
||||||
{
|
|
||||||
widget.render(area, self.buffer);
|
widget.render(area, self.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -407,6 +407,22 @@ impl<'a> From<Line<'a>> for String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Line<'_> {
|
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) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let area = area.intersection(buf.area);
|
let area = area.intersection(buf.area);
|
||||||
buf.set_style(area, self.style);
|
buf.set_style(area, self.style);
|
||||||
|
@ -418,7 +434,7 @@ impl Widget for Line<'_> {
|
||||||
None => 0,
|
None => 0,
|
||||||
};
|
};
|
||||||
let mut x = area.left().saturating_add(offset);
|
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_width = span.width() as u16;
|
||||||
let span_area = Rect {
|
let span_area = Rect {
|
||||||
x,
|
x,
|
||||||
|
|
|
@ -299,6 +299,22 @@ impl<'a> Styled for Span<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Span<'_> {
|
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) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let Rect {
|
let Rect {
|
||||||
x: mut current_x,
|
x: mut current_x,
|
||||||
|
|
|
@ -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) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
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 line_width = line.width() as u16;
|
||||||
|
|
||||||
let x_offset = match (self.alignment, line.alignment) {
|
let x_offset = match (self.alignment, line.alignment) {
|
||||||
|
|
|
@ -53,7 +53,59 @@ pub use self::{
|
||||||
};
|
};
|
||||||
use crate::{buffer::Buffer, layout::Rect};
|
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(¶graph, 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 {
|
pub trait Widget {
|
||||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||||
/// to implement a custom widget.
|
/// to implement a custom widget.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, widgets::Block};
|
||||||
|
|
||||||
mod bar;
|
mod bar;
|
||||||
mod bar_group;
|
mod bar_group;
|
||||||
|
@ -7,8 +7,6 @@ mod bar_group;
|
||||||
pub use bar::Bar;
|
pub use bar::Bar;
|
||||||
pub use bar_group::BarGroup;
|
pub use bar_group::BarGroup;
|
||||||
|
|
||||||
use super::{Block, Widget};
|
|
||||||
|
|
||||||
/// A chart showing values as [bars](Bar).
|
/// A chart showing values as [bars](Bar).
|
||||||
///
|
///
|
||||||
/// Here is a possible `BarChart` output.
|
/// Here is a possible `BarChart` output.
|
||||||
|
@ -36,6 +34,9 @@ use super::{Block, Widget};
|
||||||
/// The chart can have a [`Direction`] (by default the bars are [`Vertical`](Direction::Vertical)).
|
/// The chart can have a [`Direction`] (by default the bars are [`Vertical`](Direction::Vertical)).
|
||||||
/// This is set using [`BarChart::direction`].
|
/// 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.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// The following example creates a `BarChart` with two groups of bars.
|
/// The following example creates a `BarChart` with two groups of bars.
|
||||||
|
@ -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) {
|
fn render_horizontal(self, buf: &mut Buffer, area: Rect) {
|
||||||
// get the longest label
|
// get the longest label
|
||||||
let label_size = self
|
let label_size = self
|
||||||
|
@ -586,19 +578,20 @@ impl<'a> BarChart<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for BarChart<'a> {
|
impl Widget for BarChart<'_> {
|
||||||
fn render(mut self, mut area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.direction {
|
match self.direction {
|
||||||
Direction::Horizontal => self.render_horizontal(buf, area),
|
Direction::Horizontal => self.render_horizontal(buf, inner),
|
||||||
Direction::Vertical => self.render_vertical(buf, area),
|
Direction::Vertical => self.render_vertical(buf, inner),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
/// 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
|
/// using value_style. The second part is rendered outside the bar using bar_style
|
||||||
pub(super) fn render_value_with_different_styles(
|
pub(super) fn render_value_with_different_styles(
|
||||||
self,
|
&self,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
bar_length: usize,
|
bar_length: usize,
|
||||||
default_value_style: Style,
|
default_value_style: Style,
|
||||||
bar_style: Style,
|
bar_style: Style,
|
||||||
) {
|
) {
|
||||||
let text = if let Some(text) = self.text_value {
|
let value = self.value.to_string();
|
||||||
text
|
let text = self.text_value.as_ref().unwrap_or(&value);
|
||||||
} else {
|
|
||||||
self.value.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
let style = default_value_style.patch(self.value_style);
|
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
|
// 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
|
// 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
|
// render the second part with the bar_style
|
||||||
if text.len() > bar_length {
|
if text.len() > bar_length {
|
||||||
let (first, second) = text.split_at(bar_length);
|
let (first, second) = text.split_at(bar_length);
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
use super::Bar;
|
use super::Bar;
|
||||||
use crate::{
|
use crate::prelude::*;
|
||||||
prelude::{Alignment, Buffer, Rect},
|
|
||||||
style::Style,
|
|
||||||
text::Line,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A group of bars to be shown by the Barchart.
|
/// A group of bars to be shown by the Barchart.
|
||||||
///
|
///
|
||||||
|
|
|
@ -8,11 +8,7 @@
|
||||||
|
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
use crate::{
|
use crate::{prelude::*, symbols::border, widgets::Borders};
|
||||||
prelude::*,
|
|
||||||
symbols::border,
|
|
||||||
widgets::{Borders, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod padding;
|
mod padding;
|
||||||
pub mod title;
|
pub mod title;
|
||||||
|
@ -520,7 +516,35 @@ impl<'a> Block<'a> {
|
||||||
self.padding = padding;
|
self.padding = padding;
|
||||||
self
|
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) {
|
fn render_borders(&self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
buf.set_style(area, self.style);
|
||||||
let symbols = self.border_set;
|
let symbols = self.border_set;
|
||||||
|
@ -703,13 +727,20 @@ impl<'a> Block<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Block<'a> {
|
/// An extension trait for [`Block`] that provides some convenience methods.
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
///
|
||||||
if area.area() == 0 {
|
/// This is implemented for [`Option<Block>`](Option) to simplify the common case of having a
|
||||||
return;
|
/// 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;
|
||||||
}
|
}
|
||||||
self.render_borders(area, buf);
|
|
||||||
self.render_titles(area, buf);
|
impl BlockExt for Option<Block<'_>> {
|
||||||
|
fn inner_if_some(&self, area: Rect) -> Rect {
|
||||||
|
self.as_ref().map_or(area, |block| block.inner(area))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use time::{Date, Duration, OffsetDateTime};
|
use time::{Date, Duration, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{
|
use crate::{prelude::*, widgets::Block};
|
||||||
prelude::*,
|
|
||||||
widgets::{Block, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Display a month calendar for the month containing `display_date`
|
/// Display a month calendar for the month containing `display_date`
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
@ -117,36 +114,42 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, DS: DateStyler> Widget for Monthly<'a, DS> {
|
impl<DS: DateStyler> Widget for Monthly<'_, DS> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
// Block is used for borders and such
|
Widget::render(&self, area, buf);
|
||||||
// Draw that first, and use the blank area inside the block for our own purposes
|
|
||||||
let mut area = match self.block.take() {
|
|
||||||
None => area,
|
|
||||||
Some(b) => {
|
|
||||||
let inner = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
impl<DS: DateStyler> Widget for &Monthly<'_, DS> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let inner = self.block.inner_if_some(area);
|
||||||
|
self.render_monthly(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DS: DateStyler> Monthly<'_, DS> {
|
||||||
|
fn render_monthly(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let layout = Layout::vertical([
|
||||||
|
Constraint::Length(self.show_month.is_some().into()),
|
||||||
|
Constraint::Length(self.show_weekday.is_some().into()),
|
||||||
|
Constraint::Proportional(1),
|
||||||
|
]);
|
||||||
|
let [month_header, days_header, days_area] = area.split(&layout);
|
||||||
|
|
||||||
// Draw the month name and year
|
// Draw the month name and year
|
||||||
if let Some(style) = self.show_month {
|
if let Some(style) = self.show_month {
|
||||||
let line = Span::styled(
|
Line::styled(
|
||||||
format!("{} {}", self.display_date.month(), self.display_date.year()),
|
format!("{} {}", self.display_date.month(), self.display_date.year()),
|
||||||
style,
|
style,
|
||||||
);
|
)
|
||||||
// cal is 21 cells wide, so hard code the 11
|
.alignment(Alignment::Center)
|
||||||
let x_off = 11_u16.saturating_sub(line.width() as u16 / 2);
|
.render(month_header, buf);
|
||||||
buf.set_line(area.x + x_off, area.y, &line.into(), area.width);
|
|
||||||
area.y += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw days of week
|
// Draw days of week
|
||||||
if let Some(style) = self.show_weekday {
|
if let Some(style) = self.show_weekday {
|
||||||
let days = String::from(" Su Mo Tu We Th Fr Sa");
|
Span::styled(" Su Mo Tu We Th Fr Sa", style).render(days_header, buf);
|
||||||
buf.set_string(area.x, area.y, days, style);
|
|
||||||
area.y += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the start of the calendar to the Sunday before the 1st (or the sunday of the first)
|
// Set the start of the calendar to the Sunday before the 1st (or the sunday of the first)
|
||||||
|
@ -154,6 +157,7 @@ impl<'a, DS: DateStyler> Widget for Monthly<'a, DS> {
|
||||||
let offset = Duration::days(first_of_month.weekday().number_days_from_sunday().into());
|
let offset = Duration::days(first_of_month.weekday().number_days_from_sunday().into());
|
||||||
let mut curr_day = first_of_month - offset;
|
let mut curr_day = first_of_month - offset;
|
||||||
|
|
||||||
|
let mut y = days_area.y;
|
||||||
// go through all the weeks containing a day in the target month.
|
// go through all the weeks containing a day in the target month.
|
||||||
while curr_day.month() as u8 != self.display_date.month().next() as u8 {
|
while curr_day.month() as u8 != self.display_date.month().next() as u8 {
|
||||||
let mut spans = Vec::with_capacity(14);
|
let mut spans = Vec::with_capacity(14);
|
||||||
|
@ -168,8 +172,8 @@ impl<'a, DS: DateStyler> Widget for Monthly<'a, DS> {
|
||||||
spans.push(self.format_date(curr_day));
|
spans.push(self.format_date(curr_day));
|
||||||
curr_day += Duration::DAY;
|
curr_day += Duration::DAY;
|
||||||
}
|
}
|
||||||
buf.set_line(area.x, area.y, &spans.into(), area.width);
|
buf.set_line(days_area.x, y, &spans.into(), area.width);
|
||||||
area.y += 1;
|
y += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,7 @@ pub use self::{
|
||||||
points::Points,
|
points::Points,
|
||||||
rectangle::Rectangle,
|
rectangle::Rectangle,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{prelude::*, symbols, text::Line as TextLine, widgets::Block};
|
||||||
buffer::Buffer,
|
|
||||||
layout::Rect,
|
|
||||||
style::{Color, Style},
|
|
||||||
symbols,
|
|
||||||
text::Line as TextLine,
|
|
||||||
widgets::{Block, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Interface for all shapes that may be drawn on a Canvas widget.
|
/// Interface for all shapes that may be drawn on a Canvas widget.
|
||||||
pub trait Shape {
|
pub trait Shape {
|
||||||
|
@ -694,19 +687,25 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, F> Widget for Canvas<'a, F>
|
impl<F> Widget for Canvas<'_, F>
|
||||||
where
|
where
|
||||||
F: Fn(&mut Context),
|
F: Fn(&mut Context),
|
||||||
{
|
{
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let canvas_area = match self.block.take() {
|
Widget::render(&self, area, buf);
|
||||||
Some(b) => {
|
}
|
||||||
let inner_area = b.inner(area);
|
}
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
impl<F> Widget for &Canvas<'_, F>
|
||||||
|
where
|
||||||
|
F: Fn(&mut Context),
|
||||||
|
{
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let canvas_area = self.block.inner_if_some(area);
|
||||||
|
if canvas_area.is_empty() {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
buf.set_style(canvas_area, Style::default().bg(self.background_color));
|
buf.set_style(canvas_area, Style::default().bg(self.background_color));
|
||||||
|
|
||||||
|
|
|
@ -108,11 +108,7 @@ fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: us
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Line;
|
use super::Line;
|
||||||
use crate::{
|
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||||
assert_buffer_eq,
|
|
||||||
prelude::*,
|
|
||||||
widgets::{canvas::Canvas, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(line: Line, expected_lines: Vec<&str>) {
|
fn test(line: Line, expected_lines: Vec<&str>) {
|
||||||
|
|
|
@ -46,11 +46,7 @@ mod tests {
|
||||||
use strum::ParseError;
|
use strum::ParseError;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||||
assert_buffer_eq,
|
|
||||||
prelude::*,
|
|
||||||
widgets::{canvas::Canvas, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_resolution_to_string() {
|
fn map_resolution_to_string() {
|
||||||
|
|
|
@ -54,11 +54,7 @@ impl Shape for Rectangle {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||||
assert_buffer_eq,
|
|
||||||
prelude::*,
|
|
||||||
widgets::{canvas::Canvas, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn draw_block_lines() {
|
fn draw_block_lines() {
|
||||||
|
|
|
@ -4,14 +4,14 @@ use std::cmp::max;
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
use super::block::BlockExt;
|
||||||
use crate::{
|
use crate::{
|
||||||
buffer::Buffer,
|
|
||||||
layout::Flex,
|
layout::Flex,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
symbols,
|
symbols,
|
||||||
widgets::{
|
widgets::{
|
||||||
canvas::{Canvas, Line as CanvasLine, Points},
|
canvas::{Canvas, Line as CanvasLine, Points},
|
||||||
Block, Borders, Widget,
|
Block, Borders,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -809,7 +809,7 @@ impl<'a> Chart<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_x_labels(
|
fn render_x_labels(
|
||||||
&mut self,
|
&self,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
layout: &ChartLayout,
|
layout: &ChartLayout,
|
||||||
chart_area: Rect,
|
chart_area: Rect,
|
||||||
|
@ -892,7 +892,7 @@ impl<'a> Chart<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_y_labels(
|
fn render_y_labels(
|
||||||
&mut self,
|
&self,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
layout: &ChartLayout,
|
layout: &ChartLayout,
|
||||||
chart_area: Rect,
|
chart_area: Rect,
|
||||||
|
@ -916,26 +916,27 @@ impl<'a> Chart<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Chart<'a> {
|
impl Widget for Chart<'_> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
if area.area() == 0 {
|
Widget::render(&self, area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &Chart<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let chart_area = self.block.inner_if_some(area);
|
||||||
|
if chart_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
buf.set_style(area, self.style);
|
|
||||||
// Sample the style of the entire widget. This sample will be used to reset the style of
|
// Sample the style of the entire widget. This sample will be used to reset the style of
|
||||||
// the cells that are part of the components put on top of the grah area (i.e legend and
|
// the cells that are part of the components put on top of the grah area (i.e legend and
|
||||||
// axis names).
|
// axis names).
|
||||||
let original_style = buf.get(area.left(), area.top()).style();
|
let original_style = buf.get(area.left(), area.top()).style();
|
||||||
|
|
||||||
let chart_area = match self.block.take() {
|
|
||||||
Some(b) => {
|
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
let layout = self.layout(chart_area);
|
let layout = self.layout(chart_area);
|
||||||
let graph_area = layout.graph_area;
|
let graph_area = layout.graph_area;
|
||||||
if graph_area.width < 1 || graph_area.height < 1 {
|
if graph_area.width < 1 || graph_area.height < 1 {
|
||||||
|
@ -996,7 +997,7 @@ impl<'a> Widget for Chart<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((x, y)) = layout.title_x {
|
if let Some((x, y)) = layout.title_x {
|
||||||
let title = self.x_axis.title.unwrap();
|
let title = self.x_axis.title.as_ref().unwrap();
|
||||||
let width = graph_area
|
let width = graph_area
|
||||||
.right()
|
.right()
|
||||||
.saturating_sub(x)
|
.saturating_sub(x)
|
||||||
|
@ -1010,11 +1011,11 @@ impl<'a> Widget for Chart<'a> {
|
||||||
},
|
},
|
||||||
original_style,
|
original_style,
|
||||||
);
|
);
|
||||||
buf.set_line(x, y, &title, width);
|
buf.set_line(x, y, title, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((x, y)) = layout.title_y {
|
if let Some((x, y)) = layout.title_y {
|
||||||
let title = self.y_axis.title.unwrap();
|
let title = self.y_axis.title.as_ref().unwrap();
|
||||||
let width = graph_area
|
let width = graph_area
|
||||||
.right()
|
.right()
|
||||||
.saturating_sub(x)
|
.saturating_sub(x)
|
||||||
|
@ -1028,7 +1029,7 @@ impl<'a> Widget for Chart<'a> {
|
||||||
},
|
},
|
||||||
original_style,
|
original_style,
|
||||||
);
|
);
|
||||||
buf.set_line(x, y, &title, width);
|
buf.set_line(x, y, title, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(legend_area) = layout.legend_area {
|
if let Some(legend_area) = layout.legend_area {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{buffer::Buffer, layout::Rect, widgets::Widget};
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
|
/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
|
||||||
///
|
///
|
||||||
|
@ -25,6 +25,12 @@ use crate::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||||
pub struct Clear;
|
pub struct Clear;
|
||||||
|
|
||||||
impl Widget for Clear {
|
impl Widget for Clear {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
Widget::render(&self, area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &Clear {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
for x in area.left()..area.right() {
|
for x in area.left()..area.right() {
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
use crate::{
|
|
||||||
buffer::Buffer,
|
use crate::{prelude::*, widgets::Block};
|
||||||
layout::Rect,
|
|
||||||
style::{Color, Style, Styled},
|
|
||||||
symbols,
|
|
||||||
text::{Line, Span},
|
|
||||||
widgets::{Block, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A widget to display a progress bar.
|
/// A widget to display a progress bar.
|
||||||
///
|
///
|
||||||
|
@ -161,28 +155,33 @@ impl<'a> Gauge<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Gauge<'a> {
|
impl Widget for Gauge<'_> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
Widget::render(&self, area, buf);
|
||||||
let gauge_area = match self.block.take() {
|
|
||||||
Some(b) => {
|
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
}
|
||||||
None => area,
|
}
|
||||||
};
|
|
||||||
buf.set_style(gauge_area, self.gauge_style);
|
impl Widget for &Gauge<'_> {
|
||||||
if gauge_area.height < 1 {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let inner = self.block.inner_if_some(area);
|
||||||
|
self.render_gague(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gauge<'_> {
|
||||||
|
fn render_gague(&self, gauge_area: Rect, buf: &mut Buffer) {
|
||||||
|
if gauge_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.set_style(gauge_area, self.gauge_style);
|
||||||
|
|
||||||
// compute label value and its position
|
// compute label value and its position
|
||||||
// label is put at the center of the gauge_area
|
// label is put at the center of the gauge_area
|
||||||
let label = {
|
let default_label = Span::raw(format!("{}%", f64::round(self.ratio * 100.0)));
|
||||||
let pct = f64::round(self.ratio * 100.0);
|
let label = self.label.as_ref().unwrap_or(&default_label);
|
||||||
self.label.unwrap_or_else(|| Span::from(format!("{pct}%")))
|
|
||||||
};
|
|
||||||
let clamped_label_width = gauge_area.width.min(label.width() as u16);
|
let clamped_label_width = gauge_area.width.min(label.width() as u16);
|
||||||
let label_col = gauge_area.left() + (gauge_area.width - clamped_label_width) / 2;
|
let label_col = gauge_area.left() + (gauge_area.width - clamped_label_width) / 2;
|
||||||
let label_row = gauge_area.top() + gauge_area.height / 2;
|
let label_row = gauge_area.top() + gauge_area.height / 2;
|
||||||
|
@ -217,7 +216,7 @@ impl<'a> Widget for Gauge<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// render the label
|
// render the label
|
||||||
buf.set_span(label_col, label_row, &label, clamped_label_width);
|
buf.set_span(label_col, label_row, label, clamped_label_width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,32 +350,25 @@ impl<'a> LineGauge<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for LineGauge<'a> {
|
impl Widget for LineGauge<'_> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
Widget::render(&self, area, buf);
|
||||||
let gauge_area = match self.block.take() {
|
}
|
||||||
Some(b) => {
|
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
}
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
if gauge_area.height < 1 {
|
impl Widget for &LineGauge<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let gauge_area = self.block.inner_if_some(area);
|
||||||
|
if gauge_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ratio = self.ratio;
|
let ratio = self.ratio;
|
||||||
let label = self
|
let default_label = Line::from(format!("{:.0}%", ratio * 100.0));
|
||||||
.label
|
let label = self.label.as_ref().unwrap_or(&default_label);
|
||||||
.unwrap_or_else(move || Line::from(format!("{:.0}%", ratio * 100.0)));
|
let (col, row) = buf.set_line(gauge_area.left(), gauge_area.top(), label, gauge_area.width);
|
||||||
let (col, row) = buf.set_line(
|
|
||||||
gauge_area.left(),
|
|
||||||
gauge_area.top(),
|
|
||||||
&label,
|
|
||||||
gauge_area.width,
|
|
||||||
);
|
|
||||||
let start = col + 1;
|
let start = col + 1;
|
||||||
if start >= gauge_area.right() {
|
if start >= gauge_area.right() {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{Block, HighlightSpacing, StatefulWidget, Widget},
|
widgets::{Block, HighlightSpacing},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// State of the [`List`] widget
|
/// State of the [`List`] widget
|
||||||
|
@ -812,21 +812,37 @@ impl<'a> List<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StatefulWidget for List<'a> {
|
impl Widget for List<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let mut state = ListState::default();
|
||||||
|
StatefulWidget::render(&self, area, buf, &mut state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &List<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let mut state = ListState::default();
|
||||||
|
StatefulWidget::render(self, area, buf, &mut state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatefulWidget for List<'_> {
|
||||||
type State = ListState;
|
type State = ListState;
|
||||||
|
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
buf.set_style(area, self.style);
|
StatefulWidget::render(&self, area, buf, state);
|
||||||
let list_area = match self.block.take() {
|
}
|
||||||
Some(b) => {
|
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
}
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.items.is_empty() || list_area.is_empty() {
|
impl StatefulWidget for &List<'_> {
|
||||||
|
type State = ListState;
|
||||||
|
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let list_area = self.block.inner_if_some(area);
|
||||||
|
|
||||||
|
if list_area.is_empty() || self.items.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -846,7 +862,7 @@ impl<'a> StatefulWidget for List<'a> {
|
||||||
let selection_spacing = self.highlight_spacing.should_add(state.selected.is_some());
|
let selection_spacing = self.highlight_spacing.should_add(state.selected.is_some());
|
||||||
for (i, item) in self
|
for (i, item) in self
|
||||||
.items
|
.items
|
||||||
.iter_mut()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip(state.offset)
|
.skip(state.offset)
|
||||||
.take(last_visible_index - first_visible_index)
|
.take(last_visible_index - first_visible_index)
|
||||||
|
@ -911,13 +927,6 @@ impl<'a> StatefulWidget for List<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for List<'a> {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let mut state = ListState::default();
|
|
||||||
StatefulWidget::render(self, area, buf, &mut state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Styled for List<'a> {
|
impl<'a> Styled for List<'a> {
|
||||||
type Item = List<'a>;
|
type Item = List<'a>;
|
||||||
|
|
||||||
|
@ -961,7 +970,7 @@ mod tests {
|
||||||
prelude::Alignment,
|
prelude::Alignment,
|
||||||
style::{Color, Modifier, Stylize},
|
style::{Color, Modifier, Stylize},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Borders, StatefulWidget, Widget},
|
widgets::Borders,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
use super::block::BlockExt;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
text::StyledGrapheme,
|
text::StyledGrapheme,
|
||||||
widgets::{
|
widgets::{reflow::*, Block},
|
||||||
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
|
|
||||||
Block, Widget,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
|
fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
|
||||||
|
@ -325,19 +323,24 @@ impl<'a> Paragraph<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Paragraph<'a> {
|
impl Widget for Paragraph<'_> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
Widget::render(&self, area, buf);
|
||||||
let text_area = match self.block.take() {
|
}
|
||||||
Some(b) => {
|
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
}
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
if text_area.height < 1 {
|
impl Widget for &Paragraph<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let inner = self.block.inner_if_some(area);
|
||||||
|
self.render_paragraph(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Paragraph<'_> {
|
||||||
|
fn render_paragraph(&self, text_area: Rect, buf: &mut Buffer) {
|
||||||
|
if text_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,7 @@ use std::cmp::min;
|
||||||
|
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
use crate::{
|
use crate::{prelude::*, widgets::Block};
|
||||||
prelude::*,
|
|
||||||
widgets::{Block, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Widget to render a sparkline over one or more lines.
|
/// Widget to render a sparkline over one or more lines.
|
||||||
///
|
///
|
||||||
|
@ -156,18 +153,23 @@ impl<'a> Styled for Sparkline<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Sparkline<'a> {
|
impl Widget for Sparkline<'_> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let spark_area = match self.block.take() {
|
Widget::render(&self, area, buf);
|
||||||
Some(b) => {
|
}
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
}
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
if spark_area.height < 1 {
|
impl Widget for &Sparkline<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let inner = self.block.inner_if_some(area);
|
||||||
|
self.render_sparkline(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sparkline<'_> {
|
||||||
|
fn render_sparkline(&self, spark_area: Rect, buf: &mut Buffer) {
|
||||||
|
if spark_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{prelude::*, widgets::Widget};
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
|
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -4,7 +4,7 @@ use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{Flex, SegmentSize},
|
layout::{Flex, SegmentSize},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{Block, StatefulWidget, Widget},
|
widgets::Block,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A widget to display data in formatted columns.
|
/// A widget to display data in formatted columns.
|
||||||
|
@ -610,16 +610,32 @@ impl Widget for Table<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Widget for &Table<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let mut state = TableState::default();
|
||||||
|
StatefulWidget::render(self, area, buf, &mut state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl StatefulWidget for Table<'_> {
|
impl StatefulWidget for Table<'_> {
|
||||||
type State = TableState;
|
type State = TableState;
|
||||||
|
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
buf.set_style(area, self.style);
|
StatefulWidget::render(&self, area, buf, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let table_area = self.render_block(area, buf);
|
impl StatefulWidget for &Table<'_> {
|
||||||
|
type State = TableState;
|
||||||
|
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let table_area = self.block.inner_if_some(area);
|
||||||
if table_area.is_empty() {
|
if table_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection_width = self.selection_width(state);
|
let selection_width = self.selection_width(state);
|
||||||
let columns_widths = self.get_columns_widths(table_area.width, selection_width);
|
let columns_widths = self.get_columns_widths(table_area.width, selection_width);
|
||||||
let (header_area, rows_area, footer_area) = self.layout(table_area);
|
let (header_area, rows_area, footer_area) = self.layout(table_area);
|
||||||
|
@ -663,16 +679,6 @@ impl Table<'_> {
|
||||||
(header_area, rows_area, footer_area)
|
(header_area, rows_area, footer_area)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_block(&mut self, area: Rect, buf: &mut Buffer) -> Rect {
|
|
||||||
if let Some(block) = self.block.take() {
|
|
||||||
let inner_area = block.inner(area);
|
|
||||||
block.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
} else {
|
|
||||||
area
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_header(&self, area: Rect, buf: &mut Buffer, column_widths: &[(u16, u16)]) {
|
fn render_header(&self, area: Rect, buf: &mut Buffer, column_widths: &[(u16, u16)]) {
|
||||||
if let Some(ref header) = self.header {
|
if let Some(ref header) = self.header {
|
||||||
buf.set_style(area, header.style);
|
buf.set_style(area, header.style);
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
use crate::{
|
use crate::{prelude::*, widgets::Block};
|
||||||
prelude::*,
|
|
||||||
widgets::{Block, Widget},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVERSED);
|
const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVERSED);
|
||||||
|
|
||||||
|
@ -249,25 +246,30 @@ impl<'a> Styled for Tabs<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Tabs<'a> {
|
impl Widget for Tabs<'_> {
|
||||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
buf.set_style(area, self.style);
|
Widget::render(&self, area, buf);
|
||||||
let tabs_area = match self.block.take() {
|
}
|
||||||
Some(b) => {
|
|
||||||
let inner_area = b.inner(area);
|
|
||||||
b.render(area, buf);
|
|
||||||
inner_area
|
|
||||||
}
|
}
|
||||||
None => area,
|
|
||||||
};
|
|
||||||
|
|
||||||
if tabs_area.height < 1 {
|
impl Widget for &Tabs<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
buf.set_style(area, self.style);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
let inner = self.block.inner_if_some(area);
|
||||||
|
self.render_tabs(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tabs<'_> {
|
||||||
|
fn render_tabs(&self, tabs_area: Rect, buf: &mut Buffer) {
|
||||||
|
if tabs_area.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut x = tabs_area.left();
|
let mut x = tabs_area.left();
|
||||||
let titles_length = self.titles.len();
|
let titles_length = self.titles.len();
|
||||||
for (i, title) in self.titles.into_iter().enumerate() {
|
for (i, title) in self.titles.iter().enumerate() {
|
||||||
let last_title = titles_length - 1 == i;
|
let last_title = titles_length - 1 == i;
|
||||||
let remaining_width = tabs_area.right().saturating_sub(x);
|
let remaining_width = tabs_area.right().saturating_sub(x);
|
||||||
|
|
||||||
|
@ -284,7 +286,7 @@ impl<'a> Widget for Tabs<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
let pos = buf.set_line(x, tabs_area.top(), &title, remaining_width);
|
let pos = buf.set_line(x, tabs_area.top(), title, remaining_width);
|
||||||
if i == self.selected {
|
if i == self.selected {
|
||||||
buf.set_style(
|
buf.set_style(
|
||||||
Rect {
|
Rect {
|
||||||
|
|
|
@ -11,6 +11,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use time::{Date, Month};
|
use time::{Date, Month};
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn test_render<W: Widget>(widget: W, expected: Buffer, size: (u16, u16)) {
|
fn test_render<W: Widget>(widget: W, expected: Buffer, size: (u16, u16)) {
|
||||||
let backend = TestBackend::new(size.0, size.1);
|
let backend = TestBackend::new(size.0, size.1);
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
|
@ -8,6 +8,7 @@ use ratatui::{
|
||||||
widgets::{Axis, Block, Borders, Chart, Dataset, GraphType::Line},
|
widgets::{Axis, Block, Borders, Chart, Dataset, GraphType::Line},
|
||||||
Terminal,
|
Terminal,
|
||||||
};
|
};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
fn create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>> {
|
fn create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>> {
|
||||||
labels.iter().map(|l| Span::from(*l)).collect()
|
labels.iter().map(|l| Span::from(*l)).collect()
|
||||||
|
@ -30,9 +31,13 @@ where
|
||||||
terminal.backend().assert_buffer(&expected);
|
terminal.backend().assert_buffer(&expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[rstest]
|
||||||
fn widgets_chart_can_render_on_small_areas() {
|
#[case(0, 0)]
|
||||||
let test_case = |width, height| {
|
#[case(0, 1)]
|
||||||
|
#[case(1, 0)]
|
||||||
|
#[case(1, 1)]
|
||||||
|
#[case(2, 2)]
|
||||||
|
fn widgets_chart_can_render_on_small_areas(#[case] width: u16, #[case] height: u16) {
|
||||||
let backend = TestBackend::new(width, height);
|
let backend = TestBackend::new(width, height);
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
terminal
|
terminal
|
||||||
|
@ -56,12 +61,6 @@ fn widgets_chart_can_render_on_small_areas() {
|
||||||
f.render_widget(chart, f.size());
|
f.render_widget(chart, f.size());
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
};
|
|
||||||
test_case(0, 0);
|
|
||||||
test_case(0, 1);
|
|
||||||
test_case(1, 0);
|
|
||||||
test_case(1, 1);
|
|
||||||
test_case(2, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue