Hi Juce Team,
Doing applications for iOs, I was implementing a sub class of ListBox to be able to scroll it with a mouseDrag like the native mobile lists implementations. I thought It would be nice to go further by proposing you an implementation in Viewport that allows you to forget about scrollbars.
I tested it and it works perfectly with ListBox but there might be some modifications to do to other component that use viewport and mouseDrag. (might be in conflict with mouseDrag scrolling)
The code I join behaves like iOs native viewports except that it does not bounce when the top/buttom is reached.
Implem in Viewport:
To enable it I added this method to the viewport class: void setShouldScrollOnDrag(bool)
When set to TRUE here is the new behavior for ListBox:
- Disables Drag and Drop in ListBox::RowComponent
- Force Selection on MouseUp
- Disable Selection on MouseUp if ListBox was dragged/scrolled (isScrollingOnDrag())
Other method added: bool getShouldScrollOnDrag() /*getter*/
Other method added: bool isScrollingOnDrag() /* Returns wether the viewport is beeing scrolled by a mouse drag or not */
Behaviour in Viewport, explanations:
- Counts fingers down to be able to scroll with only one finger and avoid multitouch weird behaviors
- Starts a timer on MouseUp if the list was draged in order to keep on dragging according to the velocity that was given to the scroll. This is helpful to reach top/end of the list faster.
There is few things you can tweak manualy:
- Init Velocity, how fast the auto scrolling starts (0.5)
- Velocity reducer, how fast the auto scrolling slows down after each timer iteration (0.9)
- Distance in Px reached by the finger before the dragging enables (to avoid scrolling by mistake) (5-10 px)
- Speed that is necessary to trigger the autodrag (1)
- Timer (10ms) ratio smooth/perfs
Here is the patch for viewport:
From 543c0e0f29d607f7fbe3ffca7e7683a79b565635 Mon Sep 17 00:00:00 2001
From: BastienC <b.commelongue@uvi.net>
Date: Fri, 24 Apr 2015 10:23:39 +0200
Subject: [PATCH] - Scroll on mouseDrag viewport
---
modules/juce_gui_basics/layout/juce_Viewport.cpp | 104 ++++++++++++++++++++++-
modules/juce_gui_basics/layout/juce_Viewport.h | 17 ++++
2 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/modules/juce_gui_basics/layout/juce_Viewport.cpp b/modules/juce_gui_basics/layout/juce_Viewport.cpp
index 21360a0..9d5a236 100644
--- a/modules/juce_gui_basics/layout/juce_Viewport.cpp
+++ b/modules/juce_gui_basics/layout/juce_Viewport.cpp
@@ -22,6 +22,99 @@
==============================================================================
*/
+class Viewport::ScrollOnDragViewPort : public MouseListener, public Timer
+{
+public:
+ ScrollOnDragViewPort(Viewport &o) : owner(o),
+ numberFingerDown(0),
+ isDragging(false)
+ {
+ o.addMouseListener(this, true);
+ }
+
+ //==============================================================================
+ void mouseUp(const MouseEvent &e) override
+ {
+ if (numberFingerDown)
+ --numberFingerDown;
+
+ if (isDragging)
+ {
+ if (abs(lastScrollSpeed.x) > 1 || abs(lastScrollSpeed.y) > 1) //Autodrag Threshold
+ startTimer(10);
+ }
+ isDragging = false;
+ }
+
+ //==============================================================================
+ void mouseDrag(const MouseEvent &e) override
+ {
+ if (!owner.shouldScrollOnDrag || numberFingerDown > 1)
+ return;
+ if (isDragging == false) //init drag
+ {
+ draggingVelocity = Point<double>(0.5, 0.5);
+ startScrollPx.x = owner.getViewPosition().x;
+ startScrollPx.y = owner.getViewPosition().y;
+ lastScrollingDistance = Point<int>(0, 0);
+ lastScrollSpeed = Point<int>(0, 0);
+ }
+ Point<int> distanceInPx = e.getOffsetFromDragStart();
+
+ lastScrollSpeed.y = e.getDistanceFromDragStartY() - lastScrollingDistance.y;
+ lastScrollSpeed.x = e.getDistanceFromDragStartX() - lastScrollingDistance.x;
+
+ if (!isDragging && abs(distanceInPx.x) < 10 && abs(distanceInPx.y) < 10)
+ return;
+ isDragging = true;
+
+ Point<int> moveInPx;
+ moveInPx.x = (startScrollPx.x + (-distanceInPx.x));
+ moveInPx.y = (startScrollPx.y + (-distanceInPx.y));
+
+ owner.setViewPosition(moveInPx.x, moveInPx.y);
+ lastScrollingDistance = distanceInPx;
+ }
+
+ //==============================================================================
+ void mouseDown(const MouseEvent &e) override
+ {
+ stopTimer();
+ ++numberFingerDown;
+ }
+
+ //==============================================================================
+ void timerCallback() override
+ {
+ Point<int> stepSize;
+
+ stepSize.x = (int)(lastScrollingDistance.x + lastScrollSpeed.x * draggingVelocity.x);
+ stepSize.y = (int)(lastScrollingDistance.y + lastScrollSpeed.y * draggingVelocity.y);
+
+ if (lastScrollingDistance.x == stepSize.x && lastScrollingDistance.y == stepSize.y)
+ return stopTimer();
+
+ Point<int> moveInPx;
+ moveInPx.x = (startScrollPx.x + (-stepSize.x));
+ moveInPx.y = (startScrollPx.y + (-stepSize.y));
+
+ owner.setViewPosition(moveInPx.x, moveInPx.y);
+ lastScrollingDistance = stepSize;
+ draggingVelocity *= 0.9; //Reduce velocity
+ }
+
+private:
+ friend class Viewport;
+ Viewport& owner;
+
+ Point<double> draggingVelocity;
+ Point<int> lastScrollingDistance, lastScrollSpeed, startScrollPx;
+ int numberFingerDown;
+ bool isDragging;
+};
+
+
+//==============================================================================
Viewport::Viewport (const String& name)
: Component (name),
customScrollBarThickness(false),
@@ -34,7 +127,8 @@ Viewport::Viewport (const String& name)
allowScrollingWithoutScrollbarV (false),
allowScrollingWithoutScrollbarH (false),
verticalScrollBar (true),
- horizontalScrollBar (false)
+ horizontalScrollBar (false),
+ shouldScrollOnDrag(false)
{
// content holder is used to clip the contents so they don't overlap the scrollbars
addAndMakeVisible (contentHolder);
@@ -50,6 +144,8 @@ Viewport::Viewport (const String& name)
setInterceptsMouseClicks (false, true);
setWantsKeyboardFocus (true);
+
+ scrollOnDrag = new ScrollOnDragViewPort(*this);
}
Viewport::~Viewport()
@@ -63,6 +159,12 @@ void Viewport::visibleAreaChanged (const Rectangle<int>&) {}
void Viewport::viewedComponentChanged (Component*) {}
//==============================================================================
+bool Viewport::isScrollingOnDrag()
+{
+ return scrollOnDrag->isDragging;
+}
+
+//==============================================================================
void Viewport::deleteContentComp()
{
if (contentComp != nullptr)
diff --git a/modules/juce_gui_basics/layout/juce_Viewport.h b/modules/juce_gui_basics/layout/juce_Viewport.h
index 78aa4fa..b19281d 100644
--- a/modules/juce_gui_basics/layout/juce_Viewport.h
+++ b/modules/juce_gui_basics/layout/juce_Viewport.h
@@ -240,6 +240,17 @@ public:
*/
ScrollBar* getHorizontalScrollBar() noexcept { return &horizontalScrollBar; }
+ /** Enables or not the possibility to scroll in the viewport using mouseDrag in the viewport
+ */
+ void setShouldScrollOnDrag(bool should) { shouldScrollOnDrag = should;}
+
+ /** True if mouseDrag is scrolling the viewport
+ */
+ bool getShouldScrollOnDrag() const { return shouldScrollOnDrag; }
+
+ /** True if the viewport is currenly beeing scrolled via a mouseDrag, mouse is down and dragging in the viewport.
+ */
+ bool isScrollingOnDrag();
//==============================================================================
/** @internal */
@@ -278,6 +289,12 @@ private:
void updateVisibleArea();
void deleteContentComp();
+ bool shouldScrollOnDrag;
+
+ class ScrollOnDragViewPort;
+ friend class ScrollOnDragViewPort;
+ ScopedPointer<ScrollOnDragViewPort> scrollOnDrag;
+
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
// If you get an error here, it's because this method's parameters have changed! See the new definition above..
virtual int visibleAreaChanged (int, int, int, int) { return 0; }
--
1.9.0.msysgit.0
Here is the patch for listbox:
From 1eb2f4ba033fc43362ee6856bafbe7f1cc07c484 Mon Sep 17 00:00:00 2001
From: BastienC <b.commelongue@uvi.net>
Date: Fri, 24 Apr 2015 10:46:44 +0200
Subject: [PATCH] - Listbox uses Viewport with scrollOnDrag
---
modules/juce_gui_basics/widgets/juce_ListBox.cpp | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/modules/juce_gui_basics/widgets/juce_ListBox.cpp
index c90db18..f4c89b8 100644
--- a/modules/juce_gui_basics/widgets/juce_ListBox.cpp
+++ b/modules/juce_gui_basics/widgets/juce_ListBox.cpp
@@ -68,7 +68,7 @@ public:
if (isEnabled())
{
- if (owner.selectOnMouseDown && ! selected)
+ if (owner.selectOnMouseDown && !selected && !owner.getViewport()->getShouldScrollOnDrag())
{
owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
@@ -84,7 +84,7 @@ public:
void mouseUp (const MouseEvent& e) override
{
- if (isEnabled() && selectRowOnMouseUp && ! isDragging)
+ if (isEnabled() && selectRowOnMouseUp && !isDragging && !owner.getViewport()->isScrollingOnDrag())
{
owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
@@ -102,6 +102,9 @@ public:
void mouseDrag (const MouseEvent& e) override
{
+ if (owner.getViewport()->getShouldScrollOnDrag())
+ return;
+
if (ListBoxModel* m = owner.getModel())
{
if (isEnabled() && ! (e.mouseWasClicked() || isDragging))
--
1.9.0.msysgit.0
Please let me know what you think,
Thank you,
BastienC
