Browse Source

Added deceleration to ScrollView + fps independence + attributes for a better experience on mobile devices. Holding finger down keeps control / flicking and putting finger down stops it.

hdunderscore 11 years ago
parent
commit
f12d40168a
2 changed files with 165 additions and 22 deletions
  1. 146 21
      Source/Engine/UI/ScrollView.cpp
  2. 19 1
      Source/Engine/UI/ScrollView.h

+ 146 - 21
Source/Engine/UI/ScrollView.cpp

@@ -44,7 +44,11 @@ ScrollView::ScrollView(Context* context) :
     viewPosition_(IntVector2::ZERO),
     viewSize_(IntVector2::ZERO),
     viewPositionAttr_(IntVector2::ZERO),
-    touchScrollSpeed_(IntVector2::ZERO),
+    touchScrollSpeed_(Vector2::ZERO),
+    touchScrollSpeedMax_(Vector2::ZERO),
+    scrollDeceleration_(30.0),
+    scrollSnapEpsilon_(M_EPSILON),
+    scrollFingerDown_(false),
     pageStep_(1.0f),
     scrollBarsAutoVisible_(true),
     ignoreEvents_(false),
@@ -72,6 +76,8 @@ ScrollView::ScrollView(Context* context) :
     SubscribeToEvent(verticalScrollBar_, E_SCROLLBARCHANGED, HANDLER(ScrollView, HandleScrollBarChanged));
     SubscribeToEvent(verticalScrollBar_, E_VISIBLECHANGED, HANDLER(ScrollView, HandleScrollBarVisibleChanged));
     SubscribeToEvent(E_TOUCHMOVE, HANDLER(ScrollView, HandleTouchMove));
+    SubscribeToEvent(E_TOUCHBEGIN, HANDLER(ScrollView, HandleTouchMove));
+    SubscribeToEvent(E_TOUCHEND, HANDLER(ScrollView, HandleTouchMove));
 }
 
 ScrollView::~ScrollView()
@@ -90,29 +96,31 @@ void ScrollView::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE(ScrollView, VAR_FLOAT, "Scroll Step", GetScrollStep, SetScrollStep, float, 0.1f, AM_FILE);
     ACCESSOR_ATTRIBUTE(ScrollView, VAR_FLOAT, "Page Step", GetPageStep, SetPageStep, float, 1.0f, AM_FILE);
     ACCESSOR_ATTRIBUTE(ScrollView, VAR_BOOL, "Auto Show/Hide Scrollbars", GetScrollBarsAutoVisible, SetScrollBarsAutoVisible, bool, true, AM_FILE);
+    ACCESSOR_ATTRIBUTE(ScrollView, VAR_FLOAT, "Scroll Deceleration", GetScrollDeceleration, SetScrollDeceleration, float, 30.0f, AM_FILE);
+    ACCESSOR_ATTRIBUTE(ScrollView, VAR_FLOAT, "Scroll Snap Epsilon", GetScrollSnapEpsilon, SetScrollSnapEpsilon, float, 1.0f, AM_FILE);
 }
 
 void ScrollView::Update(float timeStep)
 {
     // Update touch scrolling here if necessary
-    if (touchScrollSpeed_ == IntVector2::ZERO)
+    if (touchScrollSpeed_ == Vector2::ZERO)
         return;
-    
+
     // Check if we should not scroll:
     // - ScrollView is not visible, is not enabled, or doesn't have focus
     // - The element being dragged is not a child of the ScrollView, or is one of our scrollbars
     if (!IsVisible() || !IsEnabled() || !HasFocus())
     {
-        touchScrollSpeed_ = IntVector2::ZERO;
+        touchScrollSpeed_ = Vector2::ZERO;
         return;
     }
-    
+
     UIElement* dragElement = GetSubsystem<UI>()->GetDragElement();
     if (dragElement)
     {
         UIElement* dragParent = dragElement->GetParent();
         bool dragElementIsChild = false;
-        
+
         while (dragParent)
         {
             if (dragParent == this)
@@ -122,22 +130,23 @@ void ScrollView::Update(float timeStep)
             }
             dragParent = dragParent->GetParent();
         }
-        
+
         if (!dragElementIsChild || dragElement == horizontalScrollBar_->GetSlider() || dragElement == verticalScrollBar_->GetSlider())
         {
-            touchScrollSpeed_ = IntVector2::ZERO;
+            touchScrollSpeed_ = Vector2::ZERO;
             return;
         }
     }
-    
-    // Update view position, reset speed accumulation for next frame
+
+    // Update view position
     IntVector2 newPosition = viewPosition_;
     newPosition.x_ += touchScrollSpeed_.x_;
     newPosition.y_ += touchScrollSpeed_.y_;
     SetViewPosition(newPosition);
-    
-    /// \todo Could have smooth deceleration
-    touchScrollSpeed_ = IntVector2::ZERO;
+
+    /// Smooth deceleration
+    ScrollSmooth(float timeStep);
+
 }
 
 void ScrollView::ApplyAttributes()
@@ -499,14 +508,130 @@ void ScrollView::HandleElementResized(StringHash eventType, VariantMap& eventDat
 void ScrollView::HandleTouchMove(StringHash eventType, VariantMap& eventData)
 {
     using namespace TouchMove;
-    
-    // Take new scrolling speed if it's faster than the current accumulated value
-    int dX = -eventData[P_DX].GetInt();
-    int dY = -eventData[P_DY].GetInt();
-    if (Abs(dX) > Abs(touchScrollSpeed_.x_))
-        touchScrollSpeed_.x_ = dX;
-    if (Abs(dY) > Abs(touchScrollSpeed_.y_))
-        touchScrollSpeed_.y_ = dY;
+
+    if (eventType == E_TOUCHMOVE)
+    {
+        scrollFingerDown_ = true;
+        // Take new scrolling speed if it's faster than the current accumulated value
+        int dX = -eventData[P_DX].GetInt();
+        int dY = -eventData[P_DY].GetInt();
+        if (Abs(dX) > Abs(touchScrollSpeed_.x_))
+            touchScrollSpeed_.x_ = dX;
+        if (Abs(dY) > Abs(touchScrollSpeed_.y_))
+            touchScrollSpeed_.y_ = dY;
+        touchScrollSpeedMax_ = touchScrollSpeed_;
+    }
+    else if (eventType == E_TOUCHBEGIN)
+    {
+        // Stop the control if the figer goes back down
+        touchScrollSpeed_ = Vector2::ZERO;
+        touchScrollSpeedMax_ = Vector2::ZERO;
+    }
+    else
+    {
+        // 'Flick' action
+        scrollFingerDown_ = false;
+        touchScrollSpeed_ = touchScrollSpeedMax_;
+        touchScrollSpeedMax_ = Vector2::ZERO;
+    }
+}
+
+void ScrollView::ScrollSmooth(float timeStep)
+{
+    if (touchScrollSpeedMax_.x_ >= scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.x_ -= scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.x_ = touchScrollSpeedMax_.x_ > 0 ? touchScrollSpeedMax_.x_ : 0;
+    }
+    else if (touchScrollSpeedMax_.x_ <= -scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.x_ += scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.x_ = touchScrollSpeedMax_.x_ < 0 ? touchScrollSpeedMax_.x_ : 0;
+    }
+    else
+        touchScrollSpeedMax_.x_ = 0;
+
+    if (touchScrollSpeedMax_.y_ >= scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.y_ -= scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.y_ = touchScrollSpeedMax_.y_ > 0 ? touchScrollSpeedMax_.y_ : 0;
+    }
+    else if (touchScrollSpeedMax_.y_ <= -scrollSnapEpsilon_)
+    {
+        touchScrollSpeedMax_.y_ += scrollDeceleration_ * timeStep;
+        touchScrollSpeedMax_.y_ = touchScrollSpeedMax_.y_ < 0 ? touchScrollSpeedMax_.y_ : 0;
+    }
+    else
+        touchScrollSpeedMax_.y_ = 0;
+
+    if (scrollFingerDown_)
+    {
+        touchScrollSpeed_ = Vector2::ZERO;
+    }
+    else
+    {
+        if (touchScrollSpeed_.x_ >= scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.x_ -= scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.x_ < 0)
+            {
+                touchScrollSpeed_.x_ = 0;
+            }
+
+            if (horizontalScrollBar_->GetValue() >= horizontalScrollBar_->GetRange() - M_EPSILON)
+            {
+                // stop movement when we reach end of scroll
+                touchScrollSpeed_.x_ = 0;
+            }
+        }
+        else if (touchScrollSpeed_.x_ < -scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.x_ += scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.x_ > 0)
+            {
+                touchScrollSpeed_.x_ = 0;
+            }
+
+            if (horizontalScrollBar_->GetValue() <= M_EPSILON)
+            {
+                // stop movement when we reach end of scroll
+                touchScrollSpeed_.x_ = 0;
+            }
+        }
+        else
+            touchScrollSpeed_.x_ = 0;
+
+        if (touchScrollSpeed_.y_ >= scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.y_ -= scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.y_ < 0)
+            {
+                touchScrollSpeed_.y_ = 0;
+            }
+
+            if (verticalScrollBar_->GetValue() >= verticalScrollBar_->GetRange() - M_EPSILON)
+            {
+                // stop movement when we reach end of scroll
+                touchScrollSpeed_.y_ = 0;
+            }
+        }
+        else if (touchScrollSpeed_.y_ < -scrollSnapEpsilon_)
+        {
+            touchScrollSpeed_.y_ += scrollDeceleration_ * timeStep;
+            if (touchScrollSpeed_.y_ > 0)
+            {
+                touchScrollSpeed_.y_ = 0;
+            }
+
+            if (verticalScrollBar_->GetValue() <= M_EPSILON)
+            {
+                // stop movement when we reach end of scroll
+                touchScrollSpeed_.y_ = 0;
+            }
+        }
+        else
+            touchScrollSpeed_.y_ = 0;
+    }
 }
 
 }

+ 19 - 1
Source/Engine/UI/ScrollView.h

@@ -68,6 +68,10 @@ public:
     void SetScrollStep(float step);
     /// Set arrow key page step.
     void SetPageStep(float step);
+    /// Return scroll deceleration.
+    void SetScrollDeceleration(float deceleration) { scrollDeceleration_ = deceleration; }
+    /// Return scroll snap epsilon
+    void SetScrollSnapEpsilon(float snap) { scrollSnapEpsilon_ = snap; }
 
     /// Return view offset from the top-left corner.
     const IntVector2& GetViewPosition() const { return viewPosition_; }
@@ -85,6 +89,10 @@ public:
     float GetScrollStep() const;
     /// Return arrow key page step.
     float GetPageStep() const { return pageStep_; }
+    /// Return scroll deceleration.
+    float GetScrollDeceleration() const { return scrollDeceleration_; }
+    /// Return scroll snap epsilon
+    float GetScrollSnapEpsilon() const { return scrollSnapEpsilon_; }
 
     /// Set view position attribute.
     void SetViewPositionAttr(const IntVector2& value);
@@ -118,7 +126,9 @@ protected:
     /// View offset attribute.
     IntVector2 viewPositionAttr_;
     /// Accumulated touch scroll speed.
-    IntVector2 touchScrollSpeed_;
+    Vector2 touchScrollSpeed_;
+    /// Max touch scroll speed.
+    Vector2 touchScrollSpeedMax_;
     /// Arrow key page step.
     float pageStep_;
     /// Automatically show/hide scrollbars flag.
@@ -127,6 +137,12 @@ protected:
     bool ignoreEvents_;
     /// Resize content widget width to match panel. Internal flag, used by the ListView class.
     bool resizeContentWidth_;
+    /// Scroll deceleration
+    float scrollDeceleration_;
+    /// Scroll snap epsilon
+    float scrollSnapEpsilon_;
+    /// Used to trigger scroll smoothing when false;
+    bool scrollFingerDown_;
 
 private:
     /// Handle scrollbar value changed.
@@ -137,6 +153,8 @@ private:
     void HandleElementResized(StringHash eventType, VariantMap& eventData);
     /// Handle touch move event for scrolling.
     void HandleTouchMove(StringHash eventType, VariantMap& eventData);
+    /// Handle the scroll smoothing.
+    void ScrollSmooth(float timeStep);
 };
 
 }