Browse Source

Viewport: Improve drag-to-scroll on devices that can accept simultaneous mouse and touch input

Some Windows 11 devices have both touch screens and mouse inputs, and
these can be used simultaneously.

The Viewport (and ListBox) now check the input source of each mouse
down. If the source is not a mouse, the viewport will always enter
drag-to-scroll mode, regardless of the result of isScrollOnDragEnabled.
v6.1.6
reuk 3 years ago
parent
commit
4cf74dfff6
4 changed files with 158 additions and 138 deletions
  1. +0
    -1
      examples/Utilities/InAppPurchasesDemo.h
  2. +129
    -124
      modules/juce_gui_basics/layout/juce_Viewport.cpp
  3. +28
    -4
      modules/juce_gui_basics/layout/juce_Viewport.h
  4. +1
    -9
      modules/juce_gui_basics/widgets/juce_ListBox.cpp

+ 0
- 1
examples/Utilities/InAppPurchasesDemo.h View File

@@ -499,7 +499,6 @@ public:
voiceListBox.setRowHeight (66);
voiceListBox.selectRow (0);
voiceListBox.updateContent();
voiceListBox.getViewport()->setScrollOnDragEnabled (true);
addAndMakeVisible (phraseLabel);
addAndMakeVisible (phraseListBox);


+ 129
- 124
modules/juce_gui_basics/layout/juce_Viewport.cpp View File

@@ -26,7 +26,132 @@
namespace juce
{
Viewport::Viewport (const String& name) : Component (name)
static bool viewportWouldScrollOnEvent (const Viewport* vp, const MouseInputSource& src) noexcept
{
if (vp != nullptr)
{
switch (vp->getScrollOnDragMode())
{
case Viewport::ScrollOnDragMode::all: return true;
case Viewport::ScrollOnDragMode::nonHover: return ! src.canHover();
case Viewport::ScrollOnDragMode::never: return false;
}
}
return false;
}
using ViewportDragPosition = AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum>;
struct Viewport::DragToScrollListener : private MouseListener,
private ViewportDragPosition::Listener
{
DragToScrollListener (Viewport& v) : viewport (v)
{
viewport.contentHolder.addMouseListener (this, true);
offsetX.addListener (this);
offsetY.addListener (this);
offsetX.behaviour.setMinimumVelocity (60);
offsetY.behaviour.setMinimumVelocity (60);
}
~DragToScrollListener() override
{
viewport.contentHolder.removeMouseListener (this);
Desktop::getInstance().removeGlobalMouseListener (this);
}
void positionChanged (ViewportDragPosition&, double) override
{
viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(),
(int) offsetY.getPosition()));
}
void mouseDown (const MouseEvent& e) override
{
if (! isGlobalMouseListener && viewportWouldScrollOnEvent (&viewport, e.source))
{
offsetX.setPosition (offsetX.getPosition());
offsetY.setPosition (offsetY.getPosition());
// switch to a global mouse listener so we still receive mouseUp events
// if the original event component is deleted
viewport.contentHolder.removeMouseListener (this);
Desktop::getInstance().addGlobalMouseListener (this);
isGlobalMouseListener = true;
scrollSource = e.source;
}
}
void mouseDrag (const MouseEvent& e) override
{
if (e.source == scrollSource
&& ! doesMouseEventComponentBlockViewportDrag (e.eventComponent))
{
auto totalOffset = e.getOffsetFromDragStart().toFloat();
if (! isDragging && totalOffset.getDistanceFromOrigin() > 8.0f && viewportWouldScrollOnEvent (&viewport, e.source))
{
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 MouseEvent& e) override
{
if (isGlobalMouseListener && e.source == scrollSource)
endDragAndClearGlobalMouseListener();
}
void endDragAndClearGlobalMouseListener()
{
offsetX.endDrag();
offsetY.endDrag();
isDragging = false;
viewport.contentHolder.addMouseListener (this, true);
Desktop::getInstance().removeGlobalMouseListener (this);
isGlobalMouseListener = false;
}
bool doesMouseEventComponentBlockViewportDrag (const Component* eventComp)
{
for (auto c = eventComp; c != nullptr && c != &viewport; c = c->getParentComponent())
if (c->getViewportIgnoreDragFlag())
return true;
return false;
}
Viewport& viewport;
ViewportDragPosition offsetX, offsetY;
Point<int> originalViewPos;
MouseInputSource scrollSource = Desktop::getInstance().getMainMouseSource();
bool isDragging = false;
bool isGlobalMouseListener = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragToScrollListener)
};
//==============================================================================
Viewport::Viewport (const String& name)
: Component (name),
dragToScrollListener (std::make_unique<DragToScrollListener> (*this))
{
// content holder is used to clip the contents so they don't overlap the scrollbars
addAndMakeVisible (contentHolder);
@@ -36,14 +161,12 @@ Viewport::Viewport (const String& name) : Component (name)
setInterceptsMouseClicks (false, true);
setWantsKeyboardFocus (true);
setScrollOnDragEnabled (Desktop::getInstance().getMainMouseSource().isTouch());
recreateScrollbars();
}
Viewport::~Viewport()
{
setScrollOnDragEnabled (false);
deleteOrRemoveContentComp();
}
@@ -196,132 +319,14 @@ void Viewport::componentMovedOrResized (Component&, bool, bool)
}
//==============================================================================
typedef AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum> ViewportDragPosition;
struct Viewport::DragToScrollListener : private MouseListener,
private ViewportDragPosition::Listener
{
DragToScrollListener (Viewport& v) : viewport (v)
{
viewport.contentHolder.addMouseListener (this, true);
offsetX.addListener (this);
offsetY.addListener (this);
offsetX.behaviour.setMinimumVelocity (60);
offsetY.behaviour.setMinimumVelocity (60);
}
~DragToScrollListener() override
{
viewport.contentHolder.removeMouseListener (this);
Desktop::getInstance().removeGlobalMouseListener (this);
}
void positionChanged (ViewportDragPosition&, double) override
{
viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(),
(int) offsetY.getPosition()));
}
void mouseDown (const MouseEvent& e) override
{
if (! isGlobalMouseListener)
{
offsetX.setPosition (offsetX.getPosition());
offsetY.setPosition (offsetY.getPosition());
// switch to a global mouse listener so we still receive mouseUp events
// if the original event component is deleted
viewport.contentHolder.removeMouseListener (this);
Desktop::getInstance().addGlobalMouseListener (this);
isGlobalMouseListener = true;
scrollSource = e.source;
}
}
void mouseDrag (const MouseEvent& e) override
{
if (e.source == scrollSource
&& ! doesMouseEventComponentBlockViewportDrag (e.eventComponent))
{
auto totalOffset = e.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 MouseEvent& e) override
{
if (isGlobalMouseListener && e.source == scrollSource)
endDragAndClearGlobalMouseListener();
}
void endDragAndClearGlobalMouseListener()
{
offsetX.endDrag();
offsetY.endDrag();
isDragging = false;
viewport.contentHolder.addMouseListener (this, true);
Desktop::getInstance().removeGlobalMouseListener (this);
isGlobalMouseListener = false;
}
bool doesMouseEventComponentBlockViewportDrag (const Component* eventComp)
{
for (auto c = eventComp; c != nullptr && c != &viewport; c = c->getParentComponent())
if (c->getViewportIgnoreDragFlag())
return true;
return false;
}
Viewport& viewport;
ViewportDragPosition offsetX, offsetY;
Point<int> originalViewPos;
MouseInputSource scrollSource = Desktop::getInstance().getMainMouseSource();
bool isDragging = false;
bool isGlobalMouseListener = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragToScrollListener)
};
void Viewport::setScrollOnDragEnabled (bool shouldScrollOnDrag)
{
if (isScrollOnDragEnabled() != shouldScrollOnDrag)
{
if (shouldScrollOnDrag)
dragToScrollListener.reset (new DragToScrollListener (*this));
else
dragToScrollListener.reset();
}
}
bool Viewport::isScrollOnDragEnabled() const noexcept
void Viewport::setScrollOnDragMode (const ScrollOnDragMode mode)
{
return dragToScrollListener != nullptr;
scrollOnDragMode = mode;
}
bool Viewport::isCurrentlyScrollingOnDrag() const noexcept
{
return dragToScrollListener != nullptr && dragToScrollListener->isDragging;
return dragToScrollListener->isDragging;
}
//==============================================================================


+ 28
- 4
modules/juce_gui_basics/layout/juce_Viewport.h View File

@@ -271,16 +271,39 @@ public:
*/
bool canScrollHorizontally() const noexcept;
/** Enables or disables drag-to-scroll functionality in the viewport.
/** Enables or disables drag-to-scroll functionality for mouse sources in the viewport.
If your viewport contains a Component that you don't want to receive mouse events when the
user is drag-scrolling, you can disable this with the Component::setViewportIgnoreDragFlag()
method.
*/
void setScrollOnDragEnabled (bool shouldScrollOnDrag);
[[deprecated ("Use setScrollOnDragMode instead.")]]
void setScrollOnDragEnabled (bool shouldScrollOnDrag)
{
setScrollOnDragMode (shouldScrollOnDrag ? ScrollOnDragMode::all : ScrollOnDragMode::never);
}
/** Returns true if drag-to-scroll functionality is enabled. */
bool isScrollOnDragEnabled() const noexcept;
/** Returns true if drag-to-scroll functionality is enabled for mouse input sources. */
[[deprecated ("Use getScrollOnDragMode instead.")]]
bool isScrollOnDragEnabled() const noexcept { return getScrollOnDragMode() == ScrollOnDragMode::all; }
enum class ScrollOnDragMode
{
never, /**< Dragging will never scroll the viewport. */
nonHover, /**< Dragging will only scroll the viewport if the input source cannot hover. */
all /**< Dragging will always scroll the viewport. */
};
/** Sets the current scroll-on-drag mode. The default is ScrollOnDragMode::nonHover.
If your viewport contains a Component that you don't want to receive mouse events when the
user is drag-scrolling, you can disable this with the Component::setViewportIgnoreDragFlag()
method.
*/
void setScrollOnDragMode (ScrollOnDragMode scrollOnDragMode);
/** Returns the current scroll-on-drag mode. */
ScrollOnDragMode getScrollOnDragMode() const { return scrollOnDragMode; }
/** Returns true if the user is currently dragging-to-scroll.
@see setScrollOnDragEnabled
@@ -320,6 +343,7 @@ private:
Rectangle<int> lastVisibleArea;
int scrollBarThickness = 0;
int singleStepX = 16, singleStepY = 16;
ScrollOnDragMode scrollOnDragMode = ScrollOnDragMode::nonHover;
bool showHScrollbar = true, showVScrollbar = true, deleteContent = true;
bool customScrollBarThickness = false;
bool allowScrollingWithoutScrollbarV = false, allowScrollingWithoutScrollbarH = false;


+ 1
- 9
modules/juce_gui_basics/widgets/juce_ListBox.cpp View File

@@ -107,14 +107,6 @@ public:
m->listBoxItemClicked (row, e);
}
bool isInDragToScrollViewport() const noexcept
{
if (auto* vp = owner.getViewport())
return vp->isScrollOnDragEnabled() && (vp->canScrollVertically() || vp->canScrollHorizontally());
return false;
}
void mouseDown (const MouseEvent& e) override
{
isDragging = false;
@@ -123,7 +115,7 @@ public:
if (isEnabled())
{
if (owner.selectOnMouseDown && ! (isSelected || isInDragToScrollViewport()))
if (owner.selectOnMouseDown && ! isSelected && ! viewportWouldScrollOnEvent (owner.getViewport(), e.source))
performSelection (e, false);
else
selectRowOnMouseUp = true;


Loading…
Cancel
Save