FRs: ListBox Improvements

  • Add support for multi-height rows.
  • Add support for spacing between rows.
    • 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.

Have my upvote.
I can’t remember the amount of times I had to rewrite everything from scratch because of the fixed row heights.

1 Like

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.

It could return the maximum number of rows, so enough components are allocated. Wouldn’t that help?

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…

1 Like

Source.zip (13.8 KB)

Here is an improved ListBox with variable height rows.

It adds the functions to ListBoxModel described above and deprecates getRowHeight and setRowHeight functions.

getNumRowsOnScreen() is still confusing.

3 Likes

I had the need for such a while ago and started making this:

There’s also a demo:

1 Like

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).

Well hopefully we can get something folded into the main JUCE thing on this!

2 Likes

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.

@jimc @ttg What are your thoughts on adding spacing support between rows?

I should preface that this is a useful design in plenty of other contexts outside of audio apps and plugins.

How I see it, there are two options:

  • User wraps row component in a higher level component, and manually insets it.
    • With your new code, I guess this means the desired size is that of the row + the bounds expanded by the margin.
  • The other option I see is to add some kind of spacing cache and maths as part of the ListBox itself.
    • Of course, the default spacing would be 0.

What are your thoughts on adding spacing support between rows?

To make sure we’re on the same page. you mean you’d like to have something like a ‘border’ per row?
Is this might change between rows?

Would be nice if you can provide an example in css-grid:

(even in juce::Grid JUCE code :slight_smile: ) to explain your vision to perfection. :slight_smile:

1 Like

To be fair, I wouldn’t use a grid to demonstrate. :slight_smile: 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.

2 Likes

Yeah, the JUCE ListBox hasn’t acquired any more functionality since Jules wrote it in 1978. :slight_smile:

4 Likes

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?