Skip to content

Commit

Permalink
feat(widgets): Collect iterator of Line into Tabs
Browse files Browse the repository at this point in the history
A follow-up from ratatui#755,
allowing any iterator whose item is convertible into `Line` to be
collected into `Tabs`.

In addition, where previously `Tabs::new` required a `Vec`, it can now
accept any object that implements `IntoIterator` with an item type
implementing `Into<Line>`.

BREAKING CHANGE:

Calls to `Tabs::new()` whose argument is collected from an iterator
will no longer compile.  For example,
`Tabs::new(["a","b"].into_iter().collect())` will no longer compile,
because the return type of `.collect()` can no longer be inferred to
be a `Vec<_>`.
  • Loading branch information
Lunderberg committed Jan 11, 2024
1 parent f2eab71 commit ff7dca4
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 11 deletions.
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This is a quick summary of the sections below:
- Removed deprecated `Block::title_on_bottom`
- `Line` now has an extra `style` field which applies the style to the entire line
- `Block` style methods cannot be created in a const context
- `Tabs::new()` now accepts `IntoIterator<Item: Into<Line<'a>>>`
- [v0.25.0](#v0250)
- Removed `Axis::title_style` and `Buffer::set_background`
- `List::new()` now accepts `IntoIterator<Item = Into<ListItem<'a>>>`
Expand Down Expand Up @@ -46,6 +47,22 @@ This is a quick summary of the sections below:

## v0.26.0 (unreleased)

### `Tabs::new()` now accepts `IntoIterator<Item: Into<Line<'a>>>` ([#776])

[#776]: https://github.com/ratatui-org/ratatui/pull/776

Previously, `Tabs::new()` accepted `Vec<T>` where `T: Into<Line<'a>>`. This allows more flexible
types from calling scopes, though it can break type inference when the calling scope.

This typically occurs when collecting an iterator prior to calling `Tabs::new`, and can be resolved
by removing the call to `.collect()`.

```diff
- let table = Tabs::new((0.3).map(|i| format!("{i}")).collect());
// becomes
+ let table = Tabs::new((0.3).map(|i| format!("{i}")));
```

### Table::default() now sets segment_size to None and column_spacing to ([#751])

[#751]: https://github.com/ratatui-org/ratatui/pull/751
Expand Down
5 changes: 2 additions & 3 deletions examples/demo/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ use crate::app::App;

pub fn draw(f: &mut Frame, app: &mut App) {
let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(f.size());
let titles = app
let tabs = app
.tabs
.titles
.iter()
.map(|t| text::Line::from(Span::styled(*t, Style::default().fg(Color::Green))))
.collect();
let tabs = Tabs::new(titles)
.collect::<Tabs>()
.block(Block::default().borders(Borders::ALL).title(app.title))
.highlight_style(Style::default().fg(Color::Yellow))
.select(app.tabs.index);
Expand Down
5 changes: 2 additions & 3 deletions examples/tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,14 @@ fn ui(f: &mut Frame, app: &App) {

let block = Block::default().on_white().black();
f.render_widget(block, area);
let titles = app
let tabs = app
.titles
.iter()
.map(|t| {
let (first, rest) = t.split_at(1);
Line::from(vec![first.yellow(), rest.green()])
})
.collect();
let tabs = Tabs::new(titles)
.collect::<Tabs>()
.block(Block::default().borders(Borders::ALL).title("Tabs"))
.select(app.index)
.style(Style::default().cyan().on_gray())
Expand Down
43 changes: 41 additions & 2 deletions src/widgets/tabs.rs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ const DEFAULT_HIGHLIGHT_STYLE: Style = Style::new().add_modifier(Modifier::REVER
/// .divider(symbols::DOT)
/// .padding("->", "<-");
/// ```
///
/// In addition to `Tabs::new`, any iterator whose element is convertible to `Line` can be collected
/// into `Tabs`.
///
/// ```
/// use ratatui::widgets::Tabs;
///
/// (0..5).map(|i| format!("Tab{i}")).collect::<Tabs>();
/// ```
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Tabs<'a> {
/// A block to wrap this widget in if necessary
Expand Down Expand Up @@ -79,9 +88,10 @@ impl<'a> Tabs<'a> {
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1".red(), "Tab 2".blue()]);
/// ```
pub fn new<T>(titles: Vec<T>) -> Tabs<'a>
pub fn new<Iter>(titles: Iter) -> Tabs<'a>
where
T: Into<Line<'a>>,
Iter: IntoIterator,
Iter::Item: Into<Line<'a>>,
{
Tabs {
block: None,
Expand Down Expand Up @@ -306,6 +316,15 @@ impl<'a> Widget for Tabs<'a> {
}
}

impl<'a, Item> FromIterator<Item> for Tabs<'a>
where
Item: Into<Line<'a>>,
{
fn from_iter<Iter: IntoIterator<Item = Item>>(iter: Iter) -> Self {
Self::new(iter)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -335,6 +354,26 @@ mod tests {
);
}

#[test]
fn new_from_vec_of_str() {
Tabs::new(vec!["a", "b"]);
}

#[test]
fn collect() {
let tabs: Tabs = (0..5).map(|i| format!("Tab{i}")).collect();
assert_eq!(
tabs.titles,
vec![
Line::from("Tab0"),
Line::from("Tab1"),
Line::from("Tab2"),
Line::from("Tab3"),
Line::from("Tab4"),
],
);
}

fn render(tabs: Tabs, area: Rect) -> Buffer {
let mut buffer = Buffer::empty(area);
tabs.render(area, &mut buffer);
Expand Down
5 changes: 2 additions & 3 deletions tests/widgets_tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use ratatui::{
layout::Rect,
style::{Style, Stylize},
symbols,
text::Line,
widgets::Tabs,
Terminal,
};
Expand All @@ -15,7 +14,7 @@ fn widgets_tabs_should_not_panic_on_narrow_areas() {
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let tabs = Tabs::new(["Tab1", "Tab2"].iter().cloned().map(Line::from).collect());
let tabs = Tabs::new(["Tab1", "Tab2"]);
f.render_widget(
tabs,
Rect {
Expand All @@ -37,7 +36,7 @@ fn widgets_tabs_should_truncate_the_last_item() {
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let tabs = Tabs::new(["Tab1", "Tab2"].iter().cloned().map(Line::from).collect());
let tabs = Tabs::new(["Tab1", "Tab2"]);
f.render_widget(
tabs,
Rect {
Expand Down

0 comments on commit ff7dca4

Please sign in to comment.