Viewport/ListBox scrolling inertia behaviour incorrect? (iOS/Mac)

Hi there,

While dragging to scroll (enough to cause inertia), touching a second time stops the list correctly, BUT if the touch is released, the inertia continues. This continuation behaviour is different to how native iOS scrolling works.

Just to clarify, if the user swipes to scroll through a list, they are then able to tap a second time to stop that list (this is done correctly in juce). When the tap is released, the list should remain unmoved, but in JUCE the list continues to move which feels pretty annoying.

Is there anyway I can cancel/interrupt scrolling while it is happening if/when a mouseUp() call occurs ?

Bump.

I’m having the same issue. Any workarounds?

I went ahead and modified the behaviour myself (see class below).
I’ll be honest with you, I forgot why it works but it does.

One feature that I remember having to add (on top of correcting the inertia) is that I check for the index of the mouse input source in order to support multi-touch. That is, if other fingers were dragging while, or have started dragging after, the component started dragging to scroll, then ignore them and scroll according to the specific finger that initiated the scroll to drag gesture.

Just instantiate a ViewportDragScroller object after your Viewport object and then whichever component you use to trigger the mouseDown() method you would call ViewportDragScroller::begin().

Compare my code to juce’s Viewport::DragToScrollListener class in juce_Viewport.cpp.
I’ll be curious to see if this fixes your issue too.

using ViewportDragAnimatedPosition =
    AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum>;

class ViewportDragScroller
: private ViewportDragAnimatedPosition::Listener
, private MouseListener
{
public:
    //==================================================================

    ViewportDragScroller(Viewport& v) noexcept
    : viewport{ v }
    {
        viewport.setScrollOnDragEnabled(false); // to allow this class to override the internal drag scroller
        offset_x.addListener (this);
        offset_y.addListener (this);
        offset_x.behaviour.setMinimumVelocity (60);
        offset_y.behaviour.setMinimumVelocity (60);
    }

    ~ViewportDragScroller() noexcept
    {
        Desktop::getInstance().removeGlobalMouseListener (this);
    }

    void begin(const MouseEvent& me) noexcept
    {
        if (! dragging_mouse_source_index) {
            
            dragging_mouse_source_index = me.source.getIndex();

            offset_x.setPosition(offset_x.getPosition());
            offset_y.setPosition(offset_y.getPosition());

            Desktop::getInstance().addGlobalMouseListener (this);
        }
    }
    
protected:
    //==================================================================
    
    void positionChanged (ViewportDragAnimatedPosition&, double)
    {
        const auto offset_pos = juce::Point<double>{
            offset_x.getPosition(), offset_y.getPosition()
        }.toInt();
        
        viewport.setViewPosition(original_view_pos - offset_pos);
    }

    void mouseDrag(const MouseEvent& me)
    {
        if (   dragging_mouse_source_index
            && *dragging_mouse_source_index == me.source.getIndex()) {

            const auto total_offset = me.getOffsetFromDragStart().toFloat();
            
            if (   ! is_dragging
                && total_offset.getDistanceFromOrigin() > 8.0f) {
                
                is_dragging = true;

                original_view_pos = viewport.getViewPosition();
                offset_x.setPosition (0.0);
                offset_x.beginDrag();
                offset_y.setPosition (0.0);
                offset_y.beginDrag();
            }

            if (is_dragging) {
                
                offset_x.drag (total_offset.x);
                offset_y.drag (total_offset.y);
            }
        }
    }

    void mouseUp(const MouseEvent& me)
    {
        if (   dragging_mouse_source_index
            && *dragging_mouse_source_index == me.source.getIndex()) {
            
            dragging_mouse_source_index = std::nullopt;

            if (is_dragging) {
                
                offset_x.endDrag();
                offset_y.endDrag();
                is_dragging = false;
            }
            
            Desktop::getInstance().removeGlobalMouseListener (this);
        }
    }

private:
    //==================================================================
    
    Viewport&
        viewport;
    
    ViewportDragAnimatedPosition
        offset_x,
        offset_y;
    
    juce::Point<int>
        original_view_pos;
    
    std::optional<uint_f8>
        dragging_mouse_source_index{};
    
    bool
        is_dragging{};

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ViewportDragScroller)
};

2 Likes

Oh, that’s brilliant, it works perfectly. Thanks a ton!

1 Like