Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List: Create a WidgetList where each item in the list is a Widget rather than Text #132

Open
joshka opened this issue Apr 12, 2023 · 14 comments
Labels
Effort: Difficult This will take a lot of effort to change / fix Status: Design Needed An issue that isn't yet designed or specificied well enough to implement Status: On Hold Not actively being worked on for now due to time / other constraints Status: Workaround Available Type: Enhancement New feature or request

Comments

@joshka
Copy link
Member

joshka commented Apr 12, 2023

Problem

Sometimes the layout options available in providing a List with Text are not quite enough.

  • One example is wrapping within the list items List: Add wrap for ListItem #128
  • Another example is where I wanted to create list items that had a title, border, etc.

Solution

Create widgets::WidgetList which has WidgetListItems as a parallel to the current List widget. The algorithm for deciding what is rendered will be similar to the current list (effectively a viewport that contains the currently selected Text item, scrolled into view if necessary, and that only renders the items in the current view)

  • Backward compatibility: NA
  • Ease of use: widgets::WidgetList might be a slightly confusing name
  • consistency: seems appropropriate

Alternatives

  1. Perhaps any places where we accept Text (or even Spans) should be migrated to accept a widget instead. Does it make sense to set the title of a block to a widget perhaps? What about the highlight character of a list? Should tables contain widgets instead of text? Pros: really flexible. Cons: really big change with some difficult backwards compatibility constraints.

Additional context

My particular use case for this is for https://github.com/joshka/tooters where I want to be able to do some neat things like show images using a widget wrapped around https://docs.rs/viuer/latest/viuer/ and make URLs clickable by wrapping them in OSC 8 escape sequences (as a UrlWidget, not just as text).

asciicast

@joshka joshka added the Type: Enhancement New feature or request label Apr 12, 2023
@karthago1
Copy link
Contributor

Hello, I hope I can help implementing this feature. I am working on it

@mindoodoo
Copy link
Member

Alright, keep us posted !

@joshka
Copy link
Member Author

joshka commented Apr 27, 2023

Sweet - looking forward to seeing how you go with this @karthago1. It's been on my todo list for a while, but hasn't been the top item.

Happy to look on as you develop this. If you want to do a work in progress PR for feedback, that can works well.

Given there's a few different ways this might be implemented, perhaps it would be worth fleshing out a plan in this issue before going at it?

@karthago1
Copy link
Contributor

@joshka I have created a draft PR #155. maybe it can describe the idea better.

I included a trait SizeHint which is required to be implemented by every widget, which should be supported by the WidgetList.

for the moment the WidgetList supports only Paragraph, which enable implementing the application in upper video.

Todo: except of organizing the current Code, I need some time to check other UI libraries how they are solving a similar issue. But I think without knowing the size before rendering it will be hard to implement it. (hence something similar to the SizeHint tait may be necessary)

Feel free to suggest improvements or other ideas.

@preiter93
Copy link

@karthago1 I've just implemented something similar, maybe it is of some help https://github.com/preiter93/tui-widget-list :)

It would be nice to see it in the ratatui library. Did you think about how to i) highlight the selected item and ii) support a list of stateful widgets?

@joshka
Copy link
Member Author

joshka commented May 1, 2023

Neat - I definitely think we could do with a Selectable trait in the main library to make it so that we can render widgets differently based on whether they are selected or not (useful for Lists, and perhaps other things like tabs or perhaps blocks or other widgets in a layout.

I'm wondering whether it's possible that this could be folded into the normal list rather than making a new widgetlist, where the existing behavior of a list that shows text is just a List with a Paragraph widget.

@karthago1
Copy link
Contributor

karthago1 commented May 1, 2023

@preiter93 thanks I will take a look. maybe you can also review changes and suggest improvements for #155.

@joshka why we don't leave it to the user, what to draw? in the widget_list example in #155 the user can change the style, widget looking or even the widget type if he is using the ListWidgetItem

here is a video
asciicast

I'm wondering whether it's possible that this could be folded into the normal list rather than making a new widgetlist, where the existing behavior of a list that shows text is just a List with a Paragraph widget.

I think we need different Widget if you want to stay backwards compatible. But we can implement the List based on ẀidgetList`.

There is only 1 missing feature I need to implement in #155. A Shrink and Fill feature for every WidgetListItem.

@ferologics
Copy link

Any chance this lands in prod soonish? wanted to have a multiline list item (#128). Is it possible to depend on changes in #217 and put Paragraphs as list items?

@orhun
Copy link
Member

orhun commented Jun 16, 2023

We might start planning a release for including the new features if there is a need towards that direction. But before that, I think @joshka needs to finish the implementation.

@ferologics
Copy link

I tried the changes in #217 but couldn't get the text list item to draw dynamically based on text size, maybe I did something wrong. Used ListItem::new(WidgetWrapper::from(Text::from(s))). Setting a fixed width/height puts some of the text on new lines, but I'd like it to self size.

@joshka
Copy link
Member Author

joshka commented Jun 17, 2023

Yeah #217 was mostly a PoC that didn't yet implement sizing (I haven't looked at it for a few weeks now):

This doesn't implement the widget sizing in #155 however (this is currently manual and TODO).

I'm not entirely sure what makes sense in terms of autosizing contents. I came across a neat library the other day that could be worth exploring for that part generally across ratatui: https://crates.io/crates/taffy

The issue of sizing Text is that wrapping isn't part of the text struct, it's part of the paragraph right now. So we're waiting on some of the resolution of #242 for that part.

The main blocker though on the widget using the #217 implementation is #122. That change allows Widget::render() to borrow widgets. I intentionally wanted to leave that PR open for a little while to get some feedback as well as for myself to get a bit more experience with ratatui before making such a big change.

From a perspective of unblocking yourself:
I'd suggest perhaps grabbing the changes in #155 and using those in your app. @karthago1's code is more complete than mine for this, but I much prefer the idea of having a single List Widget just accepting Widgets as items rather than having two implementations that we have to maintain.

@joshka joshka changed the title Create a WidgetList where each item in the list is a Widget rather than Text List: Create a WidgetList where each item in the list is a Widget rather than Text Sep 28, 2023
joshka added a commit that referenced this issue Jan 20, 2024
Many widgets can be rendered without changing their state.

This commit implements The `Widget` trait for various 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(&paragraph, left);
frame.render_widget(&paragraph, right); // we can reuse the widget
```

- Clear
- Block
- Tabs
- Sparkline
- Paragraph
- Gauge
- Calendar

Other widgets will be implemented in follow up commits.

Fixes: #164
Replaces PRs: #122 and
  #16
Enables: #132
Validated as a viable working solution by: #836
joshka added a commit that referenced this issue Jan 24, 2024
Many widgets can be rendered without changing their state.

This commit implements The `Widget` trait for various 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(&paragraph, left);
frame.render_widget(&paragraph, right); // we can reuse the widget
```

- Clear
- Block
- Tabs
- Sparkline
- Paragraph
- Gauge
- Calendar

Other widgets will be implemented in follow up commits.

Fixes: #164
Replaces PRs: #122 and
  #16
Enables: #132
Validated as a viable working solution by: #836
joshka added a commit that referenced this issue Jan 24, 2024
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(&paragraph, left);
frame.render_widget(&paragraph, 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: #164
Replaces PRs: #122 and
#16
Enables: #132
Validated as a viable working solution by:
#836
@Nannk
Copy link

Nannk commented Feb 27, 2024

This feature would be really helpful in my specific case.
Are there any updates on PR progress?

@joshka
Copy link
Member Author

joshka commented Feb 27, 2024

There haven't been, but the recent experimental changes in 0.26.0 to make WidgetRef and StatefulWidgetRef traits might make this possible to progress on. As an intermediate step, I think making the traits have an optional size_hint function similar to std::iter::Iterator::size_hint would be a good thing to design. We would want that to make it possible to easily allocate enough space for the widget items. The problem there isn't so much the code, but working out what the requirements for method that are (as 2 dimensions brings more difficulty in working out min/max values needed)

@joshka
Copy link
Member Author

joshka commented Aug 11, 2024

See tui-widget-list for one approach to implementing this (a list that accepts a closure that returns a (widget, height) tuple, the widget is a single widget, not a generic one, but this seems like it would be ok)

@joshka joshka added Status: On Hold Not actively being worked on for now due to time / other constraints Status: Workaround Available Status: Design Needed An issue that isn't yet designed or specificied well enough to implement labels Aug 11, 2024
@joshka joshka added the Effort: Difficult This will take a lot of effort to change / fix label Aug 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Effort: Difficult This will take a lot of effort to change / fix Status: Design Needed An issue that isn't yet designed or specificied well enough to implement Status: On Hold Not actively being worked on for now due to time / other constraints Status: Workaround Available Type: Enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants