mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +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,
|
||||
};
|
||||
use palette::{convert::FromColorUnclamped, Okhsv, Srgb};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use ratatui::prelude::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct App {
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(¶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 {
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom widget.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(missing_docs)]
|
||||
use crate::prelude::*;
|
||||
use crate::{prelude::*, widgets::Block};
|
||||
|
||||
mod bar;
|
||||
mod bar_group;
|
||||
|
@ -7,8 +7,6 @@ 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.
|
||||
|
@ -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.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// 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) {
|
||||
// get the longest label
|
||||
let label_size = self
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,7 @@ use std::collections::HashMap;
|
|||
|
||||
use time::{Date, Duration, OffsetDateTime};
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use crate::{prelude::*, widgets::Block};
|
||||
|
||||
/// Display a month calendar for the month containing `display_date`
|
||||
#[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> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
// Block is used for borders and such
|
||||
// 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) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if let Some(style) = self.show_month {
|
||||
let line = Span::styled(
|
||||
Line::styled(
|
||||
format!("{} {}", self.display_date.month(), self.display_date.year()),
|
||||
style,
|
||||
);
|
||||
// cal is 21 cells wide, so hard code the 11
|
||||
let x_off = 11_u16.saturating_sub(line.width() as u16 / 2);
|
||||
buf.set_line(area.x + x_off, area.y, &line.into(), area.width);
|
||||
area.y += 1
|
||||
)
|
||||
.alignment(Alignment::Center)
|
||||
.render(month_header, buf);
|
||||
}
|
||||
|
||||
// Draw days of week
|
||||
if let Some(style) = self.show_weekday {
|
||||
let days = String::from(" Su Mo Tu We Th Fr Sa");
|
||||
buf.set_string(area.x, area.y, days, style);
|
||||
area.y += 1;
|
||||
Span::styled(" Su Mo Tu We Th Fr Sa", style).render(days_header, buf);
|
||||
}
|
||||
|
||||
// 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 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.
|
||||
while curr_day.month() as u8 != self.display_date.month().next() as u8 {
|
||||
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));
|
||||
curr_day += Duration::DAY;
|
||||
}
|
||||
buf.set_line(area.x, area.y, &spans.into(), area.width);
|
||||
area.y += 1;
|
||||
buf.set_line(days_area.x, y, &spans.into(), area.width);
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,7 @@ pub use self::{
|
|||
points::Points,
|
||||
rectangle::Rectangle,
|
||||
};
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
symbols,
|
||||
text::Line as TextLine,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use crate::{prelude::*, symbols, text::Line as TextLine, widgets::Block};
|
||||
|
||||
/// Interface for all shapes that may be drawn on a Canvas widget.
|
||||
pub trait Shape {
|
||||
|
@ -694,19 +687,25 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, F> Widget for Canvas<'a, F>
|
||||
impl<F> Widget for Canvas<'_, F>
|
||||
where
|
||||
F: Fn(&mut Context),
|
||||
{
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
let canvas_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::Line;
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
prelude::*,
|
||||
widgets::{canvas::Canvas, Widget},
|
||||
};
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
|
||||
#[track_caller]
|
||||
fn test(line: Line, expected_lines: Vec<&str>) {
|
||||
|
|
|
@ -46,11 +46,7 @@ mod tests {
|
|||
use strum::ParseError;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
prelude::*,
|
||||
widgets::{canvas::Canvas, Widget},
|
||||
};
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
|
||||
#[test]
|
||||
fn map_resolution_to_string() {
|
||||
|
|
|
@ -54,11 +54,7 @@ impl Shape for Rectangle {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
assert_buffer_eq,
|
||||
prelude::*,
|
||||
widgets::{canvas::Canvas, Widget},
|
||||
};
|
||||
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
|
||||
|
||||
#[test]
|
||||
fn draw_block_lines() {
|
||||
|
|
|
@ -4,14 +4,14 @@ use std::cmp::max;
|
|||
use strum::{Display, EnumString};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::block::BlockExt;
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Flex,
|
||||
prelude::*,
|
||||
symbols,
|
||||
widgets::{
|
||||
canvas::{Canvas, Line as CanvasLine, Points},
|
||||
Block, Borders, Widget,
|
||||
Block, Borders,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -809,7 +809,7 @@ impl<'a> Chart<'a> {
|
|||
}
|
||||
|
||||
fn render_x_labels(
|
||||
&mut self,
|
||||
&self,
|
||||
buf: &mut Buffer,
|
||||
layout: &ChartLayout,
|
||||
chart_area: Rect,
|
||||
|
@ -892,7 +892,7 @@ impl<'a> Chart<'a> {
|
|||
}
|
||||
|
||||
fn render_y_labels(
|
||||
&mut self,
|
||||
&self,
|
||||
buf: &mut Buffer,
|
||||
layout: &ChartLayout,
|
||||
chart_area: Rect,
|
||||
|
@ -916,26 +916,27 @@ impl<'a> Chart<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Chart<'a> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
if area.area() == 0 {
|
||||
impl Widget for Chart<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
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;
|
||||
}
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
// 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
|
||||
// axis names).
|
||||
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 graph_area = layout.graph_area;
|
||||
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 {
|
||||
let title = self.x_axis.title.unwrap();
|
||||
let title = self.x_axis.title.as_ref().unwrap();
|
||||
let width = graph_area
|
||||
.right()
|
||||
.saturating_sub(x)
|
||||
|
@ -1010,11 +1011,11 @@ impl<'a> Widget for Chart<'a> {
|
|||
},
|
||||
original_style,
|
||||
);
|
||||
buf.set_line(x, y, &title, width);
|
||||
buf.set_line(x, y, title, width);
|
||||
}
|
||||
|
||||
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
|
||||
.right()
|
||||
.saturating_sub(x)
|
||||
|
@ -1028,7 +1029,7 @@ impl<'a> Widget for Chart<'a> {
|
|||
},
|
||||
original_style,
|
||||
);
|
||||
buf.set_line(x, y, &title, width);
|
||||
buf.set_line(x, y, title, width);
|
||||
}
|
||||
|
||||
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).
|
||||
///
|
||||
|
@ -25,6 +25,12 @@ use crate::{buffer::Buffer, layout::Rect, widgets::Widget};
|
|||
pub struct 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) {
|
||||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
#![deny(missing_docs)]
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style, Styled},
|
||||
symbols,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
|
||||
use crate::{prelude::*, widgets::Block};
|
||||
|
||||
/// A widget to display a progress bar.
|
||||
///
|
||||
|
@ -161,28 +155,33 @@ impl<'a> Gauge<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Gauge<'a> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
impl Widget for Gauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Gauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
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);
|
||||
if gauge_area.height < 1 {
|
||||
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;
|
||||
}
|
||||
|
||||
buf.set_style(gauge_area, self.gauge_style);
|
||||
|
||||
// compute label value and its position
|
||||
// label is put at the center of the gauge_area
|
||||
let label = {
|
||||
let pct = f64::round(self.ratio * 100.0);
|
||||
self.label.unwrap_or_else(|| Span::from(format!("{pct}%")))
|
||||
};
|
||||
let default_label = Span::raw(format!("{}%", f64::round(self.ratio * 100.0)));
|
||||
let label = self.label.as_ref().unwrap_or(&default_label);
|
||||
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_row = gauge_area.top() + gauge_area.height / 2;
|
||||
|
@ -217,7 +216,7 @@ impl<'a> Widget for Gauge<'a> {
|
|||
}
|
||||
}
|
||||
// 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> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
let gauge_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
impl Widget for LineGauge<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let ratio = self.ratio;
|
||||
let label = self
|
||||
.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 default_label = Line::from(format!("{:.0}%", ratio * 100.0));
|
||||
let label = self.label.as_ref().unwrap_or(&default_label);
|
||||
let (col, row) = buf.set_line(gauge_area.left(), gauge_area.top(), label, gauge_area.width);
|
||||
let start = col + 1;
|
||||
if start >= gauge_area.right() {
|
||||
return;
|
||||
|
|
|
@ -4,7 +4,7 @@ use unicode_width::UnicodeWidthStr;
|
|||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
widgets::{Block, HighlightSpacing, StatefulWidget, Widget},
|
||||
widgets::{Block, HighlightSpacing},
|
||||
};
|
||||
|
||||
/// 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;
|
||||
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
buf.set_style(area, self.style);
|
||||
let list_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
StatefulWidget::render(&self, area, buf, state);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -846,7 +862,7 @@ impl<'a> StatefulWidget for List<'a> {
|
|||
let selection_spacing = self.highlight_spacing.should_add(state.selected.is_some());
|
||||
for (i, item) in self
|
||||
.items
|
||||
.iter_mut()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(state.offset)
|
||||
.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> {
|
||||
type Item = List<'a>;
|
||||
|
||||
|
@ -961,7 +970,7 @@ mod tests {
|
|||
prelude::Alignment,
|
||||
style::{Color, Modifier, Stylize},
|
||||
text::{Line, Span},
|
||||
widgets::{Borders, StatefulWidget, Widget},
|
||||
widgets::Borders,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::block::BlockExt;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
text::StyledGrapheme,
|
||||
widgets::{
|
||||
reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
|
||||
Block, Widget,
|
||||
},
|
||||
widgets::{reflow::*, Block},
|
||||
};
|
||||
|
||||
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> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
let text_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
impl Widget for Paragraph<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,7 @@ use std::cmp::min;
|
|||
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use crate::{prelude::*, widgets::Block};
|
||||
|
||||
/// 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> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
let spark_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
impl Widget for Sparkline<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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`].
|
||||
///
|
||||
|
|
|
@ -4,7 +4,7 @@ use super::*;
|
|||
use crate::{
|
||||
layout::{Flex, SegmentSize},
|
||||
prelude::*,
|
||||
widgets::{Block, StatefulWidget, Widget},
|
||||
widgets::Block,
|
||||
};
|
||||
|
||||
/// 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<'_> {
|
||||
type State = TableState;
|
||||
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
buf.set_style(area, self.style);
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
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() {
|
||||
return;
|
||||
}
|
||||
|
||||
let selection_width = self.selection_width(state);
|
||||
let columns_widths = self.get_columns_widths(table_area.width, selection_width);
|
||||
let (header_area, rows_area, footer_area) = self.layout(table_area);
|
||||
|
@ -663,16 +679,6 @@ impl Table<'_> {
|
|||
(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)]) {
|
||||
if let Some(ref header) = self.header {
|
||||
buf.set_style(area, header.style);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
#![deny(missing_docs)]
|
||||
use crate::{
|
||||
prelude::*,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use crate::{prelude::*, widgets::Block};
|
||||
|
||||
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> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
let tabs_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
impl Widget for Tabs<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
Widget::render(&self, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let mut x = tabs_area.left();
|
||||
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 remaining_width = tabs_area.right().saturating_sub(x);
|
||||
|
||||
|
@ -284,7 +286,7 @@ impl<'a> Widget for Tabs<'a> {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
buf.set_style(
|
||||
Rect {
|
||||
|
|
|
@ -11,6 +11,7 @@ use ratatui::{
|
|||
};
|
||||
use time::{Date, Month};
|
||||
|
||||
#[track_caller]
|
||||
fn test_render<W: Widget>(widget: W, expected: Buffer, size: (u16, u16)) {
|
||||
let backend = TestBackend::new(size.0, size.1);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
@ -63,7 +64,7 @@ fn show_month_header() {
|
|||
)
|
||||
.show_month_header(Style::default());
|
||||
let expected = Buffer::with_lines(vec![
|
||||
" January 2023 ",
|
||||
" January 2023 ",
|
||||
" 1 2 3 4 5 6 7",
|
||||
" 8 9 10 11 12 13 14",
|
||||
" 15 16 17 18 19 20 21",
|
||||
|
@ -101,7 +102,7 @@ fn show_combo() {
|
|||
.show_month_header(Style::default())
|
||||
.show_surrounding(Style::default());
|
||||
let expected = Buffer::with_lines(vec![
|
||||
" January 2023 ",
|
||||
" January 2023 ",
|
||||
" Su Mo Tu We Th Fr Sa",
|
||||
" 1 2 3 4 5 6 7",
|
||||
" 8 9 10 11 12 13 14",
|
||||
|
|
|
@ -8,6 +8,7 @@ use ratatui::{
|
|||
widgets::{Axis, Block, Borders, Chart, Dataset, GraphType::Line},
|
||||
Terminal,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
fn create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>> {
|
||||
labels.iter().map(|l| Span::from(*l)).collect()
|
||||
|
@ -30,38 +31,36 @@ where
|
|||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_chart_can_render_on_small_areas() {
|
||||
let test_case = |width, height| {
|
||||
let backend = TestBackend::new(width, height);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let datasets = vec![Dataset::default()
|
||||
.marker(symbols::Marker::Braille)
|
||||
.style(Style::default().fg(Color::Magenta))
|
||||
.data(&[(0.0, 0.0)])];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(Block::default().title("Plot").borders(Borders::ALL))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
.labels(create_labels(&["0.0", "1.0"])),
|
||||
)
|
||||
.y_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
.labels(create_labels(&["0.0", "1.0"])),
|
||||
);
|
||||
f.render_widget(chart, f.size());
|
||||
})
|
||||
.unwrap();
|
||||
};
|
||||
test_case(0, 0);
|
||||
test_case(0, 1);
|
||||
test_case(1, 0);
|
||||
test_case(1, 1);
|
||||
test_case(2, 2);
|
||||
#[rstest]
|
||||
#[case(0, 0)]
|
||||
#[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 mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let datasets = vec![Dataset::default()
|
||||
.marker(symbols::Marker::Braille)
|
||||
.style(Style::default().fg(Color::Magenta))
|
||||
.data(&[(0.0, 0.0)])];
|
||||
let chart = Chart::new(datasets)
|
||||
.block(Block::default().title("Plot").borders(Borders::ALL))
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
.labels(create_labels(&["0.0", "1.0"])),
|
||||
)
|
||||
.y_axis(
|
||||
Axis::default()
|
||||
.bounds([0.0, 0.0])
|
||||
.labels(create_labels(&["0.0", "1.0"])),
|
||||
);
|
||||
f.render_widget(chart, f.size());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue