tview icon indicating copy to clipboard operation
tview copied to clipboard

[Feature Request] ScrollView Widget/Primitive

Open ossenthusiast opened this issue 5 months ago • 12 comments

Add a new tview.Primitive that is a scrollable list of tview.Primitives.

ossenthusiast avatar Jul 20 '25 02:07 ossenthusiast

Do you still maintain this? @rivo

ossenthusiast avatar Jul 21 '25 17:07 ossenthusiast

Yes. Please be patient.

rivo avatar Jul 21 '25 21:07 rivo

Also, I'm not sure what you mean by "scrollable list of Primitives". You might want to elaborate. With examples, if possible.

rivo avatar Jul 21 '25 21:07 rivo

Also, I'm not sure what you mean by "scrollable list of Primitives". You might want to elaborate. With examples, if possible.

Sorry for the mention. I mean basically a scrollable horizontal layout (list) of widgets.

tview.NewScrollList().
    Append(tview.NewInputField()).
    Append(tview.NewBox()).
    Append(tview.NewFlex())

ossenthusiast avatar Jul 21 '25 21:07 ossenthusiast

Flex is basically that. But it's not scrollable. The question with these suggestions is: How would you control the scrolling? Let's say one of the contained elements is a TextArea and the user is currently entering text. Which keys would you assign to scrolling the Flex?

rivo avatar Aug 27 '25 15:08 rivo

Flex is basically that. But it's not scrollable. The question with these suggestions is: How would you control the scrolling? Let's say one of the contained elements is a TextArea and the user is currently entering text. Which keys would you assign to scrolling the Flex?

Thanks for the response! I am aware of Flex, but fundamentally, a flexbox is not meant to be scrollable. This is where ScrollView comes in. This is a very common requirement by a lot of TUI applications: you want to display a list of "messages" that the user can perform "actions" on (delete, edit, etc.). Regarding navigation, I would recommend following traditional vim-like keybindings for autocomplete menu:

Ctrl+P = Focus Previous Ctrl+N = Focus Next

ossenthusiast avatar Aug 27 '25 22:08 ossenthusiast

Alternatively, you could just expose two methods FocusPrevious and FocusNext, which the user can use to set an input capture callback themselves for navigation instead of handling defaults. Furthermore, if you don't like the above approach, you could also accept extra arguments in the constructor of ScrollView (NewScrollView) primitive: focusNextKey and focusPreviousKey, both are of type tcell.EventKey:

tview.NewScrollNew(tcell.NewEventKey(KeyUp, ...), tcell.NewEventKey(KeyDown, ...))

ossenthusiast avatar Aug 28 '25 01:08 ossenthusiast

Actually, the main problem is not so much the navigation itself — I'd argue that you can do this today by removing/adding items from a Flex as needed, resulting in a scroll-by-flex-row effect — but that, as mentioned many times in other issues, primitives don't propagate their optimal size upwards. In a chat application, there are some longer messages and some shorter ones. It is currently not possible to size a Flex's child primitive so it fits its contents optimally and there are no plans to introduce this. (This is quite a difficult problem to solve anyway.)

The question for such apps is also, why would you need to add any primitive to such a layout? Flex or Grid are primarily aimed at placing interactive controls on the screen, like input fields, checkboxes, buttons, text areas, etc. It strikes me that a chat application is mostly about formatting a long list of text messages, rather than placing interactive controls on screen. Instead of placing dozens or hundreds of small TextView elements into a Flex, it may make more sense to put them all into one TextView, a primitive which is specifically made to display large amounts of text (and it's scrollable, too). It's also flexible in terms of text size: If there is more text, it will expand to more lines automatically.

I get that at this point, formatting messages in a TextView for a chat/messages app may be difficult. But I would think that this is a problem that can be solved a lot more easily than inventing a new container primitive. (I seem to recall an issue asking for something like this but I cannot find it right now.)

rivo avatar Aug 28 '25 13:08 rivo

FYI, I added a very simple chat application which illustrates how you might deal with flexible paragraphs:

https://github.com/rivo/tview/tree/master/demos/textview/chat

rivo avatar Sep 04 '25 20:09 rivo

FYI, I added a very simple chat application which illustrates how you might deal with flexible paragraphs:

https://github.com/rivo/tview/tree/master/demos/textview/chat

Thanks! A common thing chat apps have is being able to select a "message" and perform certain actions on it (delete, edit, etc, navigate using keys, etc), how would you implement that while being performant? For example, I have used region IDs and highlights to mimic that behavior in my chat TUI for Matrix, but to update a single message, I would have to remove the message and then redraw everything to TextView, which is pretty slow. This has been a pretty major pain-point for me with tview. Looking forward to your thoughts on this!

ossenthusiast avatar Sep 04 '25 20:09 ossenthusiast

BTW, the chat demo uses the (*tview.TextView).GetRegions function, which does not exist.

ossenthusiast avatar Sep 04 '25 21:09 ossenthusiast

BTW, the chat demo uses the (*tview.TextView).GetRegions function, which does not exist.

It does for me: https://github.com/rivo/tview/blob/4cdaaa9bd6f6dcf226f43644d00f6b5703d69a8b/textview.go#L823

rivo avatar Sep 04 '25 22:09 rivo