mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +00:00
feat(List)!: List::new
now accepts IntoIterator<Item = Into<ListItem>>
(#672)
This allows to build list like ``` List::new(["Item 1", "Item 2"]) ``` BREAKING CHANGE: `List::new` parameter type changed from `Into<Vec<ListItem<'a>>>` to `IntoIterator<Item = Into<ListItem<'a>>>`
This commit is contained in:
parent
8bfd6661e2
commit
aef495604c
3 changed files with 173 additions and 23 deletions
|
@ -11,8 +11,9 @@ github with a [breaking change] label.
|
|||
This is a quick summary of the sections below:
|
||||
|
||||
- Unreleased (0.24.1)
|
||||
- `List::new()` now accepts `IntoIterator<Item = Into<ListItem<'a>>>`
|
||||
- `Table::new()` now requires specifying the widths
|
||||
-`Table::widths()` now accepts `IntoIterator<Item = AsRef<Constraint>>`
|
||||
- `Table::widths()` now accepts `IntoIterator<Item = AsRef<Constraint>>`
|
||||
- Layout::new() now accepts direction and constraint parameters
|
||||
- The default `Tabs::highlight_style` is now `Style::new().reversed()`
|
||||
|
||||
|
@ -40,6 +41,21 @@ This is a quick summary of the sections below:
|
|||
|
||||
## Unreleased (v0.24.1)
|
||||
|
||||
### `List::new()` now accepts `IntoIterator<Item = Into<ListItem<'a>>>` ([#672])
|
||||
|
||||
[#672]: https://github.com/ratatui-org/ratatui/pull/672
|
||||
|
||||
Previously `List::new()` took `Into<Vec<ListItem<'a>>>`. This change will throw a compilation
|
||||
error for `IntoIterator`s with an indeterminate item (e.g. empty vecs).
|
||||
|
||||
E.g.
|
||||
|
||||
```rust
|
||||
let list = List::new(vec![]);
|
||||
// becomes
|
||||
let list = List::default();
|
||||
```
|
||||
|
||||
### The default `Tabs::highlight_style` is now `Style::new().reversed()` ([#635])
|
||||
|
||||
Previously the default highlight style for tabs was `Style::default()`, which meant that a `Tabs`
|
||||
|
@ -53,10 +69,12 @@ Previously the default highlight style for tabs was `Style::default()`, which me
|
|||
widget in the default configuration would not show any indication of the selected tab.
|
||||
|
||||
|
||||
### `Table::new()` now requires specifying the widths of the columrs (#664)
|
||||
### `Table::new()` now requires specifying the widths of the columns (#664)
|
||||
|
||||
[#664]: https://github.com/ratatui-org/ratatui/pull/664
|
||||
|
||||
Previously `Table`s could be constructed without widths. In almost all cases this is an error.
|
||||
A new widths parameter is now manadatory on `Table::new()`. Existing code of the form:
|
||||
A new widths parameter is now mandatory on `Table::new()`. Existing code of the form:
|
||||
|
||||
```rust
|
||||
Table::new(rows).widths(widths)
|
||||
|
|
|
@ -34,7 +34,7 @@ use crate::{
|
|||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// # let items = vec![];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let list = List::new(items);
|
||||
///
|
||||
/// // This should be stored outside of the function in your application state.
|
||||
|
@ -173,12 +173,22 @@ impl ListState {
|
|||
/// # Examples
|
||||
///
|
||||
/// You can create [`ListItem`]s from simple `&str`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item = ListItem::new("Item 1");
|
||||
/// ```
|
||||
///
|
||||
/// Anything that can be converted to [`Text`] can be a [`ListItem`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item1: ListItem = "Item 1".into();
|
||||
/// let item2: ListItem = Line::raw("Item 2").into();
|
||||
/// ```
|
||||
///
|
||||
/// A [`ListItem`] styled with [`Stylize`]
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item = ListItem::new("Item 1").red().on_white();
|
||||
|
@ -186,6 +196,7 @@ impl ListState {
|
|||
///
|
||||
/// If you need more control over the item's style, you can explicitly style the underlying
|
||||
/// [`Text`]
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let mut text = Text::default();
|
||||
|
@ -208,12 +219,22 @@ impl<'a> ListItem<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// You can create [`ListItem`]s from simple `&str`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item = ListItem::new("Item 1");
|
||||
/// ```
|
||||
///
|
||||
/// Anything that can be converted to [`Text`] can be a [`ListItem`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item1: ListItem = "Item 1".into();
|
||||
/// let item2: ListItem = Line::raw("Item 2").into();
|
||||
/// ```
|
||||
///
|
||||
/// You can also create multilines item
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item = ListItem::new("Multi-line\nitem");
|
||||
|
@ -264,6 +285,7 @@ impl<'a> ListItem<'a> {
|
|||
/// # Examples
|
||||
///
|
||||
/// One line item
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item = ListItem::new("Item 1");
|
||||
|
@ -271,6 +293,7 @@ impl<'a> ListItem<'a> {
|
|||
/// ```
|
||||
///
|
||||
/// Two lines item (note the `\n`)
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let item = ListItem::new("Multi-line\nitem");
|
||||
|
@ -300,6 +323,15 @@ impl<'a> ListItem<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for ListItem<'a>
|
||||
where
|
||||
T: Into<Text<'a>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
ListItem::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget to display several items among which one can be selected (optional)
|
||||
///
|
||||
/// A list is a collection of [`ListItem`]s.
|
||||
|
@ -336,7 +368,7 @@ impl<'a> ListItem<'a> {
|
|||
/// use ratatui::{prelude::*, widgets::*};
|
||||
/// # fn ui(frame: &mut Frame) {
|
||||
/// # let area = Rect::default();
|
||||
/// let items = [ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3")];
|
||||
/// let items = ["Item 1", "Item 2", "Item 3"];
|
||||
/// let list = List::new(items)
|
||||
/// .block(Block::default().title("List").borders(Borders::ALL))
|
||||
/// .style(Style::default().fg(Color::White))
|
||||
|
@ -357,7 +389,7 @@ impl<'a> ListItem<'a> {
|
|||
/// # let area = Rect::default();
|
||||
/// // This should be stored outside of the function in your application state.
|
||||
/// let mut state = ListState::default();
|
||||
/// let items = [ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3")];
|
||||
/// let items = ["Item 1", "Item 2", "Item 3"];
|
||||
/// let list = List::new(items)
|
||||
/// .block(Block::default().title("List").borders(Borders::ALL))
|
||||
/// .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
|
||||
|
@ -366,7 +398,7 @@ impl<'a> ListItem<'a> {
|
|||
///
|
||||
/// frame.render_stateful_widget(list, area, &mut state);
|
||||
/// # }
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct List<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
items: Vec<ListItem<'a>>,
|
||||
|
@ -387,22 +419,45 @@ pub struct List<'a> {
|
|||
impl<'a> List<'a> {
|
||||
/// Creates a new list from [`ListItem`]s
|
||||
///
|
||||
/// The `items` parameter accepts any value that can be converted into an iterator of
|
||||
/// [`Into<ListItem>`]. This includes arrays of [`&str`] or [`Vec`]s of [`Text`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// From a slice of [`ListItem`]
|
||||
/// From a slice of [`&str`]
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let items = [ListItem::new("Item 1"), ListItem::new("Item 2")];
|
||||
/// let list = List::new(items);
|
||||
/// let list = List::new(["Item 1", "Item 2"]);
|
||||
/// ```
|
||||
///
|
||||
/// From [`Text`]
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let list = List::new([
|
||||
/// Text::styled("Item 1", Style::default().red()),
|
||||
/// Text::styled("Item 2", Style::default().red())
|
||||
/// ]);
|
||||
/// ```
|
||||
///
|
||||
/// You can also create an empty list using the [`Default`] implementation and use the
|
||||
/// [`List::items`] fluent setter.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let empty_list = List::default();
|
||||
/// let filled_list = empty_list.items(["Item 1"]);
|
||||
/// ```
|
||||
pub fn new<T>(items: T) -> List<'a>
|
||||
where
|
||||
T: Into<Vec<ListItem<'a>>>,
|
||||
T: IntoIterator,
|
||||
T::Item: Into<ListItem<'a>>,
|
||||
{
|
||||
List {
|
||||
block: None,
|
||||
style: Style::default(),
|
||||
items: items.into(),
|
||||
items: items.into_iter().map(|i| i.into()).collect(),
|
||||
start_corner: Corner::TopLeft,
|
||||
highlight_style: Style::default(),
|
||||
highlight_symbol: None,
|
||||
|
@ -411,6 +466,29 @@ impl<'a> List<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the items
|
||||
///
|
||||
/// The `items` parameter accepts any value that can be converted into an iterator of
|
||||
/// [`Into<ListItem>`]. This includes arrays of [`&str`] or [`Vec`]s of [`Text`].
|
||||
///
|
||||
/// This is a fluent setter method which must be chained or used as it consumes self.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// let list = List::default().items(["Item 1", "Item 2"]);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn items<T>(mut self, items: T) -> Self
|
||||
where
|
||||
T: IntoIterator,
|
||||
T::Item: Into<ListItem<'a>>,
|
||||
{
|
||||
self.items = items.into_iter().map(|i| i.into()).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Wraps the list with a custom [`Block`] widget.
|
||||
///
|
||||
/// The `block` parameter holds the specified [`Block`] to be created around the [`List`]
|
||||
|
@ -421,7 +499,7 @@ impl<'a> List<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1")];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let block = Block::default().title("List").borders(Borders::ALL);
|
||||
/// let list = List::new(items).block(block);
|
||||
/// ```
|
||||
|
@ -442,7 +520,7 @@ impl<'a> List<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1")];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let list = List::new(items).style(Style::new().red().italic());
|
||||
/// ```
|
||||
///
|
||||
|
@ -453,7 +531,7 @@ impl<'a> List<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1")];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let list = List::new(items).red().italic();
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
|
@ -472,7 +550,7 @@ impl<'a> List<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1"), ListItem::new("Item 2")];
|
||||
/// # let items = vec!["Item 1", "Item 2"];
|
||||
/// let list = List::new(items).highlight_symbol(">>");
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
|
@ -493,7 +571,7 @@ impl<'a> List<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1"), ListItem::new("Item 2")];
|
||||
/// # let items = vec!["Item 1", "Item 2"];
|
||||
/// let list = List::new(items).highlight_style(Style::new().red().italic());
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
|
@ -535,7 +613,7 @@ impl<'a> List<'a> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1")];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let list = List::new(items).highlight_spacing(HighlightSpacing::Always);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
|
@ -561,16 +639,18 @@ impl<'a> List<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// Same as default, i.e. *top to bottom*. Despite the name implying otherwise.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1")];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let list = List::new(items).start_corner(Corner::BottomRight);
|
||||
/// ```
|
||||
///
|
||||
/// Bottom to top
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui::{prelude::*, widgets::*};
|
||||
/// # let items = vec![ListItem::new("Item 1")];
|
||||
/// # let items = vec!["Item 1"];
|
||||
/// let list = List::new(items).start_corner(Corner::BottomLeft);
|
||||
/// ```
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
|
@ -849,6 +929,38 @@ mod tests {
|
|||
assert_eq!(item.style, Style::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_str_into_list_item() {
|
||||
let s = "Test item";
|
||||
let item: ListItem = s.into();
|
||||
assert_eq!(item.content, Text::from(s));
|
||||
assert_eq!(item.style, Style::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_into_list_item() {
|
||||
let s = String::from("Test item");
|
||||
let item: ListItem = s.clone().into();
|
||||
assert_eq!(item.content, Text::from(s));
|
||||
assert_eq!(item.style, Style::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_span_into_list_item() {
|
||||
let s = Span::from("Test item");
|
||||
let item: ListItem = s.clone().into();
|
||||
assert_eq!(item.content, Text::from(s));
|
||||
assert_eq!(item.style, Style::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_lines_into_list_item() {
|
||||
let lines = vec![Line::raw("l1"), Line::raw("l2")];
|
||||
let item: ListItem = lines.clone().into();
|
||||
assert_eq!(item.content, Text::from(lines));
|
||||
assert_eq!(item.style, Style::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_item_style() {
|
||||
let item = ListItem::new("Test item").style(Style::default().bg(Color::Red));
|
||||
|
@ -897,7 +1009,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_list_does_not_render_in_small_space() {
|
||||
let items = list_items(vec!["Item 0", "Item 1", "Item 2"]);
|
||||
let items = vec!["Item 0", "Item 1", "Item 2"];
|
||||
let list = List::new(items.clone()).highlight_symbol(">>");
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
|
||||
|
@ -1137,6 +1249,21 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_items_setter() {
|
||||
let list = List::default().items(["Item 0", "Item 1", "Item 2"]);
|
||||
assert_buffer_eq!(
|
||||
render_widget(list, 10, 5),
|
||||
Buffer::with_lines(vec![
|
||||
"Item 0 ",
|
||||
"Item 1 ",
|
||||
"Item 2 ",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_with_empty_strings() {
|
||||
let items = list_items(vec!["Item 0", "", "", "Item 1", "Item 2"]);
|
||||
|
@ -1472,7 +1599,12 @@ mod tests {
|
|||
#[test]
|
||||
fn list_can_be_stylized() {
|
||||
assert_eq!(
|
||||
List::new(vec![]).black().on_white().bold().not_dim().style,
|
||||
List::new::<Vec<&str>>(vec![])
|
||||
.black()
|
||||
.on_white()
|
||||
.bold()
|
||||
.not_dim()
|
||||
.style,
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::White)
|
||||
|
|
|
@ -20,7 +20,7 @@ fn list_should_shows_the_length() {
|
|||
assert_eq!(list.len(), 3);
|
||||
assert!(!list.is_empty());
|
||||
|
||||
let empty_list = List::new(vec![]);
|
||||
let empty_list = List::default();
|
||||
assert_eq!(empty_list.len(), 0);
|
||||
assert!(empty_list.is_empty());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue