X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Fcontrols%2Fscrollable%2Fscroll-view%2Fscroll-view-impl.cpp;h=712cb17b53360ff58971299ef42dc7f2f9632e66;hp=a318c6d2f64b80efead09c030255a05c21eb21e9;hb=3ce558ca09f928cb18cacfa80f52eb0c591181b9;hpb=b8da2e53925b9abb9fa362560069e8ca4aa62f81 diff --git a/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.cpp b/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.cpp index a318c6d..712cb17 100644 --- a/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.cpp +++ b/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,7 @@ //#define ENABLED_SCROLL_STATE_LOGGING #ifdef ENABLED_SCROLL_STATE_LOGGING -#define DALI_LOG_SCROLL_STATE(format, ...) Dali::Integration::Log::LogMessage(Dali::Integration::Log::DebugInfo, "%s:%d " format "\n", __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#define DALI_LOG_SCROLL_STATE(format, ...) Dali::Integration::Log::LogMessageWithFunctionLine(Dali::Integration::Log::DebugInfo, "%s:%d " format "\n", __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) #else #define DALI_LOG_SCROLL_STATE(format, ...) #endif @@ -55,29 +56,32 @@ // TODO: Orientation. // TODO: upgrade Vector2/3 to support returning Unit vectors, normals, & cross product (dot product is already provided) -using namespace Dali; - namespace { -const float DEFAULT_SLOW_SNAP_ANIMATION_DURATION(0.5f); ///< Default Drag-Release animation time. -const float DEFAULT_FAST_SNAP_ANIMATION_DURATION(0.25f); ///< Default Drag-Flick animation time. -const float DEFAULT_SNAP_OVERSHOOT_DURATION(0.5f); ///< Default Overshoot snapping animation time. -const float DEFAULT_MAX_OVERSHOOT(100.0f); ///< Default maximum allowed overshoot in pixels - -const float DEFAULT_AXIS_AUTO_LOCK_GRADIENT(0.36f); ///< Default Axis-AutoLock gradient threshold. default is 0.36:1 (20 degrees) -const float DEFAULT_FRICTION_COEFFICIENT(1.0f); ///< Default Friction Co-efficient. (in stage diagonals per second) -const float DEFAULT_FLICK_SPEED_COEFFICIENT(1.0f); ///< Default Flick speed coefficient (multiples input touch velocity) -const float DEFAULT_MAX_FLICK_SPEED(3.0f); ///< Default Maximum flick speed. (in stage diagonals per second) - -const Vector2 DEFAULT_MIN_FLICK_DISTANCE(30.0f, 30.0f); ///< minimum distance for pan before flick allowed -const float DEFAULT_MIN_FLICK_SPEED_THRESHOLD(500.0f); ///< Minimum pan speed required for flick in pixels/s -const float FREE_FLICK_SPEED_THRESHOLD = 200.0f; ///< Free-Flick threshold in pixels/ms -const float AUTOLOCK_AXIS_MINIMUM_DISTANCE2 = 100.0f; ///< Auto-lock axis after minimum distance squared. -const float FLICK_ORTHO_ANGLE_RANGE = 75.0f; ///< degrees. (if >45, then supports diagonal flicking) -const Vector2 DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION = Vector2(0.17f, 0.1f); ///< The step of horizontal scroll distance in the proportion of stage size for each wheel event received. -const unsigned long MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET(150u); -const float TOUCH_DOWN_TIMER_INTERVAL = 100.0f; -const float DEFAULT_SCROLL_UPDATE_DISTANCE(30.0f); ///< Default distance to travel in pixels for scroll update signal +using namespace Dali; + +constexpr float DEFAULT_SLOW_SNAP_ANIMATION_DURATION(0.5f); ///< Default Drag-Release animation time. +constexpr float DEFAULT_FAST_SNAP_ANIMATION_DURATION(0.25f); ///< Default Drag-Flick animation time. +constexpr float DEFAULT_SNAP_OVERSHOOT_DURATION(0.5f); ///< Default Overshoot snapping animation time. +constexpr float DEFAULT_MAX_OVERSHOOT(100.0f); ///< Default maximum allowed overshoot in pixels + +constexpr float DEFAULT_AXIS_AUTO_LOCK_GRADIENT(0.36f); ///< Default Axis-AutoLock gradient threshold. default is 0.36:1 (20 degrees) +constexpr float DEFAULT_FRICTION_COEFFICIENT(1.0f); ///< Default Friction Co-efficient. (in stage diagonals per second) +constexpr float DEFAULT_FLICK_SPEED_COEFFICIENT(1.0f); ///< Default Flick speed coefficient (multiples input touch velocity) +constexpr float DEFAULT_MAX_FLICK_SPEED(3.0f); ///< Default Maximum flick speed. (in stage diagonals per second) + +constexpr Dali::Vector2 DEFAULT_MIN_FLICK_DISTANCE(30.0f, 30.0f); ///< minimum distance for pan before flick allowed +constexpr float DEFAULT_MIN_FLICK_SPEED_THRESHOLD(500.0f); ///< Minimum pan speed required for flick in pixels/s + +constexpr float FREE_FLICK_SPEED_THRESHOLD = 200.0f; ///< Free-Flick threshold in pixels/ms +constexpr float AUTOLOCK_AXIS_MINIMUM_DISTANCE2 = 100.0f; ///< Auto-lock axis after minimum distance squared. +constexpr float FLICK_ORTHO_ANGLE_RANGE = 75.0f; ///< degrees. (if >45, then supports diagonal flicking) + +constexpr Dali::Vector2 DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION(0.17f, 0.1f); ///< The step of horizontal scroll distance in the proportion of stage size for each wheel event received. + +constexpr unsigned long MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET(150u); +constexpr float TOUCH_DOWN_TIMER_INTERVAL = 100.0f; +constexpr float DEFAULT_SCROLL_UPDATE_DISTANCE(30.0f); ///< Default distance to travel in pixels for scroll update signal const std::string INTERNAL_MAX_POSITION_PROPERTY_NAME("internalMaxPosition"); @@ -104,7 +108,7 @@ float VectorInDomain(float a, float b, float start, float end, Dali::Toolkit::Di { if(bias == Dali::Toolkit::DIRECTION_BIAS_NONE) { - return ShortestDistanceInDomain(a, b, start, end); + return Dali::ShortestDistanceInDomain(a, b, start, end); } // (a-start + end-b) float size = end - start; @@ -145,22 +149,118 @@ float VectorInDomain(float a, float b, float start, float end, Dali::Toolkit::Di * @param anchor The Anchor point of interest. * @return The position of the Anchor */ -Vector3 GetPositionOfAnchor(Actor& actor, const Vector3& anchor) +Dali::Vector3 GetPositionOfAnchor(Dali::Actor& actor, const Dali::Vector3& anchor) { - Vector3 childPosition = actor.GetCurrentProperty(Actor::Property::POSITION); - Vector3 childAnchor = -actor.GetCurrentProperty(Actor::Property::ANCHOR_POINT) + anchor; - Vector3 childSize = actor.GetCurrentProperty(Actor::Property::SIZE); + Dali::Vector3 childPosition = actor.GetCurrentProperty(Dali::Actor::Property::POSITION); + Dali::Vector3 childAnchor = -actor.GetCurrentProperty(Dali::Actor::Property::ANCHOR_POINT) + anchor; + Dali::Vector3 childSize = actor.GetCurrentProperty(Dali::Actor::Property::SIZE); return childPosition + childAnchor * childSize; } -// AlphaFunctions ///////////////////////////////////////////////////////////////////////////////// +/** + * Returns the closest actor to the given position + * @param[in] actor The scrollview actor + * @param[in] internalActor The internal actor (to ignore) + * @param[in] position The given position + * @param[in] dirX Direction to search in + * @param[in] dirY Direction to search in + * @param[in] dirZ Direction to search in + * @return the closest child actor + */ +using FindDirection = Dali::Toolkit::Internal::ScrollView::FindDirection; -float FinalDefaultAlphaFunction(float offset) +Actor FindClosestActorToPosition( + CustomActor actor, Actor internalActor, const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ) { - return offset * 0.5f; + Actor closestChild; + float closestDistance2 = 0.0f; + Vector3 actualPosition = position; + + unsigned int numChildren = actor.GetChildCount(); + + for(unsigned int i = 0; i < numChildren; ++i) + { + Actor child = actor.GetChildAt(i); + + if(internalActor == child) // ignore internal actor. + { + continue; + } + + Vector3 childPosition = GetPositionOfAnchor(child, AnchorPoint::CENTER); + + Vector3 delta = childPosition - actualPosition; + + // X-axis checking (only find Actors to the [dirX] of actualPosition) + if(dirX > FindDirection::All) // != All,None + { + FindDirection deltaH = delta.x > 0 ? FindDirection::Right : FindDirection::Left; + if(dirX != deltaH) + { + continue; + } + } + + // Y-axis checking (only find Actors to the [dirY] of actualPosition) + if(dirY > FindDirection::All) // != All,None + { + FindDirection deltaV = delta.y > 0 ? FindDirection::Down : FindDirection::Up; + if(dirY != deltaV) + { + continue; + } + } + + // Z-axis checking (only find Actors to the [dirZ] of actualPosition) + if(dirZ > FindDirection::All) // != All,None + { + FindDirection deltaV = delta.y > 0 ? FindDirection::In : FindDirection::Out; + if(dirZ != deltaV) + { + continue; + } + } + + // compare child to closest child in terms of distance. + float distance2 = 0.0f; + + // distance2 = the Square of the relevant dimensions of delta + if(dirX != FindDirection::None) + { + distance2 += delta.x * delta.x; + } + + if(dirY != FindDirection::None) + { + distance2 += delta.y * delta.y; + } + + if(dirZ != FindDirection::None) + { + distance2 += delta.z * delta.z; + } + + if(closestChild) // Next time. + { + if(distance2 < closestDistance2) + { + closestChild = child; + closestDistance2 = distance2; + } + } + else // First time. + { + closestChild = child; + closestDistance2 = distance2; + } + } + + return closestChild; } +// AlphaFunctions ///////////////////////////////////////////////////////////////////////////////// + /** * ConstantDecelerationAlphaFunction * Newtoninan distance for constant deceleration @@ -177,435 +277,308 @@ float ConstantDecelerationAlphaFunction(float progress) return progress * 2.0f - progress * progress; } -// Internal Constraints /////////////////////////////////////////////////////////////////////////// - /** - * Internal Relative position Constraint - * Generates the relative position value of the scroll view - * based on the absolute position, and it's relation to the - * scroll domain. This is a value from 0.0f to 1.0f in each - * scroll position axis. + * Clamp a position + * @param[in] size The size to clamp to + * @param[in] rulerX The horizontal ruler + * @param[in] rulerY The vertical ruler + * @param[in,out] position The position to clamp + * @param[out] clamped the clamped state */ -void InternalRelativePositionConstraint(Vector2& relativePosition, const PropertyInputContainer& inputs) +void ClampPosition(const Vector3& size, Dali::Toolkit::RulerPtr rulerX, Dali::Toolkit::RulerPtr rulerY, Vector2& position, Dali::Toolkit::ClampState2D& clamped) { - Vector2 position = -inputs[0]->GetVector2(); - const Vector2& min = inputs[1]->GetVector2(); - const Vector2& max = inputs[2]->GetVector2(); - const Vector3& size = inputs[3]->GetVector3(); - - position.x = WrapInDomain(position.x, min.x, max.x); - position.y = WrapInDomain(position.y, min.y, max.y); - - Vector2 domainSize = (max - min) - size.GetVectorXY(); - - relativePosition.x = domainSize.x > Math::MACHINE_EPSILON_1 ? fabsf((position.x - min.x) / domainSize.x) : 0.0f; - relativePosition.y = domainSize.y > Math::MACHINE_EPSILON_1 ? fabsf((position.y - min.y) / domainSize.y) : 0.0f; -} - -/** - * Internal scroll domain Constraint - * Generates the scroll domain of the scroll view. - */ -void InternalScrollDomainConstraint(Vector2& scrollDomain, const PropertyInputContainer& inputs) -{ - const Vector2& min = inputs[0]->GetVector2(); - const Vector2& max = inputs[1]->GetVector2(); - const Vector3& size = inputs[2]->GetVector3(); - - scrollDomain = (max - min) - size.GetVectorXY(); + position.x = -rulerX->Clamp(-position.x, size.width, 1.0f, clamped.x); // NOTE: X & Y rulers think in -ve coordinate system. + position.y = -rulerY->Clamp(-position.y, size.height, 1.0f, clamped.y); // That is scrolling RIGHT (e.g. 100.0, 0.0) means moving LEFT. } /** - * Internal maximum scroll position Constraint - * Generates the maximum scroll position of the scroll view. + * TODO: In situations where axes are different (X snap, Y free) + * Each axis should really have their own independent animation (time and equation) + * Consider, X axis snapping to nearest grid point (EaseOut over fixed time) + * Consider, Y axis simulating physics to arrive at a point (Physics equation over variable time) + * Currently, the axes have been split however, they both use the same EaseOut equation. + * + * @param[in] scrollView The main scrollview + * @param[in] rulerX The X ruler + * @param[in] rulerY The Y ruler + * @param[in] lockAxis Which axis (if any) is locked. + * @param[in] velocity Current pan velocity + * @param[in] maxOvershoot Maximum overshoot + * @param[in] inAcessibilityPan True if we are currently panning with accessibility + * @param[out] positionSnap The target position of snap animation + * @param[out] positionDuration The duration of the snap animation + * @param[out] alphaFunction The snap animation alpha function + * @param[out] isFlick if we are flicking or not + * @param[out] isFreeFlick if we are free flicking or not */ -void InternalPrePositionMaxConstraint(Vector2& scrollMax, const PropertyInputContainer& inputs) -{ - const Vector2& max = inputs[0]->GetVector2(); - const Vector3& size = inputs[1]->GetVector3(); - - scrollMax = max - size.GetVectorXY(); -} - -} // unnamed namespace - -namespace Dali +void SnapWithVelocity( + Dali::Toolkit::Internal::ScrollView& scrollView, + Dali::Toolkit::RulerPtr rulerX, + Dali::Toolkit::RulerPtr rulerY, + Dali::Toolkit::Internal::ScrollView::LockAxis lockAxis, + Vector2 velocity, + Vector2 maxOvershoot, + Vector2& positionSnap, + Vector2& positionDuration, + AlphaFunction& alphaFunction, + bool inAccessibilityPan, + bool& isFlick, + bool& isFreeFlick) { -namespace Toolkit -{ -namespace Internal -{ -namespace -{ -BaseHandle Create() -{ - return Toolkit::ScrollView::New(); -} + // Animator takes over now, touches are assumed not to interfere. + // And if touches do interfere, then we'll stop animation, update PrePosition + // to current mScroll's properties, and then resume. + // Note: For Flicking this may work a bit different... -// Setup properties, signals and actions using the type-registry. -DALI_TYPE_REGISTRATION_BEGIN(Toolkit::ScrollView, Toolkit::Scrollable, Create) + float angle = atan2(velocity.y, velocity.x); + float speed2 = velocity.LengthSquared(); + float biasX = 0.5f; + float biasY = 0.5f; + FindDirection horizontal = FindDirection::None; + FindDirection vertical = FindDirection::None; -DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrapEnabled", BOOLEAN, WRAP_ENABLED) -DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panningEnabled", BOOLEAN, PANNING_ENABLED) -DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "axisAutoLockEnabled", BOOLEAN, AXIS_AUTO_LOCK_ENABLED) -DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wheelScrollDistanceStep", VECTOR2, WHEEL_SCROLL_DISTANCE_STEP) -DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollMode", MAP, SCROLL_MODE) + using LockAxis = Dali::Toolkit::Internal::ScrollView::LockAxis; -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPosition", VECTOR2, SCROLL_POSITION) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePosition", VECTOR2, SCROLL_PRE_POSITION) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionX", SCROLL_PRE_POSITION_X, SCROLL_PRE_POSITION, 0) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionY", SCROLL_PRE_POSITION_Y, SCROLL_PRE_POSITION, 1) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMax", VECTOR2, SCROLL_PRE_POSITION_MAX) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxX", SCROLL_PRE_POSITION_MAX_X, SCROLL_PRE_POSITION_MAX, 0) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxY", SCROLL_PRE_POSITION_MAX_Y, SCROLL_PRE_POSITION_MAX, 1) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootX", FLOAT, OVERSHOOT_X) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootY", FLOAT, OVERSHOOT_Y) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollFinal", VECTOR2, SCROLL_FINAL) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalX", SCROLL_FINAL_X, SCROLL_FINAL, 0) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalY", SCROLL_FINAL_Y, SCROLL_FINAL, 1) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrap", BOOLEAN, WRAP) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panning", BOOLEAN, PANNING) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrolling", BOOLEAN, SCROLLING) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainSize", VECTOR2, SCROLL_DOMAIN_SIZE) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeX", SCROLL_DOMAIN_SIZE_X, SCROLL_DOMAIN_SIZE, 0) -DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeY", SCROLL_DOMAIN_SIZE_Y, SCROLL_DOMAIN_SIZE, 1) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainOffset", VECTOR2, SCROLL_DOMAIN_OFFSET) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPositionDelta", VECTOR2, SCROLL_POSITION_DELTA) -DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "startPagePosition", VECTOR3, START_PAGE_POSITION) + // orthoAngleRange = Angle tolerance within the Exact N,E,S,W direction + // that will be accepted as a general N,E,S,W flick direction. -DALI_SIGNAL_REGISTRATION(Toolkit, ScrollView, "valueChanged", SIGNAL_SNAP_STARTED) + const float orthoAngleRange = FLICK_ORTHO_ANGLE_RANGE * M_PI / 180.0f; + const float flickSpeedThreshold2 = scrollView.GetMinimumSpeedForFlick() * scrollView.GetMinimumSpeedForFlick(); -DALI_TYPE_REGISTRATION_END() + // Flick logic X Axis -/** - * Returns whether to lock scrolling to a particular axis - * - * @param[in] panDelta Distance panned since gesture started - * @param[in] currentLockAxis The current lock axis value - * @param[in] lockGradient How quickly to lock to a particular axis - * - * @return The new axis lock state - */ -ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient) -{ - if(panDelta.LengthSquared() > AUTOLOCK_AXIS_MINIMUM_DISTANCE2 && - currentLockAxis == ScrollView::LockPossible) + if(rulerX->IsEnabled() && lockAxis != LockAxis::LockHorizontal) { - float dx = fabsf(panDelta.x); - float dy = fabsf(panDelta.y); - if(dx * lockGradient >= dy) - { - // 0.36:1 gradient to the horizontal (deviate < 20 degrees) - currentLockAxis = ScrollView::LockVertical; - } - else if(dy * lockGradient > dx) - { - // 0.36:1 gradient to the vertical (deviate < 20 degrees) - currentLockAxis = ScrollView::LockHorizontal; - } - else - { - currentLockAxis = ScrollView::LockNone; - } - } - return currentLockAxis; -} + horizontal = FindDirection::All; -/** - * Internal Pre-Position Property Constraint. - * - * Generates position property based on current position + gesture displacement. - * Or generates position property based on positionX/Y. - * Note: This is the position prior to any clamping at scroll boundaries. - */ -struct InternalPrePositionConstraint -{ - InternalPrePositionConstraint(const Vector2& initialPanPosition, - const Vector2& initialPanMask, - bool axisAutoLock, - float axisAutoLockGradient, - ScrollView::LockAxis initialLockAxis, - const Vector2& maxOvershoot, - const RulerPtr& rulerX, - const RulerPtr& rulerY) - : mLocalStart(initialPanPosition), - mInitialPanMask(initialPanMask), - mMaxOvershoot(maxOvershoot), - mAxisAutoLockGradient(axisAutoLockGradient), - mLockAxis(initialLockAxis), - mAxisAutoLock(axisAutoLock), - mWasPanning(false) - { - const RulerDomain& rulerDomainX = rulerX->GetDomain(); - const RulerDomain& rulerDomainY = rulerY->GetDomain(); - mDomainMin = Vector2(rulerDomainX.min, -rulerDomainY.min); - mDomainMax = Vector2(-rulerDomainX.max, -rulerDomainY.max); - mClampX = rulerDomainX.enabled; - mClampY = rulerDomainY.enabled; - mFixedRulerX = rulerX->GetType() == Ruler::FIXED; - mFixedRulerY = rulerY->GetType() == Ruler::FIXED; - } - - void operator()(Vector2& scrollPostPosition, const PropertyInputContainer& inputs) - { - const Vector2& panPosition = inputs[0]->GetVector2(); - const bool& inGesture = inputs[1]->GetBoolean(); - - // First check if we are within a gesture. - // The ScrollView may have received a start gesture from ::OnPan() - // while the finish gesture is received now in this constraint. - // This gesture must then be rejected as the value will be "old". - // Typically the last value from the end of the last gesture. - // If we are rejecting the gesture, we simply don't modify the constraint target. - if(inGesture) - { - if(!mWasPanning) + if(speed2 > flickSpeedThreshold2 || // exceeds flick threshold + inAccessibilityPan) // With AccessibilityPan its easier to move between snap positions + { + if((angle >= -orthoAngleRange) && (angle < orthoAngleRange)) // Swiping East { - mPrePosition = scrollPostPosition; - mStartPosition = mPrePosition; - mCurrentPanMask = mInitialPanMask; - mWasPanning = true; - } + biasX = 0.0f, horizontal = FindDirection::Left; - // Calculate Deltas... - const Vector2& currentPosition = panPosition; - Vector2 panDelta(currentPosition - mLocalStart); - - // Axis Auto Lock - locks the panning to the horizontal or vertical axis if the pan - // appears mostly horizontal or mostly vertical respectively... - if(mAxisAutoLock) + // This guards against an error where no movement occurs, due to the flick finishing + // before the update-thread has advanced mScrollPostPosition past the the previous snap point. + positionSnap.x += 1.0f; + } + else if((angle >= M_PI - orthoAngleRange) || (angle < -M_PI + orthoAngleRange)) // Swiping West { - mLockAxis = GetLockAxis(panDelta, mLockAxis, mAxisAutoLockGradient); - if(mLockAxis == ScrollView::LockVertical) - { - mCurrentPanMask.y = 0.0f; - } - else if(mLockAxis == ScrollView::LockHorizontal) - { - mCurrentPanMask.x = 0.0f; - } + biasX = 1.0f, horizontal = FindDirection::Right; + + // This guards against an error where no movement occurs, due to the flick finishing + // before the update-thread has advanced mScrollPostPosition past the the previous snap point. + positionSnap.x -= 1.0f; } + } + } - // Restrict deltas based on ruler enable/disable and axis-lock state... - panDelta *= mCurrentPanMask; + // Flick logic Y Axis - // Perform Position transform based on input deltas... - scrollPostPosition = mPrePosition; - scrollPostPosition += panDelta; + if(rulerY->IsEnabled() && lockAxis != LockAxis::LockVertical) + { + vertical = FindDirection::All; - // if no wrapping then clamp preposition to maximum overshoot amount - const Vector3& size = inputs[2]->GetVector3(); - if(mClampX) - { - float newXPosition = Clamp(scrollPostPosition.x, (mDomainMax.x + size.x) - mMaxOvershoot.x, mDomainMin.x + mMaxOvershoot.x); - if((newXPosition < scrollPostPosition.x - Math::MACHINE_EPSILON_1) || (newXPosition > scrollPostPosition.x + Math::MACHINE_EPSILON_1)) - { - mPrePosition.x = newXPosition; - mLocalStart.x = panPosition.x; - } - scrollPostPosition.x = newXPosition; - } - if(mClampY) + if(speed2 > flickSpeedThreshold2 || // exceeds flick threshold + inAccessibilityPan) // With AccessibilityPan its easier to move between snap positions + { + if((angle >= M_PI_2 - orthoAngleRange) && (angle < M_PI_2 + orthoAngleRange)) // Swiping South { - float newYPosition = Clamp(scrollPostPosition.y, (mDomainMax.y + size.y) - mMaxOvershoot.y, mDomainMin.y + mMaxOvershoot.y); - if((newYPosition < scrollPostPosition.y - Math::MACHINE_EPSILON_1) || (newYPosition > scrollPostPosition.y + Math::MACHINE_EPSILON_1)) - { - mPrePosition.y = newYPosition; - mLocalStart.y = panPosition.y; - } - scrollPostPosition.y = newYPosition; + biasY = 0.0f, vertical = FindDirection::Up; } - - // If we are using a fixed ruler in a particular axis, limit the maximum pages scrolled on that axis. - if(mFixedRulerX || mFixedRulerY) + else if((angle >= -M_PI_2 - orthoAngleRange) && (angle < -M_PI_2 + orthoAngleRange)) // Swiping North { - // Here we limit the maximum amount that can be moved from the starting position of the gesture to one page. - // We do this only if we have a fixed ruler (on that axis) and the mode is enabled. - // Note: 1.0f is subtracted to keep the value within one page size (otherwise we stray on to the page after). - // Note: A further 1.0f is subtracted to handle a compensation that happens later within the flick handling code in SnapWithVelocity(). - // When a flick is completed, an adjustment of 1.0f is sometimes made to allow for the scenario where: - // A flick finishes before the update thread has advanced the scroll position past the previous snap point. - Vector2 viewPageSizeLimit(size.x - (1.0f + 1.0f), size.y - (1.0f - 1.0f)); - Vector2 minPosition(mStartPosition.x - viewPageSizeLimit.x, mStartPosition.y - viewPageSizeLimit.y); - Vector2 maxPosition(mStartPosition.x + viewPageSizeLimit.x, mStartPosition.y + viewPageSizeLimit.y); - - if(mFixedRulerX) - { - scrollPostPosition.x = Clamp(scrollPostPosition.x, minPosition.x, maxPosition.x); - } - if(mFixedRulerY) - { - scrollPostPosition.y = Clamp(scrollPostPosition.y, minPosition.y, maxPosition.y); - } + biasY = 1.0f, vertical = FindDirection::Down; } } } - Vector2 mPrePosition; - Vector2 mLocalStart; - Vector2 mStartPosition; ///< The start position of the gesture - used to limit scroll amount (not modified by clamping). - Vector2 mInitialPanMask; ///< Initial pan mask (based on ruler settings). - Vector2 mCurrentPanMask; ///< Current pan mask that can be altered by axis lock mode. - Vector2 mDomainMin; - Vector2 mDomainMax; - Vector2 mMaxOvershoot; - - float mAxisAutoLockGradient; ///< Set by ScrollView - ScrollView::LockAxis mLockAxis; - - bool mAxisAutoLock : 1; ///< Set by ScrollView - bool mWasPanning : 1; - bool mClampX : 1; - bool mClampY : 1; - bool mFixedRulerX : 1; - bool mFixedRulerY : 1; -}; + // isFlick: Whether this gesture is a flick or not. + isFlick = (horizontal != FindDirection::All || vertical != FindDirection::All); + // isFreeFlick: Whether this gesture is a flick under free panning criteria. + isFreeFlick = velocity.LengthSquared() > (FREE_FLICK_SPEED_THRESHOLD * FREE_FLICK_SPEED_THRESHOLD); -/** - * Internal Position Property Constraint. - * - * Generates position property based on pre-position - * Note: This is the position after clamping. - * (uses result of InternalPrePositionConstraint) - */ -struct InternalPositionConstraint -{ - InternalPositionConstraint(const RulerDomain& domainX, const RulerDomain& domainY, bool wrap) - : mDomainMin(-domainX.min, -domainY.min), - mDomainMax(-domainX.max, -domainY.max), - mClampX(domainX.enabled), - mClampY(domainY.enabled), - mWrap(wrap) + if(isFlick || isFreeFlick) { + positionDuration = Vector2::ONE * scrollView.GetScrollFlickDuration(); + alphaFunction = scrollView.GetScrollFlickAlphaFunction(); } - void operator()(Vector2& position, const PropertyInputContainer& inputs) + // Calculate next positionSnap //////////////////////////////////////////////////////////// + + if(scrollView.GetActorAutoSnap()) { - position = inputs[0]->GetVector2(); - const Vector2& size = inputs[3]->GetVector3().GetVectorXY(); - const Vector2& min = inputs[1]->GetVector2(); - const Vector2& max = inputs[2]->GetVector2(); + Vector3 size = scrollView.Self().GetCurrentProperty(Actor::Property::SIZE); + + Actor child = scrollView.FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f), horizontal, vertical); - if(mWrap) + if(!child && isFlick) { - position.x = -WrapInDomain(-position.x, min.x, max.x); - position.y = -WrapInDomain(-position.y, min.y, max.y); + // If we conducted a direction limited search and found no actor, then just snap to the closest actor. + child = scrollView.FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f)); } - else + + if(child) { - // clamp post position to domain - position.x = mClampX ? Clamp(position.x, mDomainMax.x + size.x, mDomainMin.x) : position.x; - position.y = mClampY ? Clamp(position.y, mDomainMax.y + size.y, mDomainMin.y) : position.y; + Vector2 position = scrollView.Self().GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION); + + // Get center-point of the Actor. + Vector3 childPosition = GetPositionOfAnchor(child, AnchorPoint::CENTER); + + if(rulerX->IsEnabled()) + { + positionSnap.x = position.x - childPosition.x + size.width * 0.5f; + } + if(rulerY->IsEnabled()) + { + positionSnap.y = position.y - childPosition.y + size.height * 0.5f; + } } } - Vector2 mDomainMin; - Vector2 mDomainMax; - bool mClampX; - bool mClampY; - bool mWrap; -}; + Vector2 startPosition = positionSnap; + positionSnap.x = -rulerX->Snap(-positionSnap.x, biasX); // NOTE: X & Y rulers think in -ve coordinate system. + positionSnap.y = -rulerY->Snap(-positionSnap.y, biasY); // That is scrolling RIGHT (e.g. 100.0, 0.0) means moving LEFT. -/** - * This constraint updates the X overshoot property using the difference - * SCROLL_PRE_POSITION.x and SCROLL_POSITION.x, returning a relative value between 0.0f and 1.0f - */ -struct OvershootXConstraint -{ - OvershootXConstraint(float maxOvershoot) - : mMaxOvershoot(maxOvershoot) + Dali::Toolkit::ClampState2D clamped; + Vector3 size = scrollView.Self().GetCurrentProperty(Actor::Property::SIZE); + Vector2 clampDelta(Vector2::ZERO); + ClampPosition(size, rulerX, rulerY, positionSnap, clamped); + + if((rulerX->GetType() == Dali::Toolkit::Ruler::FREE || rulerY->GetType() == Dali::Toolkit::Ruler::FREE) && + isFreeFlick && !scrollView.GetActorAutoSnap()) { - } + // Calculate target position based on velocity of flick. + + // a = Deceleration (Set to diagonal stage length * friction coefficient) + // u = Initial Velocity (Flick velocity) + // v = 0 (Final Velocity) + // t = Time (Velocity / Deceleration) + Vector2 stageSize = Stage::GetCurrent().GetSize(); + float stageLength = Vector3(stageSize.x, stageSize.y, 0.0f).Length(); + float a = (stageLength * scrollView.GetFrictionCoefficient()); + Vector3 u = Vector3(velocity.x, velocity.y, 0.0f) * scrollView.GetFlickSpeedCoefficient(); + float speed = u.Length(); + u /= speed; + + // TODO: Change this to a decay function. (faster you flick, the slower it should be) + speed = std::min(speed, stageLength * scrollView.GetMaxFlickSpeed()); + u *= speed; + alphaFunction = ConstantDecelerationAlphaFunction; + + float t = speed / a; + + if(rulerX->IsEnabled() && rulerX->GetType() == Dali::Toolkit::Ruler::FREE) + { + positionSnap.x += t * u.x * 0.5f; + } + + if(rulerY->IsEnabled() && rulerY->GetType() == Dali::Toolkit::Ruler::FREE) + { + positionSnap.y += t * u.y * 0.5f; + } - void operator()(float& current, const PropertyInputContainer& inputs) - { - if(inputs[2]->GetBoolean()) + clampDelta = positionSnap; + ClampPosition(size, rulerX, rulerY, positionSnap, clamped); + + if((positionSnap - startPosition).LengthSquared() > Math::MACHINE_EPSILON_0) { - const Vector2& scrollPrePosition = inputs[0]->GetVector2(); - const Vector2& scrollPostPosition = inputs[1]->GetVector2(); - float newOvershoot = scrollPrePosition.x - scrollPostPosition.x; - current = (newOvershoot > 0.0f ? std::min(newOvershoot, mMaxOvershoot) : std::max(newOvershoot, -mMaxOvershoot)) / mMaxOvershoot; + clampDelta -= positionSnap; + clampDelta.x = clampDelta.x > 0.0f ? std::min(clampDelta.x, maxOvershoot.x) : std::max(clampDelta.x, -maxOvershoot.x); + clampDelta.y = clampDelta.y > 0.0f ? std::min(clampDelta.y, maxOvershoot.y) : std::max(clampDelta.y, -maxOvershoot.y); } else { - current = 0.0f; + clampDelta = Vector2::ZERO; } - } - float mMaxOvershoot; -}; - -/** - * This constraint updates the Y overshoot property using the difference - * SCROLL_PRE_POSITION.y and SCROLL_POSITION.y, returning a relative value between 0.0f and 1.0f - */ -struct OvershootYConstraint -{ - OvershootYConstraint(float maxOvershoot) - : mMaxOvershoot(maxOvershoot) - { - } - - void operator()(float& current, const PropertyInputContainer& inputs) - { - if(inputs[2]->GetBoolean()) + // If Axis is Free and has velocity, then calculate time taken + // to reach target based on velocity in axis. + if(rulerX->IsEnabled() && rulerX->GetType() == Dali::Toolkit::Ruler::FREE) { - const Vector2& scrollPrePosition = inputs[0]->GetVector2(); - const Vector2& scrollPostPosition = inputs[1]->GetVector2(); - float newOvershoot = scrollPrePosition.y - scrollPostPosition.y; - current = (newOvershoot > 0.0f ? std::min(newOvershoot, mMaxOvershoot) : std::max(newOvershoot, -mMaxOvershoot)) / mMaxOvershoot; + float deltaX = fabsf(startPosition.x - positionSnap.x); + + if(fabsf(u.x) > Math::MACHINE_EPSILON_1) + { + positionDuration.x = fabsf(deltaX / u.x); + } + else + { + positionDuration.x = 0; + } } - else + + if(rulerY->IsEnabled() && rulerY->GetType() == Dali::Toolkit::Ruler::FREE) { - current = 0.0f; + float deltaY = fabsf(startPosition.y - positionSnap.y); + + if(fabsf(u.y) > Math::MACHINE_EPSILON_1) + { + positionDuration.y = fabsf(deltaY / u.y); + } + else + { + positionDuration.y = 0; + } } } - float mMaxOvershoot; -}; + if(scrollView.IsOvershootEnabled()) + { + // Scroll to the end of the overshoot only when overshoot is enabled. + positionSnap += clampDelta; + } +} -/** - * Internal Position-Delta Property Constraint. - * - * Generates position-delta property based on scroll-position + scroll-offset properties. - */ -void InternalPositionDeltaConstraint(Vector2& current, const PropertyInputContainer& inputs) -{ - const Vector2& scrollPosition = inputs[0]->GetVector2(); - const Vector2& scrollOffset = inputs[1]->GetVector2(); +} // unnamed namespace - current = scrollPosition + scrollOffset; +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +namespace +{ +BaseHandle Create() +{ + return Toolkit::ScrollView::New(); } -/** - * Internal Final Position Constraint - * The position of content is: - * of scroll-position + f(scroll-overshoot) - * where f(...) function defines how overshoot - * should affect final-position. - */ -struct InternalFinalConstraint -{ - InternalFinalConstraint(AlphaFunctionPrototype functionX, - AlphaFunctionPrototype functionY) - : mFunctionX(functionX), - mFunctionY(functionY) - { - } +// Setup properties, signals and actions using the type-registry. +DALI_TYPE_REGISTRATION_BEGIN(Toolkit::ScrollView, Toolkit::Scrollable, Create) - void operator()(Vector2& current, const PropertyInputContainer& inputs) - { - const float& overshootx = inputs[1]->GetFloat(); - const float& overshooty = inputs[2]->GetFloat(); - Vector2 offset(mFunctionX(overshootx), - mFunctionY(overshooty)); +DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrapEnabled", BOOLEAN, WRAP_ENABLED) +DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panningEnabled", BOOLEAN, PANNING_ENABLED) +DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "axisAutoLockEnabled", BOOLEAN, AXIS_AUTO_LOCK_ENABLED) +DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wheelScrollDistanceStep", VECTOR2, WHEEL_SCROLL_DISTANCE_STEP) +DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollMode", MAP, SCROLL_MODE) - current = inputs[0]->GetVector2() - offset; - } +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPosition", VECTOR2, SCROLL_POSITION) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePosition", VECTOR2, SCROLL_PRE_POSITION) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionX", SCROLL_PRE_POSITION_X, SCROLL_PRE_POSITION, 0) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionY", SCROLL_PRE_POSITION_Y, SCROLL_PRE_POSITION, 1) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMax", VECTOR2, SCROLL_PRE_POSITION_MAX) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxX", SCROLL_PRE_POSITION_MAX_X, SCROLL_PRE_POSITION_MAX, 0) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxY", SCROLL_PRE_POSITION_MAX_Y, SCROLL_PRE_POSITION_MAX, 1) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootX", FLOAT, OVERSHOOT_X) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootY", FLOAT, OVERSHOOT_Y) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollFinal", VECTOR2, SCROLL_FINAL) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalX", SCROLL_FINAL_X, SCROLL_FINAL, 0) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalY", SCROLL_FINAL_Y, SCROLL_FINAL, 1) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrap", BOOLEAN, WRAP) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panning", BOOLEAN, PANNING) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrolling", BOOLEAN, SCROLLING) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainSize", VECTOR2, SCROLL_DOMAIN_SIZE) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeX", SCROLL_DOMAIN_SIZE_X, SCROLL_DOMAIN_SIZE, 0) +DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeY", SCROLL_DOMAIN_SIZE_Y, SCROLL_DOMAIN_SIZE, 1) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainOffset", VECTOR2, SCROLL_DOMAIN_OFFSET) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPositionDelta", VECTOR2, SCROLL_POSITION_DELTA) +DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "startPagePosition", VECTOR3, START_PAGE_POSITION) - AlphaFunctionPrototype mFunctionX; - AlphaFunctionPrototype mFunctionY; -}; +DALI_SIGNAL_REGISTRATION(Toolkit, ScrollView, "valueChanged", SIGNAL_SNAP_STARTED) + +DALI_TYPE_REGISTRATION_END() } // namespace @@ -701,8 +674,8 @@ void ScrollView::OnInitialize() self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, mCanScrollVertical); self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, mCanScrollHorizontal); - UpdatePropertyDomain(); - SetInternalConstraints(); + ScrollViewPropertyHandler::UpdatePropertyDomain(*this); + mConstraints.SetInternalConstraints(*this); // Connect wheel event self.WheelEventSignal().Connect(this, &ScrollView::OnWheelEvent); @@ -746,46 +719,6 @@ ScrollView::~ScrollView() DALI_LOG_SCROLL_STATE("[0x%X]", this); } -AlphaFunction ScrollView::GetScrollSnapAlphaFunction() const -{ - return mSnapAlphaFunction; -} - -void ScrollView::SetScrollSnapAlphaFunction(AlphaFunction alpha) -{ - mSnapAlphaFunction = alpha; -} - -AlphaFunction ScrollView::GetScrollFlickAlphaFunction() const -{ - return mFlickAlphaFunction; -} - -void ScrollView::SetScrollFlickAlphaFunction(AlphaFunction alpha) -{ - mFlickAlphaFunction = alpha; -} - -float ScrollView::GetScrollSnapDuration() const -{ - return mSnapDuration; -} - -void ScrollView::SetScrollSnapDuration(float time) -{ - mSnapDuration = time; -} - -float ScrollView::GetScrollFlickDuration() const -{ - return mFlickDuration; -} - -void ScrollView::SetScrollFlickDuration(float time) -{ - mFlickDuration = time; -} - void ScrollView::ApplyEffect(Toolkit::ScrollViewEffect effect) { Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self()); @@ -858,132 +791,20 @@ void ScrollView::RemoveConstraintsFromChildren() RemoveConstraintsFromBoundActors(); } -const RulerPtr ScrollView::GetRulerX() const -{ - return mRulerX; -} - -const RulerPtr ScrollView::GetRulerY() const -{ - return mRulerY; -} - void ScrollView::SetRulerX(RulerPtr ruler) { mRulerX = ruler; - UpdatePropertyDomain(); - UpdateMainInternalConstraint(); + ScrollViewPropertyHandler::UpdatePropertyDomain(*this); + mConstraints.UpdateMainInternalConstraint(*this); } void ScrollView::SetRulerY(RulerPtr ruler) { mRulerY = ruler; - UpdatePropertyDomain(); - UpdateMainInternalConstraint(); -} - -void ScrollView::UpdatePropertyDomain() -{ - Actor self = Self(); - Vector3 size = self.GetTargetSize(); - Vector2 min = mMinScroll; - Vector2 max = mMaxScroll; - bool scrollPositionChanged = false; - bool domainChanged = false; - - bool canScrollVertical = false; - bool canScrollHorizontal = false; - UpdateLocalScrollProperties(); - if(mRulerX->IsEnabled()) - { - const Toolkit::RulerDomain& rulerDomain = mRulerX->GetDomain(); - if(fabsf(min.x - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.x - rulerDomain.max) > Math::MACHINE_EPSILON_100) - { - domainChanged = true; - min.x = rulerDomain.min; - max.x = rulerDomain.max; - - // make sure new scroll value is within new domain - if(mScrollPrePosition.x < min.x || mScrollPrePosition.x > max.x) - { - scrollPositionChanged = true; - mScrollPrePosition.x = Clamp(mScrollPrePosition.x, -(max.x - size.x), -min.x); - } - } - if((fabsf(rulerDomain.max - rulerDomain.min) - size.x) > Math::MACHINE_EPSILON_100) - { - canScrollHorizontal = true; - } - } - else if(fabs(min.x) > Math::MACHINE_EPSILON_100 || fabs(max.x) > Math::MACHINE_EPSILON_100) - { - // need to reset to 0 - domainChanged = true; - min.x = 0.0f; - max.x = 0.0f; - canScrollHorizontal = false; - } - - if(mRulerY->IsEnabled()) - { - const Toolkit::RulerDomain& rulerDomain = mRulerY->GetDomain(); - if(fabsf(min.y - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.y - rulerDomain.max) > Math::MACHINE_EPSILON_100) - { - domainChanged = true; - min.y = rulerDomain.min; - max.y = rulerDomain.max; - - // make sure new scroll value is within new domain - if(mScrollPrePosition.y < min.y || mScrollPrePosition.y > max.y) - { - scrollPositionChanged = true; - mScrollPrePosition.y = Clamp(mScrollPrePosition.y, -(max.y - size.y), -min.y); - } - } - if((fabsf(rulerDomain.max - rulerDomain.min) - size.y) > Math::MACHINE_EPSILON_100) - { - canScrollVertical = true; - } - } - else if(fabs(min.y) > Math::MACHINE_EPSILON_100 || fabs(max.y) > Math::MACHINE_EPSILON_100) - { - // need to reset to 0 - domainChanged = true; - min.y = 0.0f; - max.y = 0.0f; - canScrollVertical = false; - } - - // avoid setting properties if possible, otherwise this will cause an entire update as well as triggering constraints using each property we update - if(mCanScrollVertical != canScrollVertical) - { - mCanScrollVertical = canScrollVertical; - self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, canScrollVertical); - } - if(mCanScrollHorizontal != canScrollHorizontal) - { - mCanScrollHorizontal = canScrollHorizontal; - self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, canScrollHorizontal); - } - if(scrollPositionChanged) - { - DALI_LOG_SCROLL_STATE("[0x%X] Domain Changed, setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y); - self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition); - } - if(domainChanged) - { - mMinScroll = min; - mMaxScroll = max; - self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN, mMinScroll); - self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, mMaxScroll); - } -} - -bool ScrollView::GetScrollSensitive() -{ - return mSensitive; + ScrollViewPropertyHandler::UpdatePropertyDomain(*this); + mConstraints.UpdateMainInternalConstraint(*this); } void ScrollView::SetScrollSensitive(bool sensitive) @@ -1002,164 +823,64 @@ void ScrollView::SetScrollSensitive(bool sensitive) { DALI_LOG_SCROLL_STATE("[0x%X] BEFORE: panning:[%d]", this, int(mPanning)); - // while the scroll view is panning, the state needs to be reset. - if(mPanning) - { - PanGesture cancelGesture = DevelPanGesture::New(GestureState::CANCELLED); - OnPan(cancelGesture); - } - - panGesture.Detach(self); - mSensitive = sensitive; - - mGestureStackDepth = 0; - DALI_LOG_SCROLL_STATE("[0x%X] AFTER: panning:[%d]", this, int(mPanning)); - } -} - -void ScrollView::SetMaxOvershoot(float overshootX, float overshootY) -{ - mMaxOvershoot.x = overshootX; - mMaxOvershoot.y = overshootY; - mUserMaxOvershoot = mMaxOvershoot; - mDefaultMaxOvershoot = false; - UpdateMainInternalConstraint(); -} - -void ScrollView::SetSnapOvershootAlphaFunction(AlphaFunction alpha) -{ - mSnapOvershootAlphaFunction = alpha; -} - -float ScrollView::GetSnapOvershootDuration() -{ - return mSnapOvershootDuration; -} - -void ScrollView::SetSnapOvershootDuration(float duration) -{ - mSnapOvershootDuration = duration; -} - -bool ScrollView::GetActorAutoSnap() -{ - return mActorAutoSnapEnabled; -} - -void ScrollView::SetActorAutoSnap(bool enable) -{ - mActorAutoSnapEnabled = enable; -} - -void ScrollView::SetAutoResize(bool enable) -{ - mAutoResizeContainerEnabled = enable; - // TODO: This needs a lot of issues to be addressed before working. -} - -bool ScrollView::GetWrapMode() const -{ - return mWrapMode; -} - -void ScrollView::SetWrapMode(bool enable) -{ - mWrapMode = enable; - Self().SetProperty(Toolkit::ScrollView::Property::WRAP, enable); -} - -int ScrollView::GetScrollUpdateDistance() const -{ - return mScrollUpdateDistance; -} - -void ScrollView::SetScrollUpdateDistance(int distance) -{ - mScrollUpdateDistance = distance; -} - -bool ScrollView::GetAxisAutoLock() const -{ - return mAxisAutoLock; -} - -void ScrollView::SetAxisAutoLock(bool enable) -{ - mAxisAutoLock = enable; - UpdateMainInternalConstraint(); -} - -float ScrollView::GetAxisAutoLockGradient() const -{ - return mAxisAutoLockGradient; -} - -void ScrollView::SetAxisAutoLockGradient(float gradient) -{ - DALI_ASSERT_DEBUG(gradient >= 0.0f && gradient <= 1.0f); - mAxisAutoLockGradient = gradient; - UpdateMainInternalConstraint(); -} - -float ScrollView::GetFrictionCoefficient() const -{ - return mFrictionCoefficient; -} - -void ScrollView::SetFrictionCoefficient(float friction) -{ - DALI_ASSERT_DEBUG(friction > 0.0f); - mFrictionCoefficient = friction; -} - -float ScrollView::GetFlickSpeedCoefficient() const -{ - return mFlickSpeedCoefficient; -} + // while the scroll view is panning, the state needs to be reset. + if(mPanning) + { + PanGesture cancelGesture = DevelPanGesture::New(GestureState::CANCELLED); + OnPan(cancelGesture); + } -void ScrollView::SetFlickSpeedCoefficient(float speed) -{ - mFlickSpeedCoefficient = speed; -} + panGesture.Detach(self); + mSensitive = sensitive; -Vector2 ScrollView::GetMinimumDistanceForFlick() const -{ - return mMinFlickDistance; + mGestureStackDepth = 0; + DALI_LOG_SCROLL_STATE("[0x%X] AFTER: panning:[%d]", this, int(mPanning)); + } } -void ScrollView::SetMinimumDistanceForFlick(const Vector2& distance) +void ScrollView::SetMaxOvershoot(float overshootX, float overshootY) { - mMinFlickDistance = distance; + mMaxOvershoot.x = overshootX; + mMaxOvershoot.y = overshootY; + mUserMaxOvershoot = mMaxOvershoot; + mDefaultMaxOvershoot = false; + mConstraints.UpdateMainInternalConstraint(*this); } -float ScrollView::GetMinimumSpeedForFlick() const +bool ScrollView::GetActorAutoSnap() { - return mFlickSpeedThreshold; + return mActorAutoSnapEnabled; } -void ScrollView::SetMinimumSpeedForFlick(float speed) +void ScrollView::SetAutoResize(bool enable) { - mFlickSpeedThreshold = speed; + mAutoResizeContainerEnabled = enable; + // TODO: This needs a lot of issues to be addressed before working. } -float ScrollView::GetMaxFlickSpeed() const +void ScrollView::SetWrapMode(bool enable) { - return mMaxFlickSpeed; + mWrapMode = enable; + Self().SetProperty(Toolkit::ScrollView::Property::WRAP, enable); } -void ScrollView::SetMaxFlickSpeed(float speed) +void ScrollView::SetAxisAutoLock(bool enable) { - mMaxFlickSpeed = speed; + mAxisAutoLock = enable; + mConstraints.UpdateMainInternalConstraint(*this); } -void ScrollView::SetWheelScrollDistanceStep(Vector2 step) +void ScrollView::SetAxisAutoLockGradient(float gradient) { - mWheelScrollDistanceStep = step; + DALI_ASSERT_DEBUG(gradient >= 0.0f && gradient <= 1.0f); + mAxisAutoLockGradient = gradient; + mConstraints.UpdateMainInternalConstraint(*this); } -Vector2 ScrollView::GetWheelScrollDistanceStep() const +void ScrollView::SetFrictionCoefficient(float friction) { - return mWheelScrollDistanceStep; + DALI_ASSERT_DEBUG(friction > 0.0f); + mFrictionCoefficient = friction; } unsigned int ScrollView::GetCurrentPage() const @@ -1229,9 +950,9 @@ void ScrollView::TransformTo(const Vector2& position, float duration, AlphaFunct mGestureStackDepth = 0; self.SetProperty(Toolkit::ScrollView::Property::PANNING, false); - if(mScrollMainInternalPrePositionConstraint) + if(mConstraints.mScrollMainInternalPrePositionConstraint) { - mScrollMainInternalPrePositionConstraint.Remove(); + mConstraints.mScrollMainInternalPrePositionConstraint.Remove(); } } @@ -1294,358 +1015,76 @@ void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction TransformTo(position, duration, alpha, horizontalBias, verticalBias); } -void ScrollView::ScrollTo(unsigned int page) -{ - ScrollTo(page, mSnapDuration); -} - -void ScrollView::ScrollTo(unsigned int page, float duration, DirectionBias bias) -{ - Vector2 position; - unsigned int volume; - unsigned int libraries; - - // The position to scroll to is continuous and linear - // unless a domain has been enabled on the X axis. - // or if WrapMode has been enabled. - bool carryX = mRulerX->GetDomain().enabled | mWrapMode; - bool carryY = mRulerY->GetDomain().enabled | mWrapMode; - - position.x = mRulerX->GetPositionFromPage(page, volume, carryX); - position.y = mRulerY->GetPositionFromPage(volume, libraries, carryY); - - ScrollTo(position, duration, bias, bias); -} - -void ScrollView::ScrollTo(Actor& actor) -{ - ScrollTo(actor, mSnapDuration); -} - -void ScrollView::ScrollTo(Actor& actor, float duration) -{ - DALI_ASSERT_ALWAYS(actor.GetParent() == Self()); - - Actor self = Self(); - Vector3 size = self.GetCurrentProperty(Actor::Property::SIZE); - Vector3 position = actor.GetCurrentProperty(Actor::Property::POSITION); - Vector2 prePosition = GetPropertyPrePosition(); - position.GetVectorXY() -= prePosition; - - ScrollTo(Vector2(position.x - size.width * 0.5f, position.y - size.height * 0.5f), duration); -} - -Actor ScrollView::FindClosestActor() -{ - Actor self = Self(); - Vector3 size = self.GetCurrentProperty(Actor::Property::SIZE); - - return FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f)); -} - -Actor ScrollView::FindClosestActorToPosition(const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ) -{ - Actor closestChild; - float closestDistance2 = 0.0f; - Vector3 actualPosition = position; - - unsigned int numChildren = Self().GetChildCount(); - - for(unsigned int i = 0; i < numChildren; ++i) - { - Actor child = Self().GetChildAt(i); - - if(mInternalActor == child) // ignore internal actor. - { - continue; - } - - Vector3 childPosition = GetPositionOfAnchor(child, AnchorPoint::CENTER); - - Vector3 delta = childPosition - actualPosition; - - // X-axis checking (only find Actors to the [dirX] of actualPosition) - if(dirX > All) // != All,None - { - FindDirection deltaH = delta.x > 0 ? Right : Left; - if(dirX != deltaH) - { - continue; - } - } - - // Y-axis checking (only find Actors to the [dirY] of actualPosition) - if(dirY > All) // != All,None - { - FindDirection deltaV = delta.y > 0 ? Down : Up; - if(dirY != deltaV) - { - continue; - } - } - - // Z-axis checking (only find Actors to the [dirZ] of actualPosition) - if(dirZ > All) // != All,None - { - FindDirection deltaV = delta.y > 0 ? In : Out; - if(dirZ != deltaV) - { - continue; - } - } - - // compare child to closest child in terms of distance. - float distance2 = 0.0f; - - // distance2 = the Square of the relevant dimensions of delta - if(dirX != None) - { - distance2 += delta.x * delta.x; - } - - if(dirY != None) - { - distance2 += delta.y * delta.y; - } - - if(dirZ != None) - { - distance2 += delta.z * delta.z; - } - - if(closestChild) // Next time. - { - if(distance2 < closestDistance2) - { - closestChild = child; - closestDistance2 = distance2; - } - } - else // First time. - { - closestChild = child; - closestDistance2 = distance2; - } - } - - return closestChild; -} - -bool ScrollView::ScrollToSnapPoint() -{ - DALI_LOG_SCROLL_STATE("[0x%X]", this); - Vector2 stationaryVelocity = Vector2(0.0f, 0.0f); - return SnapWithVelocity(stationaryVelocity); -} - -// TODO: In situations where axes are different (X snap, Y free) -// Each axis should really have their own independent animation (time and equation) -// Consider, X axis snapping to nearest grid point (EaseOut over fixed time) -// Consider, Y axis simulating physics to arrive at a point (Physics equation over variable time) -// Currently, the axes have been split however, they both use the same EaseOut equation. -bool ScrollView::SnapWithVelocity(Vector2 velocity) -{ - // Animator takes over now, touches are assumed not to interfere. - // And if touches do interfere, then we'll stop animation, update PrePosition - // to current mScroll's properties, and then resume. - // Note: For Flicking this may work a bit different... - - float angle = atan2(velocity.y, velocity.x); - float speed2 = velocity.LengthSquared(); - AlphaFunction alphaFunction = mSnapAlphaFunction; - Vector2 positionDuration = Vector2::ONE * mSnapDuration; - float biasX = 0.5f; - float biasY = 0.5f; - FindDirection horizontal = None; - FindDirection vertical = None; - - // orthoAngleRange = Angle tolerance within the Exact N,E,S,W direction - // that will be accepted as a general N,E,S,W flick direction. - - const float orthoAngleRange = FLICK_ORTHO_ANGLE_RANGE * M_PI / 180.0f; - const float flickSpeedThreshold2 = mFlickSpeedThreshold * mFlickSpeedThreshold; - - Vector2 positionSnap = mScrollPrePosition; - - // Flick logic X Axis - - if(mRulerX->IsEnabled() && mLockAxis != LockHorizontal) - { - horizontal = All; - - if(speed2 > flickSpeedThreshold2 || // exceeds flick threshold - mInAccessibilityPan) // With AccessibilityPan its easier to move between snap positions - { - if((angle >= -orthoAngleRange) && (angle < orthoAngleRange)) // Swiping East - { - biasX = 0.0f, horizontal = Left; - - // This guards against an error where no movement occurs, due to the flick finishing - // before the update-thread has advanced mScrollPostPosition past the the previous snap point. - positionSnap.x += 1.0f; - } - else if((angle >= M_PI - orthoAngleRange) || (angle < -M_PI + orthoAngleRange)) // Swiping West - { - biasX = 1.0f, horizontal = Right; - - // This guards against an error where no movement occurs, due to the flick finishing - // before the update-thread has advanced mScrollPostPosition past the the previous snap point. - positionSnap.x -= 1.0f; - } - } - } - - // Flick logic Y Axis - - if(mRulerY->IsEnabled() && mLockAxis != LockVertical) - { - vertical = All; - - if(speed2 > flickSpeedThreshold2 || // exceeds flick threshold - mInAccessibilityPan) // With AccessibilityPan its easier to move between snap positions - { - if((angle >= M_PI_2 - orthoAngleRange) && (angle < M_PI_2 + orthoAngleRange)) // Swiping South - { - biasY = 0.0f, vertical = Up; - } - else if((angle >= -M_PI_2 - orthoAngleRange) && (angle < -M_PI_2 + orthoAngleRange)) // Swiping North - { - biasY = 1.0f, vertical = Down; - } - } - } - - // isFlick: Whether this gesture is a flick or not. - bool isFlick = (horizontal != All || vertical != All); - // isFreeFlick: Whether this gesture is a flick under free panning criteria. - bool isFreeFlick = velocity.LengthSquared() > (FREE_FLICK_SPEED_THRESHOLD * FREE_FLICK_SPEED_THRESHOLD); - - if(isFlick || isFreeFlick) - { - positionDuration = Vector2::ONE * mFlickDuration; - alphaFunction = mFlickAlphaFunction; - } - - // Calculate next positionSnap //////////////////////////////////////////////////////////// - - if(mActorAutoSnapEnabled) - { - Vector3 size = Self().GetCurrentProperty(Actor::Property::SIZE); - - Actor child = FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f), horizontal, vertical); - - if(!child && isFlick) - { - // If we conducted a direction limited search and found no actor, then just snap to the closest actor. - child = FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f)); - } - - if(child) - { - Vector2 position = Self().GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION); - - // Get center-point of the Actor. - Vector3 childPosition = GetPositionOfAnchor(child, AnchorPoint::CENTER); - - if(mRulerX->IsEnabled()) - { - positionSnap.x = position.x - childPosition.x + size.width * 0.5f; - } - if(mRulerY->IsEnabled()) - { - positionSnap.y = position.y - childPosition.y + size.height * 0.5f; - } - } - } +void ScrollView::ScrollTo(unsigned int page) +{ + ScrollTo(page, mSnapDuration); +} - Vector2 startPosition = positionSnap; - positionSnap.x = -mRulerX->Snap(-positionSnap.x, biasX); // NOTE: X & Y rulers think in -ve coordinate system. - positionSnap.y = -mRulerY->Snap(-positionSnap.y, biasY); // That is scrolling RIGHT (e.g. 100.0, 0.0) means moving LEFT. +void ScrollView::ScrollTo(unsigned int page, float duration, DirectionBias bias) +{ + Vector2 position; + unsigned int volume; + unsigned int libraries; - Vector2 clampDelta(Vector2::ZERO); - ClampPosition(positionSnap); + // The position to scroll to is continuous and linear + // unless a domain has been enabled on the X axis. + // or if WrapMode has been enabled. + bool carryX = mRulerX->GetDomain().enabled | mWrapMode; + bool carryY = mRulerY->GetDomain().enabled | mWrapMode; - if((mRulerX->GetType() == Ruler::FREE || mRulerY->GetType() == Ruler::FREE) && isFreeFlick && !mActorAutoSnapEnabled) - { - // Calculate target position based on velocity of flick. + position.x = mRulerX->GetPositionFromPage(page, volume, carryX); + position.y = mRulerY->GetPositionFromPage(volume, libraries, carryY); - // a = Deceleration (Set to diagonal stage length * friction coefficient) - // u = Initial Velocity (Flick velocity) - // v = 0 (Final Velocity) - // t = Time (Velocity / Deceleration) - Vector2 stageSize = Stage::GetCurrent().GetSize(); - float stageLength = Vector3(stageSize.x, stageSize.y, 0.0f).Length(); - float a = (stageLength * mFrictionCoefficient); - Vector3 u = Vector3(velocity.x, velocity.y, 0.0f) * mFlickSpeedCoefficient; - float speed = u.Length(); - u /= speed; + ScrollTo(position, duration, bias, bias); +} - // TODO: Change this to a decay function. (faster you flick, the slower it should be) - speed = std::min(speed, stageLength * mMaxFlickSpeed); - u *= speed; - alphaFunction = ConstantDecelerationAlphaFunction; +void ScrollView::ScrollTo(Actor& actor) +{ + ScrollTo(actor, mSnapDuration); +} - float t = speed / a; +void ScrollView::ScrollTo(Actor& actor, float duration) +{ + DALI_ASSERT_ALWAYS(actor.GetParent() == Self()); - if(mRulerX->IsEnabled() && mRulerX->GetType() == Ruler::FREE) - { - positionSnap.x += t * u.x * 0.5f; - } + Actor self = Self(); + Vector3 size = self.GetCurrentProperty(Actor::Property::SIZE); + Vector3 position = actor.GetCurrentProperty(Actor::Property::POSITION); + Vector2 prePosition = GetPropertyPrePosition(); + position.GetVectorXY() -= prePosition; - if(mRulerY->IsEnabled() && mRulerY->GetType() == Ruler::FREE) - { - positionSnap.y += t * u.y * 0.5f; - } + ScrollTo(Vector2(position.x - size.width * 0.5f, position.y - size.height * 0.5f), duration); +} - clampDelta = positionSnap; - ClampPosition(positionSnap); - if((positionSnap - startPosition).LengthSquared() > Math::MACHINE_EPSILON_0) - { - clampDelta -= positionSnap; - clampDelta.x = clampDelta.x > 0.0f ? std::min(clampDelta.x, mMaxOvershoot.x) : std::max(clampDelta.x, -mMaxOvershoot.x); - clampDelta.y = clampDelta.y > 0.0f ? std::min(clampDelta.y, mMaxOvershoot.y) : std::max(clampDelta.y, -mMaxOvershoot.y); - } - else - { - clampDelta = Vector2::ZERO; - } +Actor ScrollView::FindClosestActor() +{ + Actor self = Self(); + Vector3 size = self.GetCurrentProperty(Actor::Property::SIZE); - // If Axis is Free and has velocity, then calculate time taken - // to reach target based on velocity in axis. - if(mRulerX->IsEnabled() && mRulerX->GetType() == Ruler::FREE) - { - float deltaX = fabsf(startPosition.x - positionSnap.x); + return FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f)); +} - if(fabsf(u.x) > Math::MACHINE_EPSILON_1) - { - positionDuration.x = fabsf(deltaX / u.x); - } - else - { - positionDuration.x = 0; - } - } +Actor ScrollView::FindClosestActorToPosition(const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ) +{ + return ::FindClosestActorToPosition(Self(), mInternalActor, position, dirX, dirY, dirZ); +} - if(mRulerY->IsEnabled() && mRulerY->GetType() == Ruler::FREE) - { - float deltaY = fabsf(startPosition.y - positionSnap.y); +bool ScrollView::ScrollToSnapPoint() +{ + DALI_LOG_SCROLL_STATE("[0x%X]", this); + Vector2 stationaryVelocity = Vector2(0.0f, 0.0f); + return SnapWithVelocity(stationaryVelocity); +} - if(fabsf(u.y) > Math::MACHINE_EPSILON_1) - { - positionDuration.y = fabsf(deltaY / u.y); - } - else - { - positionDuration.y = 0; - } - } - } +bool ScrollView::SnapWithVelocity(Vector2 velocity) +{ + Vector2 positionSnap = mScrollPrePosition; + Vector2 positionDuration = Vector2::ONE * mSnapDuration; + AlphaFunction alphaFunction = mSnapAlphaFunction; + bool isFlick; + bool isFreeFlick; - if(IsOvershootEnabled()) - { - // Scroll to the end of the overshoot only when overshoot is enabled. - positionSnap += clampDelta; - } + ::SnapWithVelocity(*this, mRulerX, mRulerY, mLockAxis, velocity, mMaxOvershoot, positionSnap, positionDuration, alphaFunction, mInAccessibilityPan, isFlick, isFreeFlick); bool animating = AnimateTo(positionSnap, positionDuration, alphaFunction, false, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE, isFlick || isFreeFlick ? FLICK : SNAP); @@ -1698,7 +1137,7 @@ bool ScrollView::AnimateTo(const Vector2& position, const Vector2& positionDurat // Position Delta /////////////////////////////////////////////////////// if(positionChanged) { - UpdateMainInternalConstraint(); + mConstraints.UpdateMainInternalConstraint(*this); if(mWrapMode && findShortcuts) { // In Wrap Mode, the shortest distance is a little less intuitive... @@ -1771,7 +1210,7 @@ void ScrollView::EnableScrollOvershoot(bool enable) } } - UpdateMainInternalConstraint(); + mConstraints.UpdateMainInternalConstraint(*this); } void ScrollView::AddOverlay(Actor actor) @@ -1823,10 +1262,17 @@ Toolkit::ScrollView::SnapStartedSignalType& ScrollView::SnapStartedSignal() return mSnapStartedSignal; } -void ScrollView::AccessibleImpl::EnsureChildVisible(Actor child) +bool ScrollView::AccessibleImpl::ScrollToChild(Actor child) { - auto scrollView = Dali::Toolkit::ScrollView::DownCast(self); - scrollView.ScrollTo(child); + auto scrollView = Dali::Toolkit::ScrollView::DownCast(Self()); + if(Toolkit::GetImpl(scrollView).FindClosestActor() == child) + { + return false; + } + + // FIXME: ScrollTo does not work (snaps back to original position) + scrollView.ScrollTo(child, scrollView.GetScrollFlickDuration()); + return true; } void ScrollView::FindAndUnbindActor(Actor child) @@ -1940,7 +1386,7 @@ bool ScrollView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* void ScrollView::OnSizeAnimation(Animation& animation, const Vector3& targetSize) { // need to update domain properties for new size - UpdatePropertyDomain(); + ScrollViewPropertyHandler::UpdatePropertyDomain(*this); } void ScrollView::OnSizeSet(const Vector3& size) @@ -1955,8 +1401,8 @@ void ScrollView::OnSizeSet(const Vector3& size) mMaxOvershoot = mUserMaxOvershoot; } } - UpdatePropertyDomain(); - UpdateMainInternalConstraint(); + ScrollViewPropertyHandler::UpdatePropertyDomain(*this); + mConstraints.UpdateMainInternalConstraint(*this); if(IsOvershootEnabled()) { mOvershootIndicator->Reset(); @@ -2496,7 +1942,7 @@ void ScrollView::OnPan(const PanGesture& gesture) self.SetProperty(Toolkit::ScrollView::Property::PANNING, true); self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(position.x, position.y, 0.0f)); - UpdateMainInternalConstraint(); + mConstraints.UpdateMainInternalConstraint(*this); Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle(); if(scrollBar && mTransientScrollBar) { @@ -2539,9 +1985,9 @@ void ScrollView::OnPan(const PanGesture& gesture) mPanning = false; self.SetProperty(Toolkit::ScrollView::Property::PANNING, false); - if(mScrollMainInternalPrePositionConstraint) + if(mConstraints.mScrollMainInternalPrePositionConstraint) { - mScrollMainInternalPrePositionConstraint.Remove(); + mConstraints.mScrollMainInternalPrePositionConstraint.Remove(); } Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle(); @@ -2638,45 +2084,6 @@ void ScrollView::FinishTransform() } } -Vector2 ScrollView::GetOvershoot(Vector2& position) const -{ - Vector3 size = Self().GetCurrentProperty(Actor::Property::SIZE); - Vector2 overshoot; - - const RulerDomain rulerDomainX = mRulerX->GetDomain(); - const RulerDomain rulerDomainY = mRulerY->GetDomain(); - - if(mRulerX->IsEnabled() && rulerDomainX.enabled) - { - const float left = rulerDomainX.min - position.x; - const float right = size.width - rulerDomainX.max - position.x; - if(left < 0) - { - overshoot.x = left; - } - else if(right > 0) - { - overshoot.x = right; - } - } - - if(mRulerY->IsEnabled() && rulerDomainY.enabled) - { - const float top = rulerDomainY.min - position.y; - const float bottom = size.height - rulerDomainY.max - position.y; - if(top < 0) - { - overshoot.y = top; - } - else if(bottom > 0) - { - overshoot.y = bottom; - } - } - - return overshoot; -} - bool ScrollView::OnAccessibilityPan(PanGesture gesture) { // Keep track of whether this is an AccessibilityPan @@ -2697,8 +2104,7 @@ void ScrollView::ClampPosition(Vector2& position, ClampState2D& clamped) const { Vector3 size = Self().GetCurrentProperty(Actor::Property::SIZE); - position.x = -mRulerX->Clamp(-position.x, size.width, 1.0f, clamped.x); // NOTE: X & Y rulers think in -ve coordinate system. - position.y = -mRulerY->Clamp(-position.y, size.height, 1.0f, clamped.y); // That is scrolling RIGHT (e.g. 100.0, 0.0) means moving LEFT. + ::ClampPosition(size, mRulerX, mRulerY, position, clamped); } void ScrollView::WrapPosition(Vector2& position) const @@ -2720,328 +2126,39 @@ void ScrollView::WrapPosition(Vector2& position) const } } -void ScrollView::UpdateMainInternalConstraint() -{ - // TODO: Only update the constraints which have changed, rather than remove all and add all again. - // Requires a dali-core ApplyConstraintAt, or a ReplaceConstraint. The former is probably more flexible. - Actor self = Self(); - PanGestureDetector detector(GetPanGestureDetector()); - - if(mScrollMainInternalPositionConstraint) - { - mScrollMainInternalPositionConstraint.Remove(); - mScrollMainInternalDeltaConstraint.Remove(); - mScrollMainInternalFinalConstraint.Remove(); - mScrollMainInternalRelativeConstraint.Remove(); - mScrollMainInternalDomainConstraint.Remove(); - mScrollMainInternalPrePositionMaxConstraint.Remove(); - } - if(mScrollMainInternalPrePositionConstraint) - { - mScrollMainInternalPrePositionConstraint.Remove(); - } - - // TODO: It's probably better to use a local displacement value as this will give a displacement when scrolling just commences - // but we need to make sure than the gesture system gives displacement since last frame (60Hz), not displacement since last touch event (90Hz). - - // 1. First calculate the pre-position (this is the scroll position if no clamping has taken place) - Vector2 initialPanMask = Vector2(mRulerX->IsEnabled() ? 1.0f : 0.0f, mRulerY->IsEnabled() ? 1.0f : 0.0f); - - if(mLockAxis == LockVertical) - { - initialPanMask.y = 0.0f; - } - else if(mLockAxis == LockHorizontal) - { - initialPanMask.x = 0.0f; - } - - if(mPanning) - { - mScrollMainInternalPrePositionConstraint = Constraint::New(self, - Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, - InternalPrePositionConstraint(mPanStartPosition, - initialPanMask, - mAxisAutoLock, - mAxisAutoLockGradient, - mLockAxis, - mMaxOvershoot, - mRulerX, - mRulerY)); - mScrollMainInternalPrePositionConstraint.AddSource(Source(detector, PanGestureDetector::Property::LOCAL_POSITION)); - mScrollMainInternalPrePositionConstraint.AddSource(Source(detector, PanGestureDetector::Property::PANNING)); - mScrollMainInternalPrePositionConstraint.AddSource(Source(self, Actor::Property::SIZE)); - mScrollMainInternalPrePositionConstraint.Apply(); - } - - // 2. Second calculate the clamped position (actual position) - mScrollMainInternalPositionConstraint = Constraint::New(self, - Toolkit::ScrollView::Property::SCROLL_POSITION, - InternalPositionConstraint(mRulerX->GetDomain(), - mRulerY->GetDomain(), - mWrapMode)); - mScrollMainInternalPositionConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION)); - mScrollMainInternalPositionConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN)); - mScrollMainInternalPositionConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX)); - mScrollMainInternalPositionConstraint.AddSource(Source(self, Actor::Property::SIZE)); - mScrollMainInternalPositionConstraint.Apply(); - - mScrollMainInternalDeltaConstraint = Constraint::New(self, Toolkit::ScrollView::Property::SCROLL_POSITION_DELTA, InternalPositionDeltaConstraint); - mScrollMainInternalDeltaConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION)); - mScrollMainInternalDeltaConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET)); - mScrollMainInternalDeltaConstraint.Apply(); - - mScrollMainInternalFinalConstraint = Constraint::New(self, Toolkit::ScrollView::Property::SCROLL_FINAL, InternalFinalConstraint(FinalDefaultAlphaFunction, FinalDefaultAlphaFunction)); - mScrollMainInternalFinalConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION)); - mScrollMainInternalFinalConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::OVERSHOOT_X)); - mScrollMainInternalFinalConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::OVERSHOOT_Y)); - mScrollMainInternalFinalConstraint.Apply(); - - mScrollMainInternalRelativeConstraint = Constraint::New(self, Toolkit::Scrollable::Property::SCROLL_RELATIVE_POSITION, InternalRelativePositionConstraint); - mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION)); - mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN)); - mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX)); - mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Actor::Property::SIZE)); - mScrollMainInternalRelativeConstraint.Apply(); - - mScrollMainInternalDomainConstraint = Constraint::New(self, Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE, InternalScrollDomainConstraint); - mScrollMainInternalDomainConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN)); - mScrollMainInternalDomainConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX)); - mScrollMainInternalDomainConstraint.AddSource(LocalSource(Actor::Property::SIZE)); - mScrollMainInternalDomainConstraint.Apply(); - - mScrollMainInternalPrePositionMaxConstraint = Constraint::New(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX, InternalPrePositionMaxConstraint); - mScrollMainInternalPrePositionMaxConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX)); - mScrollMainInternalPrePositionMaxConstraint.AddSource(LocalSource(Actor::Property::SIZE)); - mScrollMainInternalPrePositionMaxConstraint.Apply(); - - // When panning we want to make sure overshoot values are affected by pre position and post position - SetOvershootConstraintsEnabled(!mWrapMode); -} - -void ScrollView::SetOvershootConstraintsEnabled(bool enabled) -{ - Actor self(Self()); - // remove and reset, it may now be in wrong order with the main internal constraints - if(mScrollMainInternalOvershootXConstraint) - { - mScrollMainInternalOvershootXConstraint.Remove(); - mScrollMainInternalOvershootXConstraint.Reset(); - mScrollMainInternalOvershootYConstraint.Remove(); - mScrollMainInternalOvershootYConstraint.Reset(); - } - if(enabled) - { - mScrollMainInternalOvershootXConstraint = Constraint::New(self, Toolkit::ScrollView::Property::OVERSHOOT_X, OvershootXConstraint(mMaxOvershoot.x)); - mScrollMainInternalOvershootXConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION)); - mScrollMainInternalOvershootXConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION)); - mScrollMainInternalOvershootXConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL)); - mScrollMainInternalOvershootXConstraint.Apply(); - - mScrollMainInternalOvershootYConstraint = Constraint::New(self, Toolkit::ScrollView::Property::OVERSHOOT_Y, OvershootYConstraint(mMaxOvershoot.y)); - mScrollMainInternalOvershootYConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION)); - mScrollMainInternalOvershootYConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION)); - mScrollMainInternalOvershootYConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL)); - mScrollMainInternalOvershootYConstraint.Apply(); - } - else - { - self.SetProperty(Toolkit::ScrollView::Property::OVERSHOOT_X, 0.0f); - self.SetProperty(Toolkit::ScrollView::Property::OVERSHOOT_Y, 0.0f); - } -} - -void ScrollView::SetInternalConstraints() -{ - // Internal constraints (applied to target ScrollBase Actor itself) ///////// - UpdateMainInternalConstraint(); - - // User definable constraints to apply to all child actors ////////////////// - Actor self = Self(); - - // Apply some default constraints to ScrollView & its bound actors - // Movement + Wrap function - - Constraint constraint; - - // MoveActor (scrolling) - constraint = Constraint::New(self, Actor::Property::POSITION, MoveActorConstraint); - constraint.AddSource(Source(self, Toolkit::ScrollView::Property::SCROLL_POSITION)); - constraint.SetRemoveAction(Constraint::DISCARD); - ApplyConstraintToBoundActors(constraint); - - // WrapActor (wrap functionality) - constraint = Constraint::New(self, Actor::Property::POSITION, WrapActorConstraint); - constraint.AddSource(LocalSource(Actor::Property::SCALE)); - constraint.AddSource(LocalSource(Actor::Property::ANCHOR_POINT)); - constraint.AddSource(LocalSource(Actor::Property::SIZE)); - constraint.AddSource(Source(self, Toolkit::Scrollable::Property::SCROLL_POSITION_MIN)); - constraint.AddSource(Source(self, Toolkit::Scrollable::Property::SCROLL_POSITION_MAX)); - constraint.AddSource(Source(self, Toolkit::ScrollView::Property::WRAP)); - constraint.SetRemoveAction(Constraint::DISCARD); - ApplyConstraintToBoundActors(constraint); -} - void ScrollView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value) { - Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object)); - - if(scrollView) - { - ScrollView& scrollViewImpl(GetImpl(scrollView)); - switch(index) - { - case Toolkit::ScrollView::Property::WRAP_ENABLED: - { - scrollViewImpl.SetWrapMode(value.Get()); - break; - } - case Toolkit::ScrollView::Property::PANNING_ENABLED: - { - scrollViewImpl.SetScrollSensitive(value.Get()); - break; - } - case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED: - { - scrollViewImpl.SetAxisAutoLock(value.Get()); - break; - } - case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP: - { - scrollViewImpl.SetWheelScrollDistanceStep(value.Get()); - break; - } - case Toolkit::ScrollView::Property::SCROLL_MODE: - { - const Property::Map* map = value.GetMap(); - if(map) - { - scrollViewImpl.SetScrollMode(*map); - } - } - } - } + ScrollViewPropertyHandler::Set(object, index, value); } Property::Value ScrollView::GetProperty(BaseObject* object, Property::Index index) { - Property::Value value; - - Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object)); - - if(scrollView) - { - ScrollView& scrollViewImpl(GetImpl(scrollView)); - switch(index) - { - case Toolkit::ScrollView::Property::WRAP_ENABLED: - { - value = scrollViewImpl.GetWrapMode(); - break; - } - case Toolkit::ScrollView::Property::PANNING_ENABLED: - { - value = scrollViewImpl.GetScrollSensitive(); - break; - } - case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED: - { - value = scrollViewImpl.GetAxisAutoLock(); - break; - } - case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP: - { - value = scrollViewImpl.GetWheelScrollDistanceStep(); - break; - } - } - } - - return value; + return ScrollViewPropertyHandler::Get(object, index); } -void ScrollView::SetScrollMode(const Property::Map& scrollModeMap) +ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient) { - Toolkit::RulerPtr rulerX, rulerY; - - // Check the scroll mode in the X axis - bool xAxisScrollEnabled = true; - Property::Value* valuePtr = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_ENABLED, "xAxisScrollEnabled"); - if(valuePtr && valuePtr->GetType() == Property::BOOLEAN) - { - valuePtr->Get(xAxisScrollEnabled); - } - - if(!xAxisScrollEnabled) - { - // Default ruler and disabled - rulerX = new Toolkit::DefaultRuler(); - rulerX->Disable(); - } - else + if(panDelta.LengthSquared() > AUTOLOCK_AXIS_MINIMUM_DISTANCE2 && + currentLockAxis == ScrollView::LockPossible) { - valuePtr = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SNAP_TO_INTERVAL, "xAxisSnapToInterval"); - float xAxisSnapToInterval = 0.0f; - if(valuePtr && valuePtr->Get(xAxisSnapToInterval)) - { - // Fixed ruler and enabled - rulerX = new Toolkit::FixedRuler(xAxisSnapToInterval); - } - else - { - // Default ruler and enabled - rulerX = new Toolkit::DefaultRuler(); - } - - valuePtr = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_BOUNDARY, "xAxisScrollBoundary"); - float xAxisScrollBoundary = 0.0f; - if(valuePtr && valuePtr->Get(xAxisScrollBoundary)) + float dx = fabsf(panDelta.x); + float dy = fabsf(panDelta.y); + if(dx * lockGradient >= dy) { - // By default ruler domain is disabled unless set - rulerX->SetDomain(Toolkit::RulerDomain(0, xAxisScrollBoundary, true)); + // 0.36:1 gradient to the horizontal (deviate < 20 degrees) + currentLockAxis = ScrollView::LockVertical; } - } - - // Check the scroll mode in the Y axis - bool yAxisScrollEnabled = true; - valuePtr = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_ENABLED, "yAxisScrollEnabled"); - if(valuePtr && valuePtr->GetType() == Property::BOOLEAN) - { - valuePtr->Get(yAxisScrollEnabled); - } - - if(!yAxisScrollEnabled) - { - // Default ruler and disabled - rulerY = new Toolkit::DefaultRuler(); - rulerY->Disable(); - } - else - { - valuePtr = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SNAP_TO_INTERVAL, "yAxisSnapToInterval"); - float yAxisSnapToInterval = 0.0f; - if(valuePtr && valuePtr->Get(yAxisSnapToInterval)) + else if(dy * lockGradient > dx) { - // Fixed ruler and enabled - rulerY = new Toolkit::FixedRuler(yAxisSnapToInterval); + // 0.36:1 gradient to the vertical (deviate < 20 degrees) + currentLockAxis = ScrollView::LockHorizontal; } else { - // Default ruler and enabled - rulerY = new Toolkit::DefaultRuler(); - } - - valuePtr = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_BOUNDARY, "yAxisScrollBoundary"); - float yAxisScrollBoundary = 0.0f; - if(valuePtr && valuePtr->Get(yAxisScrollBoundary)) - { - // By default ruler domain is disabled unless set - rulerY->SetDomain(Toolkit::RulerDomain(0, yAxisScrollBoundary, true)); + currentLockAxis = ScrollView::LockNone; } } - - SetRulerX(rulerX); - SetRulerY(rulerY); + return currentLockAxis; } } // namespace Internal