As opposed to requiring each “row” to inset itself, which (to me) seems like redundant work in a multi-height row context.
Add support for horizontal layouts
The importance of using the ListBox is that it “streams” rows in, which means not having to use a Viewport which a great many Component instances so as to properly scroll around.
We’ve been rewriting lists into bastardised TreeViews when we want varying row heights. Would be great to get this natively in ListBox, and presumably it’s not super hard. Just needs a bit of thought so it’s fast enough when you’ve got 50,000 rows.
In fact there’s only a few operations needed:
Set height of row component needs to know the row height
Get the Y position for a given row
Get the row number of a given Y position
The only problem is that the best interface for this would be something like
ListBoxModel::getRowHeight(int rowNumber);
But you’d have to call it a lot to find out anything about say row 50,000. If it’s quick maybe that won’t matter?
More efficient in many cases would be to delegate the queries for getting the Y position for a row and the row number for a Y position to the ListBoxModel, as it’s likely that in many cases it will have a much faster way of getting to the right answer. E.g. my header row is big and all my other rows are the same size.
So it could provide both options by adding to ListBoxModel:
virtual int getRowHeight(int rowNumber) { return rowHeight; }
virtual int getRowForPosition(int pixelPosition) { /* O(n) implementation */; }
virtual int getPositionForRow(int rowNumber) { /* O(n) implementation */; }
And armed with those functions I think the actual changes to ListBox don’t seem so hard, and it’s backwards compatible.
Having got half way through adding variable height row support to ListBox there’s a small problem.
The function getNumRowsOnScreen() can no longer fulfill the description, which says: This is the number of whole rows which will fit on-screen, so the value might be more than the actual number of rows in the list.
But as we don’t know the height of rows that are outside of the number of rows in the list it can’t really do anything useful.
I’m just fixing the component allocation now. That bit will be ok - it’s internal. Sadly the method is public so it’s difficult to know what it’s been used for.
A few problems to solve still but it’s looking pretty good. Why does it not recreate components all the time - presumably that’s slow? So we should just keep the maximum number of components around we need (as the number varies during scrolling)…?
Also viewport.setSingleStepSize(…) which used to be set to the rowHeight makes no sense either anymore…
Update
Ok - so I’ve got a pretty tidy and partially tested version of ListBox that now supports variable height rows. I’ll have a bit more of a play tomorrow and post it up. Seems fast up to about 100,000 rows in debug and if it gets much more than that the O(n) algorithm for calculating the row position needs to be overriden by the user. Maybe there’s an improvement for this but I don’t know if it’s worth it. I’ll add something to optimise it for the original fixed height row behaviour though.
The other thing that’d be nice would be rows that can’t be selected, so you can use some of the variable height ListBox rows as headers…
Ah yeah - cool. Same sort of thing, only I think I’ve done the pageup/pagedown handling and a few efficiency things so it can be made to work with a million rows ok (yes we do sometimes have a listbox that long…!).
Interesting, I’ve tried Page Up / Down on native macOS UI and noticed there’s not such thing. so I didn’t spend time there.
Optimizations - yeah. I didn’t have the need for such longs lists :).
I did it a while ago so I haven’t looked for it quite a while. but it does get length until row N in O(1) if I remember correctly.
Insertion/Deletion though is O(N).
Ah yeah - I see what you’ve done now. Created a component height or position cache in updateContents which should be run less often. That might be a better idea really. It doesn’t take that long to do it for a million items, it’s just a bit ugly if you try scrolling around and it’s trying to repeat that exercise constantly.
To be fair, I wouldn’t use a grid to demonstrate. I need to give this some more thought but I’m pretty swamped right now.
Preferably, I can pool together some example lists that aren’t flat, boring, and restrictive like juce::ListBox as it stands.
The thing about the list box that I’d expect is the ability to freely stream items in and out, but with variable heights, with optional spacing between items, with focus handling in mind, and even adding and controlling over-pull animations and gravity/acceleration/deceleration to the list’s scroll.
Preferably, with that, your components would get notifications for when they’re being created/streamed in and when they’re getting streamed out (ie: for caching’s sake if scrolling up/down quickly, and for animation support).
If you visualise the various list type structures beyond just vanilla text lists, such as:
comments and replies
Like Teams, Slack, FB, Twitter
multi-item rows with posters
Netflix, Amazon Prime, Dinsey+, HBO Max
EPG navigations
the pattern presents itself fairly obviously. This is why Android provides a ListView and iOS its UITableView, likewise with React Native and its various list views.
There’s not much point in me going into details in all of their design strategies. Suffice it to say that sometimes I feel like just abandoning JUCE for the UI because its components are lifeless and oversimplified.
My goal with this FR is to pull out some of the smaller bits of what I mentioned to get started - warm things up a bit.
Also, having spent Sunday mulling over ListBox I don’t think it’d be very hard to add spacing between rows. In fact, having added a function that returns the Y position of each row I guess it’s completely trivial!
I would really love to use this feature in a TableListBox and made a custom class that inherits from @ttg s TableList. So I moved the static getListRowAccessibilityActions(RowComponentType& rowComponent) to the ListBox header, added a getRow() and getOwner() class to the TableListBox::RowComp class and everything compiles, but now I’m a bit stuck on how to actually make the row height variable. The height of the whole row should be decided by the row’s highest cell, so I guess I’d have to iterate over all columns before they are drawn and get what their height would be? Is that even possible? Any tips on what to touch here?