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 ?
aamf
August 23, 2021, 7:05am
2
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)
};
3 Likes
aamf
August 23, 2021, 12:36pm
4
Oh, that’s brilliant, it works perfectly. Thanks a ton!
1 Like
+1 Thank you for this, helped improve my ListBox behaviour on iOS.
I updated it to the latest JUCE, fixed some warnings and converted to JUCE style.
#pragma once
using ViewportDragAnimatedPosition =
juce::AnimatedPosition<juce::AnimatedPositionBehaviours::ContinuousWithMomentum>;
class ViewportDragScroller
: private ViewportDragAnimatedPosition::Listener,
private juce::MouseListener
{
public:
//==================================================================
explicit ViewportDragScroller (juce::Viewport& v) noexcept
: viewport { v }
{
viewport.setScrollOnDragMode (juce::Viewport::ScrollOnDragMode::never); // to allow this class to override the internal drag scroller
offsetX.addListener (this);
offsetY.addListener (this);
offsetX.behaviour.setMinimumVelocity (60);
offsetY.behaviour.setMinimumVelocity (60);
}
~ViewportDragScroller() noexcept override
{
juce::Desktop::getInstance().removeGlobalMouseListener (this);
}
void begin (const juce::MouseEvent& me) noexcept
{
if (! draggingMouseSourceIndex)
{
draggingMouseSourceIndex = me.source.getIndex();
offsetX.setPosition (offsetX.getPosition());
offsetY.setPosition (offsetY.getPosition());
juce::Desktop::getInstance().addGlobalMouseListener (this);
}
}
protected:
//==================================================================
void positionChanged (ViewportDragAnimatedPosition&, double) override
{
const auto offsetPos = juce::Point<double> { offsetX.getPosition(), offsetY.getPosition() }.toInt();
viewport.setViewPosition (originalViewPos - offsetPos);
}
void mouseDrag (const juce::MouseEvent& me) override
{
if (draggingMouseSourceIndex && *draggingMouseSourceIndex == me.source.getIndex())
{
const auto totalOffset = me.getOffsetFromDragStart().toFloat();
if (! isDragging && totalOffset.getDistanceFromOrigin() > 8.0f)
{
isDragging = true;
originalViewPos = viewport.getViewPosition();
offsetX.setPosition (0.0);
offsetX.beginDrag();
offsetY.setPosition (0.0);
offsetY.beginDrag();
}
if (isDragging)
{
offsetX.drag (totalOffset.x);
offsetY.drag (totalOffset.y);
}
}
}
void mouseUp (const juce::MouseEvent& me) override
{
if (draggingMouseSourceIndex
&& *draggingMouseSourceIndex == me.source.getIndex())
{
draggingMouseSourceIndex = std::nullopt;
if (isDragging)
{
offsetX.endDrag();
offsetY.endDrag();
isDragging = false;
}
juce::Desktop::getInstance().removeGlobalMouseListener (this);
}
}
private:
//==================================================================
bool isDragging {};
juce::Viewport& viewport;
ViewportDragAnimatedPosition offsetX, offsetY;
juce::Point<int> originalViewPos;
std::optional<int> draggingMouseSourceIndex {};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ViewportDragScroller)
};
In addition, in order to prevent unwanted selection events when the user is dragging, I added the following to my ListBox item component:
void mouseDown (const juce::MouseEvent& mouseEvent) override
{
#if JUCE_IOS
viewportDragScroller.begin (mouseEvent);
// Wait to see if this is a drag event to prevent selecting while dragging
juce::Timer::callAfterDelay (80, [mouseEvent, this]
{
#endif
if (mouseIsDragging) return;
if (mouseEvent.mods.isPopupMenu())
{
// handle right click
}
// handle click event
#if JUCE_IOS
});
#endif
}
void mouseUp (const juce::MouseEvent& /* mouseEvent */) override
{
mouseIsDragging = false;
}
void mouseDrag (const juce::MouseEvent& /* mouseEvent */) override
{
mouseIsDragging = true;
}
private:
ViewportDragScroller& viewportDragScroller;
bool mouseIsDragging = false;
// ...
3 Likes