diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 156d2cd7..22dc82f5 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -11,6 +11,7 @@ github with a [breaking change] label. This is a quick summary of the sections below: - Unreleased (0.24.1) + - `List::start_corner` is renamed to `List::direction` - `List::new()` now accepts `IntoIterator>>` - `Table::new()` now requires specifying the widths - `Table::widths()` now accepts `IntoIterator>` @@ -41,6 +42,29 @@ This is a quick summary of the sections below: ## Unreleased (v0.24.1) +### `List::start_corner` is renamed to `List::direction` ([#673]) + +[#673]: https://github.com/ratatui-org/ratatui/pull/673 + +Previously `List::start_corner` didn't communicate the intent of the method. It also used an +inadequate `Corner` enum. The method is now renamed `direction` and a new enum +`ListDirection` has been added. + +```diff +- List::new(/* items */).start_corner(Corner::TopLeft); +- List::new(/* items */).start_corner(Corner::TopRight); +// This is not an error, BottomRight rendered top to bottom previously +- List::new(/* items */).start_corner(Corner::BottomRight); +// all becomes ++ List::new(/* items */).direction(ListDirection::TopToBottom); +``` + +```diff +- List::new(/* items */).start_corner(Corner::BottomLeft); +// becomes ++ List::new(/* items */).direction(ListDirection::BottomToTop); +``` + ### `List::new()` now accepts `IntoIterator>>` ([#672]) [#672]: https://github.com/ratatui-org/ratatui/pull/672 @@ -50,10 +74,10 @@ error for `IntoIterator`s with an indeterminate item (e.g. empty vecs). E.g. -```rust -let list = List::new(vec![]); +```diff +- let list = List::new(vec![]); // becomes -let list = List::default(); ++ let list = List::default(); ``` ### The default `Tabs::highlight_style` is now `Style::new().reversed()` ([#635]) @@ -76,23 +100,23 @@ widget in the default configuration would not show any indication of the selecte Previously `Table`s could be constructed without widths. In almost all cases this is an error. A new widths parameter is now mandatory on `Table::new()`. Existing code of the form: -```rust -Table::new(rows).widths(widths) +```diff +- Table::new(rows).widths(widths) ``` Should be updated to: -```rust -Table::new(rows, widths) +```diff ++ Table::new(rows, widths) ``` For ease of automated replacement in cases where the amount of code broken by this change is large or complex, it may be convenient to replace `Table::new` with `Table::default().rows`. -```rust -Table::new(rows).block(block).widths(widths); +```diff +- Table::new(rows).block(block).widths(widths); // becomes -Table::default().rows(rows).widths(widths) ++ Table::default().rows(rows).widths(widths) ``` ### `Table::widths()` now accepts `IntoIterator>` ([#663]) @@ -105,10 +129,10 @@ the `&`. E.g. -```rust -let table = Table::new(rows).widths(&[Constraint::Length(1)]); +```diff +- let table = Table::new(rows).widths(&[Constraint::Length(1)]); // becomes -let table = Table::new(rows).widths([Constraint::Length(1)]); ++ let table = Table::new(rows).widths([Constraint::Length(1)]); ``` ### Layout::new() now accepts direction and constraint parameters ([#557]) @@ -147,10 +171,10 @@ Applications can now set custom borders on a `Block` by calling `border_set()`. `BorderType::line_symbols()` is renamed to `border_symbols()` and now returns a new struct `symbols::border::Set`. E.g.: -```rust -let line_set: symbols::line::Set = BorderType::line_symbols(BorderType::Plain); +```diff +- let line_set: symbols::line::Set = BorderType::line_symbols(BorderType::Plain); // becomes -let border_set: symbols::border::Set = BorderType::border_symbols(BorderType::Plain); ++ let border_set: symbols::border::Set = BorderType::border_symbols(BorderType::Plain); ``` ### Generic `Backend` parameter removed from `Frame` ([#530]) @@ -161,10 +185,10 @@ let border_set: symbols::border::Set = BorderType::border_symbols(BorderType::Pl accept `Frame`. To migrate existing code, remove any generic parameters from code that uses an instance of a Frame. E.g.: -```rust -fn ui(frame: &mut Frame) { ... } +```diff +- fn ui(frame: &mut Frame) { ... } // becomes -fn ui(frame: Frame) { ... } ++ fn ui(frame: Frame) { ... } ``` ### `Stylize` shorthands now consume rather than borrow `String` ([#466]) @@ -176,13 +200,13 @@ new implementation of `Stylize` was added that returns a `Span<'static>`. This c be consumed rather than borrowed. Existing code that expects to use the string after a call will no longer compile. E.g. -```rust -let s = String::new("foo"); -let span1 = s.red(); -let span2 = s.blue(); // will no longer compile as s is consumed by the previous line +```diff +- let s = String::new("foo"); +- let span1 = s.red(); +- let span2 = s.blue(); // will no longer compile as s is consumed by the previous line // becomes -let span1 = s.clone().red(); -let span2 = s.blue(); ++ let span1 = s.clone().red(); ++ let span2 = s.blue(); ``` ### Deprecated `Spans` type removed (replaced with `Line`) ([#426]) @@ -192,12 +216,12 @@ let span2 = s.blue(); `Spans` was replaced with `Line` in 0.21.0. `Buffer::set_spans` was replaced with `Buffer::set_line`. -```rust -let spans = Spans::from(some_string_str_span_or_vec_span); -buffer.set_spans(0, 0, spans, 10); +```diff +- let spans = Spans::from(some_string_str_span_or_vec_span); +- buffer.set_spans(0, 0, spans, 10); // becomes -let line - Line::from(some_string_str_span_or_vec_span); -buffer.set_line(0, 0, line, 10); ++ let line - Line::from(some_string_str_span_or_vec_span); ++ buffer.set_line(0, 0, line, 10); ``` ## [v0.23.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.23.0) @@ -208,10 +232,10 @@ buffer.set_line(0, 0, line, 10); The track symbol of `Scrollbar` is now optional, this method now takes an optional value. -```rust -let scrollbar = Scrollbar::default().track_symbol("|"); +```diff +- let scrollbar = Scrollbar::default().track_symbol("|"); // becomes -let scrollbar = Scrollbar::default().track_symbol(Some("|")); ++ let scrollbar = Scrollbar::default().track_symbol(Some("|")); ``` ### `Scrollbar` symbols moved to `symbols::scrollbar` and `widgets::scrollbar` module is private ([#330]) @@ -222,10 +246,10 @@ The symbols for defining scrollbars have been moved to the `symbols` module from `widgets::scrollbar` module which is no longer public. To update your code update any imports to the new module locations. E.g.: -```rust -use ratatui::{widgets::scrollbar::{Scrollbar, Set}}; +```diff +- use ratatui::{widgets::scrollbar::{Scrollbar, Set}}; // becomes -use ratatui::{widgets::Scrollbar, symbols::scrollbar::Set} ++ use ratatui::{widgets::Scrollbar, symbols::scrollbar::Set} ``` ### MSRV updated to 1.67 ([#361]) @@ -259,13 +283,13 @@ The minimum supported rust version is now 1.65.0. In order to support inline viewports, the unstable method `Terminal::with_options()` was stabilized and `ViewPort` was changed from a struct to an enum. -```rust +```diff let terminal = Terminal::with_options(backend, TerminalOptions { - viewport: Viewport::fixed(area), +- viewport: Viewport::fixed(area), }); // becomes let terminal = Terminal::with_options(backend, TerminalOptions { - viewport: Viewport::Fixed(area), ++ viewport: Viewport::Fixed(area), }); ``` @@ -277,10 +301,10 @@ A new type `Masked` was introduced that implements `From>`. This causes previously did not need to use type annotations to fail to compile. To fix this, annotate or call to_string() / to_owned() / as_str() on the value. E.g.: -```rust -let paragraph = Paragraph::new("".as_ref()); +```diff +- let paragraph = Paragraph::new("".as_ref()); // becomes -let paragraph = Paragraph::new("".as_str()); ++ let paragraph = Paragraph::new("".as_str()); ``` ### `Marker::Block` now renders as a block rather than a bar character ([#133]) @@ -291,10 +315,10 @@ Code using the `Block` marker that previously rendered using a half block charac renders using the full block character (`'█'`). A new marker variant`Bar` is introduced to replace the existing code. -```rust -let canvas = Canvas::default().marker(Marker::Block); +```diff +- let canvas = Canvas::default().marker(Marker::Block); // becomes -let canvas = Canvas::default().marker(Marker::Bar); ++ let canvas = Canvas::default().marker(Marker::Bar); ``` ## [v0.20.0](https://github.com/ratatui-org/ratatui/releases/tag/v0.20.0) diff --git a/examples/list.rs b/examples/list.rs index 5c74eee2..885d8e76 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -273,6 +273,6 @@ fn ui(f: &mut Frame, app: &mut App) { .collect(); let events_list = List::new(events) .block(Block::default().borders(Borders::ALL).title("List")) - .start_corner(Corner::BottomLeft); + .direction(ListDirection::BottomToTop); f.render_widget(events_list, chunks[1]); } diff --git a/src/widgets.rs b/src/widgets.rs index 08757945..5933731a 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -46,7 +46,7 @@ pub use self::{ chart::{Axis, Chart, Dataset, GraphType}, clear::Clear, gauge::{Gauge, LineGauge}, - list::{List, ListItem, ListState}, + list::{List, ListDirection, ListItem, ListState}, paragraph::{Paragraph, Wrap}, scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState}, sparkline::{RenderDirection, Sparkline}, diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 926ebdeb..1dccdd4b 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -1,4 +1,5 @@ #![warn(missing_docs)] +use strum::{Display, EnumString}; use unicode_width::UnicodeWidthStr; use crate::{ @@ -360,7 +361,7 @@ where /// - [`List::highlight_symbol`] sets the symbol to be displayed in front of the selected item. /// - [`List::repeat_highlight_symbol`] sets whether to repeat the symbol and style over selected /// multi-line items -/// - [`List::start_corner`] sets the list direction +/// - [`List::direction`] sets the list direction /// /// # Examples /// @@ -375,7 +376,7 @@ where /// .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) /// .highlight_symbol(">>") /// .repeat_highlight_symbol(true) -/// .start_corner(Corner::TopLeft); +/// .direction(ListDirection::BottomToTop); /// /// frame.render_widget(list, area); /// # } @@ -404,8 +405,8 @@ pub struct List<'a> { items: Vec>, /// Style used as a base style for the widget style: Style, - /// List display direction, *top to bottom* or *bottom to top* - start_corner: Corner, + /// List display direction + direction: ListDirection, /// Style used to render selected item highlight_style: Style, /// Symbol in front of the selected item (Shift all items to the right) @@ -416,6 +417,20 @@ pub struct List<'a> { highlight_spacing: HighlightSpacing, } +/// Defines the direction in which the list will be rendered. +/// +/// If there are too few items to fill the screen, the list will stick to the starting edge. +/// +/// See [`List::direction`]. +#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)] +pub enum ListDirection { + /// The first value is on the top, going to the bottom + #[default] + TopToBottom, + /// The first value is on the bottom, going to the top. + BottomToTop, +} + impl<'a> List<'a> { /// Creates a new list from [`ListItem`]s /// @@ -458,11 +473,8 @@ impl<'a> List<'a> { block: None, style: Style::default(), items: items.into_iter().map(|i| i.into()).collect(), - start_corner: Corner::TopLeft, - highlight_style: Style::default(), - highlight_symbol: None, - repeat_highlight_symbol: false, - highlight_spacing: HighlightSpacing::default(), + direction: ListDirection::default(), + ..Self::default() } } @@ -622,10 +634,33 @@ impl<'a> List<'a> { self } + /// Defines the list direction (up or down) + /// + /// Defines if the `List` is displayed *top to bottom* (default) or *bottom to top*. + /// If there is too few items to fill the screen, the list will stick to the starting edge. + /// + /// This is a fluent setter method which must be chained or used as it consumes self + /// + /// # Example + /// + /// Bottom to top + /// + /// ```rust + /// # use ratatui::{prelude::*, widgets::*}; + /// # let items = vec!["Item 1"]; + /// let list = List::new(items).direction(ListDirection::BottomToTop); + /// ``` + #[must_use = "method moves the value of self and returns the modified value"] + pub fn direction(mut self, direction: ListDirection) -> List<'a> { + self.direction = direction; + self + } + /// Defines the list direction (up or down) /// /// Defines if the `List` is displayed *top to bottom* (default) or *bottom to top*. Use /// [`Corner::BottomLeft`] to go *bottom to top*. **Any** other variant will go *top to bottom*. + /// If there is too few items to fill the screen, the list will stick to the starting edge. /// /// This is set to [`Corner::TopLeft`] by default. /// @@ -654,9 +689,13 @@ impl<'a> List<'a> { /// let list = List::new(items).start_corner(Corner::BottomLeft); /// ``` #[must_use = "method moves the value of self and returns the modified value"] - pub fn start_corner(mut self, corner: Corner) -> List<'a> { - self.start_corner = corner; - self + #[deprecated(since = "0.25.0", note = "You should use `List::direction` instead.")] + pub fn start_corner(self, corner: Corner) -> List<'a> { + if corner == Corner::BottomLeft { + self.direction(ListDirection::BottomToTop) + } else { + self.direction(ListDirection::TopToBottom) + } } /// Returns the number of [`ListItem`]s in the list @@ -746,7 +785,7 @@ impl<'a> StatefulWidget for List<'a> { .skip(state.offset) .take(end - start) { - let (x, y) = if self.start_corner == Corner::BottomLeft { + let (x, y) = if self.direction == ListDirection::BottomToTop { current_height += item.height() as u16; (list_area.left(), list_area.bottom() - current_height) } else { @@ -1488,9 +1527,40 @@ mod tests { ); } + #[test] + fn test_list_direction_top_to_bottom() { + let items = list_items(vec!["Item 0", "Item 1", "Item 2"]); + let list = List::new(items).direction(ListDirection::TopToBottom); + let buffer = render_widget(list, 10, 5); + let expected = Buffer::with_lines(vec![ + "Item 0 ", + "Item 1 ", + "Item 2 ", + " ", + " ", + ]); + assert_buffer_eq!(buffer, expected); + } + + #[test] + fn test_list_direction_bottom_to_top() { + let items = list_items(vec!["Item 0", "Item 1", "Item 2"]); + let list = List::new(items).direction(ListDirection::BottomToTop); + let buffer = render_widget(list, 10, 5); + let expected = Buffer::with_lines(vec![ + " ", + " ", + "Item 2 ", + "Item 1 ", + "Item 0 ", + ]); + assert_buffer_eq!(buffer, expected); + } + #[test] fn test_list_start_corner_top_left() { let items = list_items(vec!["Item 0", "Item 1", "Item 2"]); + #[allow(deprecated)] // For start_corner let list = List::new(items).start_corner(Corner::TopLeft); let buffer = render_widget(list, 10, 5); let expected = Buffer::with_lines(vec![ @@ -1506,6 +1576,7 @@ mod tests { #[test] fn test_list_start_corner_bottom_left() { let items = list_items(vec!["Item 0", "Item 1", "Item 2"]); + #[allow(deprecated)] // For start_corner let list = List::new(items).start_corner(Corner::BottomLeft); let buffer = render_widget(list, 10, 5); let expected = Buffer::with_lines(vec![