Touch-scroll a ListBox

If I have a ListBox on a touch-screen device, is there a way to allow the user to use touch to scroll the list? In this case, my software is running on a Surface Pro, and many UI elements work great for touch - just make the buttons a little bigger, etc. But the ListBox isn’t touch-friendly. Is there a way to enable touch-based scrolling?

Does the ListBox allow for touch scrolling on mobile OS’s?

Alternatively, if I have to implement this myself does anyone have some tips for how to get started?

You can call Viewport::setScrollOnDragEnabled on the Listbox’s viewport

1 Like

OK, so that works, except…
After calling that method, dragging to scroll works using my finger on the Surface Pro’s screen - good.
But, the instant my finger touches the screen, I get a selectedRowsChanged() callback on my ListBoxModel. This means scrolling and selecting an item are not independent. Think of a mobile app with a list, say a contact list on your phone. If you swipe and drag the list scrolls - you don’t select the person you started dragging on. You only select a person if you tap and release without dragging.

Is there a way to get that behavior? It’s so close: if I call setRowSelectedOnMouseDown(false) on the ListBox, then I can touch and drag to scroll, but I still get a selection on mouse up. I haven’t found a way to disable selection if the user is scrolling. Any tips for that?

1 Like

Jules, any ideas? SInce the ListBox manages selection, I don’t know if I can work around this without modifying ListBox, which I’d rather not dig into.

Maybe I could check to see if the scroll amount has changed between mouse down and mouse up, and revert to the previous selection in that case? But I’d rather the ListBox made that check, and didn’t trigger a selection change at all in that case.

Hello, can of worms. We did something similar where a user can drag the content of a viewport by holding down the mouse button, but clicks were forwarded to the things inside the viewport. (that was not using JUCE Components and Viewports, so I can’t really help you with the ListBox)

An implementation will roughly work like this:

When the user taps:

  1. mouseDown: we don’t know for sure if this is a tap. So things like buttons can’t fire their actions yet.
  2. mouseUp: We now know this is a tap, so now we can run any click actions on child components.

When the user drags:

  1. mouseDown: As before nothing happens yet.
  2. mouseDrag: Now (usually) the viewport may scroll around.
  3. mouseUp: end of scroll. No click actions will be run
  4. Not sure how this looks to child components. Here are some observations from native Android apps:
    1. Buttons or list items see a mouseDown (you can observe the feedback), but the click somehow gets cancelled.
    2. Sliders can fully handle the drag and the viewport will not scroll

As a consequence you cannot have components that fire their actions on mouseDown.


Roeland

Yes, exactly. The ListBox component can almost get there to the proper drag behavior by combining calls to setScrollOnDragEnabled(true) and setRowSelectedOnMouseDown(false). But you still get the click action on mouse up, even though the user has scrolled.

I’m hoping that there’s a way to make scrolling and tapping truly independent. Conceptually, it’s not that hard - you described it step-by-step. But I’m not sure Juce components can do this without modification.

Hi @lostmarble1 ,

We recently ran into the same problem, do you manage to fix it?

Currently, we use MouseEvent from listBoxItemClicked() and check if the distance of drag is not zero, it true, then don’t do click operation.

Code looks like below:

void listBoxItemClicked(int row, const MouseEvent& e) override
{
	const auto distance_drag = e.getDistanceFromDragStart();
	if (distance_drag > 0)
		return;
}

But still, I do think drag and tap should be separated.

Interesting solution. I did something different. In my ListBoxModel class I have a boolean value called fDragScrolling, initialized to false. Then I implemented this:

void listBoxItemClicked(int rowNumber, const MouseEvent &)
{
    if (fDragScrolling)
    {
        fDragScrolling = false;
        return;
    }

    // stuff that should happen when an item is clicked
}
void listWasScrolled()
{
    if (fListBox->getViewport()->isCurrentlyScrollingOnDrag())
    {
        fDragScrolling = true;
    }
}

The idea is that is cancels a click if a scroll happens. Same thing as your solution, just a little different method.

So that works nicely for some of my ListBoxes. But I have another one that you can use a mouse to drag-reorder items in it. I can’t figure out a way to allow touch events to scroll that ListBox, as well as reorder items in it.

On an iPad, maybe touch and drag would scroll, while touch and hod for a second would switch into item reordering mode. But JUCE doesn’t seem to have a concept of that in its ListBoxes. I may have to implement my own kind of ListBox to properly handle touch interaction - it doesn’t seem to be a priority for JUCE. Even this Viewport::setScrollOnDragEnabled method doesn’t really work right without some other workarounds by us.

2 Likes

I have a similar issue with a ViewPort containing a bunch of buttons. The solution I tried initially consisted in writing my own button class derived from Button and override a few methods:

void mouseDown(const MouseEvent& event) override
{
    mouseIsDown = true;
}

void mouseUp(const MouseEvent& event) override
{
    if (!mouseIsDown) return;
    onClickCallback();
    mouseIsDown = false;
    dragCount = 0;
}

void mouseDrag(const MouseEvent& event) override
{
    if (++dragCount > 5) 
    {
        mouseIsDown = false;
        dragCount = 0;
    }
}

This used to work pretty well, but it all depends on how fast you start dragging after putting your finger on the screen, that number 5 should be adjusted accordingly, or maybe a better implementation should be made with a timer.

In the end I decided to trigger the buttons with a double tap, which is not a solutionl, but given the purpose of the application (home automation control), it’s a safer method for me.