2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-constraints.h>
22 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-view-constraints.h>
23 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.h>
34 float FinalDefaultAlphaFunction(float offset)
40 * Internal Relative position Constraint
41 * Generates the relative position value of the scroll view
42 * based on the absolute position, and it's relation to the
43 * scroll domain. This is a value from 0.0f to 1.0f in each
44 * scroll position axis.
46 void InternalRelativePositionConstraint(Vector2& relativePosition, const PropertyInputContainer& inputs)
48 Vector2 position = -inputs[0]->GetVector2();
49 const Vector2& min = inputs[1]->GetVector2();
50 const Vector2& max = inputs[2]->GetVector2();
51 const Vector3& size = inputs[3]->GetVector3();
53 position.x = WrapInDomain(position.x, min.x, max.x);
54 position.y = WrapInDomain(position.y, min.y, max.y);
56 Vector2 domainSize = (max - min) - size.GetVectorXY();
58 relativePosition.x = domainSize.x > Math::MACHINE_EPSILON_1 ? fabsf((position.x - min.x) / domainSize.x) : 0.0f;
59 relativePosition.y = domainSize.y > Math::MACHINE_EPSILON_1 ? fabsf((position.y - min.y) / domainSize.y) : 0.0f;
63 * Internal scroll domain Constraint
64 * Generates the scroll domain of the scroll view.
66 void InternalScrollDomainConstraint(Vector2& scrollDomain, const PropertyInputContainer& inputs)
68 const Vector2& min = inputs[0]->GetVector2();
69 const Vector2& max = inputs[1]->GetVector2();
70 const Vector3& size = inputs[2]->GetVector3();
72 scrollDomain = (max - min) - size.GetVectorXY();
76 * Internal maximum scroll position Constraint
77 * Generates the maximum scroll position of the scroll view.
79 void InternalPrePositionMaxConstraint(Vector2& scrollMax, const PropertyInputContainer& inputs)
81 const Vector2& max = inputs[0]->GetVector2();
82 const Vector3& size = inputs[1]->GetVector3();
84 scrollMax = max - size.GetVectorXY();
88 * Internal Pre-Position Property Constraint.
90 * Generates position property based on current position + gesture displacement.
91 * Or generates position property based on positionX/Y.
92 * Note: This is the position prior to any clamping at scroll boundaries.
94 struct InternalPrePositionConstraint
96 InternalPrePositionConstraint(const Vector2& initialPanPosition,
97 const Vector2& initialPanMask,
99 float axisAutoLockGradient,
100 ScrollView::LockAxis initialLockAxis,
101 const Vector2& maxOvershoot,
102 const RulerPtr& rulerX,
103 const RulerPtr& rulerY)
104 : mLocalStart(initialPanPosition),
105 mInitialPanMask(initialPanMask),
106 mMaxOvershoot(maxOvershoot),
107 mAxisAutoLockGradient(axisAutoLockGradient),
108 mLockAxis(initialLockAxis),
109 mAxisAutoLock(axisAutoLock),
112 const RulerDomain& rulerDomainX = rulerX->GetDomain();
113 const RulerDomain& rulerDomainY = rulerY->GetDomain();
114 mDomainMin = Vector2(rulerDomainX.min, -rulerDomainY.min);
115 mDomainMax = Vector2(-rulerDomainX.max, -rulerDomainY.max);
116 mClampX = rulerDomainX.enabled;
117 mClampY = rulerDomainY.enabled;
118 mFixedRulerX = rulerX->GetType() == Ruler::FIXED;
119 mFixedRulerY = rulerY->GetType() == Ruler::FIXED;
122 void operator()(Vector2& scrollPostPosition, const PropertyInputContainer& inputs)
124 const Vector2& panPosition = inputs[0]->GetVector2();
125 const bool& inGesture = inputs[1]->GetBoolean();
127 // First check if we are within a gesture.
128 // The ScrollView may have received a start gesture from ::OnPan()
129 // while the finish gesture is received now in this constraint.
130 // This gesture must then be rejected as the value will be "old".
131 // Typically the last value from the end of the last gesture.
132 // If we are rejecting the gesture, we simply don't modify the constraint target.
137 mPrePosition = scrollPostPosition;
138 mStartPosition = mPrePosition;
139 mCurrentPanMask = mInitialPanMask;
143 // Calculate Deltas...
144 const Vector2& currentPosition = panPosition;
145 Vector2 panDelta(currentPosition - mLocalStart);
147 // Axis Auto Lock - locks the panning to the horizontal or vertical axis if the pan
148 // appears mostly horizontal or mostly vertical respectively...
151 mLockAxis = GetLockAxis(panDelta, mLockAxis, mAxisAutoLockGradient);
152 if(mLockAxis == ScrollView::LockVertical)
154 mCurrentPanMask.y = 0.0f;
156 else if(mLockAxis == ScrollView::LockHorizontal)
158 mCurrentPanMask.x = 0.0f;
162 // Restrict deltas based on ruler enable/disable and axis-lock state...
163 panDelta *= mCurrentPanMask;
165 // Perform Position transform based on input deltas...
166 scrollPostPosition = mPrePosition;
167 scrollPostPosition += panDelta;
169 // if no wrapping then clamp preposition to maximum overshoot amount
170 const Vector3& size = inputs[2]->GetVector3();
173 float newXPosition = Clamp(scrollPostPosition.x, (mDomainMax.x + size.x) - mMaxOvershoot.x, mDomainMin.x + mMaxOvershoot.x);
174 if((newXPosition < scrollPostPosition.x - Math::MACHINE_EPSILON_1) || (newXPosition > scrollPostPosition.x + Math::MACHINE_EPSILON_1))
176 mPrePosition.x = newXPosition;
177 mLocalStart.x = panPosition.x;
179 scrollPostPosition.x = newXPosition;
183 float newYPosition = Clamp(scrollPostPosition.y, (mDomainMax.y + size.y) - mMaxOvershoot.y, mDomainMin.y + mMaxOvershoot.y);
184 if((newYPosition < scrollPostPosition.y - Math::MACHINE_EPSILON_1) || (newYPosition > scrollPostPosition.y + Math::MACHINE_EPSILON_1))
186 mPrePosition.y = newYPosition;
187 mLocalStart.y = panPosition.y;
189 scrollPostPosition.y = newYPosition;
192 // If we are using a fixed ruler in a particular axis, limit the maximum pages scrolled on that axis.
193 if(mFixedRulerX || mFixedRulerY)
195 // Here we limit the maximum amount that can be moved from the starting position of the gesture to one page.
196 // We do this only if we have a fixed ruler (on that axis) and the mode is enabled.
197 // Note: 1.0f is subtracted to keep the value within one page size (otherwise we stray on to the page after).
198 // Note: A further 1.0f is subtracted to handle a compensation that happens later within the flick handling code in SnapWithVelocity().
199 // When a flick is completed, an adjustment of 1.0f is sometimes made to allow for the scenario where:
200 // A flick finishes before the update thread has advanced the scroll position past the previous snap point.
201 Vector2 viewPageSizeLimit(size.x - (1.0f + 1.0f), size.y - (1.0f - 1.0f));
202 Vector2 minPosition(mStartPosition.x - viewPageSizeLimit.x, mStartPosition.y - viewPageSizeLimit.y);
203 Vector2 maxPosition(mStartPosition.x + viewPageSizeLimit.x, mStartPosition.y + viewPageSizeLimit.y);
207 scrollPostPosition.x = Clamp(scrollPostPosition.x, minPosition.x, maxPosition.x);
211 scrollPostPosition.y = Clamp(scrollPostPosition.y, minPosition.y, maxPosition.y);
217 Vector2 mPrePosition;
219 Vector2 mStartPosition; ///< The start position of the gesture - used to limit scroll amount (not modified by clamping).
220 Vector2 mInitialPanMask; ///< Initial pan mask (based on ruler settings).
221 Vector2 mCurrentPanMask; ///< Current pan mask that can be altered by axis lock mode.
224 Vector2 mMaxOvershoot;
226 float mAxisAutoLockGradient; ///< Set by ScrollView
227 ScrollView::LockAxis mLockAxis;
229 bool mAxisAutoLock : 1; ///< Set by ScrollView
230 bool mWasPanning : 1;
233 bool mFixedRulerX : 1;
234 bool mFixedRulerY : 1;
238 * Internal Position Property Constraint.
240 * Generates position property based on pre-position
241 * Note: This is the position after clamping.
242 * (uses result of InternalPrePositionConstraint)
244 struct InternalPositionConstraint
246 InternalPositionConstraint(const RulerDomain& domainX, const RulerDomain& domainY, bool wrap)
247 : mDomainMin(-domainX.min, -domainY.min),
248 mDomainMax(-domainX.max, -domainY.max),
249 mClampX(domainX.enabled),
250 mClampY(domainY.enabled),
255 void operator()(Vector2& position, const PropertyInputContainer& inputs)
257 position = inputs[0]->GetVector2();
258 const Vector2& size = inputs[3]->GetVector3().GetVectorXY();
259 const Vector2& min = inputs[1]->GetVector2();
260 const Vector2& max = inputs[2]->GetVector2();
264 position.x = -WrapInDomain(-position.x, min.x, max.x);
265 position.y = -WrapInDomain(-position.y, min.y, max.y);
269 // clamp post position to domain
270 position.x = mClampX ? Clamp(position.x, mDomainMax.x + size.x, mDomainMin.x) : position.x;
271 position.y = mClampY ? Clamp(position.y, mDomainMax.y + size.y, mDomainMin.y) : position.y;
283 * This constraint updates the X overshoot property using the difference
284 * SCROLL_PRE_POSITION.x and SCROLL_POSITION.x, returning a relative value between 0.0f and 1.0f
286 struct OvershootXConstraint
288 OvershootXConstraint(float maxOvershoot)
289 : mMaxOvershoot(maxOvershoot)
293 void operator()(float& current, const PropertyInputContainer& inputs)
295 if(inputs[2]->GetBoolean())
297 const Vector2& scrollPrePosition = inputs[0]->GetVector2();
298 const Vector2& scrollPostPosition = inputs[1]->GetVector2();
299 float newOvershoot = scrollPrePosition.x - scrollPostPosition.x;
300 current = (newOvershoot > 0.0f ? std::min(newOvershoot, mMaxOvershoot) : std::max(newOvershoot, -mMaxOvershoot)) / mMaxOvershoot;
312 * This constraint updates the Y overshoot property using the difference
313 * SCROLL_PRE_POSITION.y and SCROLL_POSITION.y, returning a relative value between 0.0f and 1.0f
315 struct OvershootYConstraint
317 OvershootYConstraint(float maxOvershoot)
318 : mMaxOvershoot(maxOvershoot)
322 void operator()(float& current, const PropertyInputContainer& inputs)
324 if(inputs[2]->GetBoolean())
326 const Vector2& scrollPrePosition = inputs[0]->GetVector2();
327 const Vector2& scrollPostPosition = inputs[1]->GetVector2();
328 float newOvershoot = scrollPrePosition.y - scrollPostPosition.y;
329 current = (newOvershoot > 0.0f ? std::min(newOvershoot, mMaxOvershoot) : std::max(newOvershoot, -mMaxOvershoot)) / mMaxOvershoot;
341 * Internal Position-Delta Property Constraint.
343 * Generates position-delta property based on scroll-position + scroll-offset properties.
345 void InternalPositionDeltaConstraint(Vector2& current, const PropertyInputContainer& inputs)
347 const Vector2& scrollPosition = inputs[0]->GetVector2();
348 const Vector2& scrollOffset = inputs[1]->GetVector2();
350 current = scrollPosition + scrollOffset;
354 * Internal Final Position Constraint
355 * The position of content is:
356 * of scroll-position + f(scroll-overshoot)
357 * where f(...) function defines how overshoot
358 * should affect final-position.
360 struct InternalFinalConstraint
362 InternalFinalConstraint(AlphaFunctionPrototype functionX,
363 AlphaFunctionPrototype functionY)
364 : mFunctionX(functionX),
365 mFunctionY(functionY)
369 void operator()(Vector2& current, const PropertyInputContainer& inputs)
371 const float& overshootx = inputs[1]->GetFloat();
372 const float& overshooty = inputs[2]->GetFloat();
373 Vector2 offset(mFunctionX(overshootx),
374 mFunctionY(overshooty));
376 current = inputs[0]->GetVector2() - offset;
379 AlphaFunctionPrototype mFunctionX;
380 AlphaFunctionPrototype mFunctionY;
385 void ScrollViewConstraints::UpdateMainInternalConstraint(ScrollView& scrollView)
387 // TODO: Only update the constraints which have changed, rather than remove all and add all again.
388 // Requires a dali-core ApplyConstraintAt, or a ReplaceConstraint. The former is probably more flexible.
389 Actor scrollViewActor = scrollView.Self();
390 PanGestureDetector detector(scrollView.GetPanGestureDetector());
392 if(mScrollMainInternalPositionConstraint)
394 mScrollMainInternalPositionConstraint.Remove();
395 mScrollMainInternalDeltaConstraint.Remove();
396 mScrollMainInternalFinalConstraint.Remove();
397 mScrollMainInternalRelativeConstraint.Remove();
398 mScrollMainInternalDomainConstraint.Remove();
399 mScrollMainInternalPrePositionMaxConstraint.Remove();
401 if(mScrollMainInternalPrePositionConstraint)
403 mScrollMainInternalPrePositionConstraint.Remove();
406 // TODO: It's probably better to use a local displacement value as this will give a displacement when scrolling just commences
407 // but we need to make sure than the gesture system gives displacement since last frame (60Hz), not displacement since last touch event (90Hz).
409 // 1. First calculate the pre-position (this is the scroll position if no clamping has taken place)
410 Vector2 initialPanMask = Vector2(scrollView.mRulerX->IsEnabled() ? 1.0f : 0.0f, scrollView.mRulerY->IsEnabled() ? 1.0f : 0.0f);
412 if(scrollView.mLockAxis == ScrollView::LockVertical)
414 initialPanMask.y = 0.0f;
416 else if(scrollView.mLockAxis == ScrollView::LockHorizontal)
418 initialPanMask.x = 0.0f;
421 if(scrollView.mPanning)
423 mScrollMainInternalPrePositionConstraint = Constraint::New<Vector2>(scrollViewActor,
424 Toolkit::ScrollView::Property::SCROLL_PRE_POSITION,
425 InternalPrePositionConstraint(scrollView.mPanStartPosition,
427 scrollView.mAxisAutoLock,
428 scrollView.mAxisAutoLockGradient,
429 scrollView.mLockAxis,
430 scrollView.mMaxOvershoot,
432 scrollView.mRulerY));
433 mScrollMainInternalPrePositionConstraint.AddSource(Source(detector, PanGestureDetector::Property::LOCAL_POSITION));
434 mScrollMainInternalPrePositionConstraint.AddSource(Source(detector, PanGestureDetector::Property::PANNING));
435 mScrollMainInternalPrePositionConstraint.AddSource(Source(scrollViewActor, Actor::Property::SIZE));
436 mScrollMainInternalPrePositionConstraint.Apply();
439 // 2. Second calculate the clamped position (actual position)
440 mScrollMainInternalPositionConstraint = Constraint::New<Vector2>(scrollViewActor,
441 Toolkit::ScrollView::Property::SCROLL_POSITION,
442 InternalPositionConstraint(scrollView.mRulerX->GetDomain(),
443 scrollView.mRulerY->GetDomain(),
444 scrollView.mWrapMode));
445 mScrollMainInternalPositionConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION));
446 mScrollMainInternalPositionConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN));
447 mScrollMainInternalPositionConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX));
448 mScrollMainInternalPositionConstraint.AddSource(Source(scrollViewActor, Actor::Property::SIZE));
449 mScrollMainInternalPositionConstraint.Apply();
451 mScrollMainInternalDeltaConstraint = Constraint::New<Vector2>(scrollViewActor, Toolkit::ScrollView::Property::SCROLL_POSITION_DELTA, InternalPositionDeltaConstraint);
452 mScrollMainInternalDeltaConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION));
453 mScrollMainInternalDeltaConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET));
454 mScrollMainInternalDeltaConstraint.Apply();
456 mScrollMainInternalFinalConstraint = Constraint::New<Vector2>(scrollViewActor, Toolkit::ScrollView::Property::SCROLL_FINAL, InternalFinalConstraint(FinalDefaultAlphaFunction, FinalDefaultAlphaFunction));
457 mScrollMainInternalFinalConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION));
458 mScrollMainInternalFinalConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::OVERSHOOT_X));
459 mScrollMainInternalFinalConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::OVERSHOOT_Y));
460 mScrollMainInternalFinalConstraint.Apply();
462 mScrollMainInternalRelativeConstraint = Constraint::New<Vector2>(scrollViewActor, Toolkit::Scrollable::Property::SCROLL_RELATIVE_POSITION, InternalRelativePositionConstraint);
463 mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION));
464 mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN));
465 mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX));
466 mScrollMainInternalRelativeConstraint.AddSource(LocalSource(Actor::Property::SIZE));
467 mScrollMainInternalRelativeConstraint.Apply();
469 mScrollMainInternalDomainConstraint = Constraint::New<Vector2>(scrollViewActor, Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE, InternalScrollDomainConstraint);
470 mScrollMainInternalDomainConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN));
471 mScrollMainInternalDomainConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX));
472 mScrollMainInternalDomainConstraint.AddSource(LocalSource(Actor::Property::SIZE));
473 mScrollMainInternalDomainConstraint.Apply();
475 mScrollMainInternalPrePositionMaxConstraint = Constraint::New<Vector2>(scrollViewActor, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX, InternalPrePositionMaxConstraint);
476 mScrollMainInternalPrePositionMaxConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX));
477 mScrollMainInternalPrePositionMaxConstraint.AddSource(LocalSource(Actor::Property::SIZE));
478 mScrollMainInternalPrePositionMaxConstraint.Apply();
480 // When panning we want to make sure overshoot values are affected by pre position and post position
481 SetOvershootConstraintsEnabled(scrollView, !scrollView.mWrapMode);
484 void ScrollViewConstraints::SetOvershootConstraintsEnabled(ScrollView& scrollView, bool enabled)
486 Actor scrollViewActor(scrollView.Self());
487 // remove and reset, it may now be in wrong order with the main internal constraints
488 if(mScrollMainInternalOvershootXConstraint)
490 mScrollMainInternalOvershootXConstraint.Remove();
491 mScrollMainInternalOvershootXConstraint.Reset();
492 mScrollMainInternalOvershootYConstraint.Remove();
493 mScrollMainInternalOvershootYConstraint.Reset();
497 mScrollMainInternalOvershootXConstraint = Constraint::New<float>(scrollViewActor, Toolkit::ScrollView::Property::OVERSHOOT_X, OvershootXConstraint(scrollView.mMaxOvershoot.x));
498 mScrollMainInternalOvershootXConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION));
499 mScrollMainInternalOvershootXConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION));
500 mScrollMainInternalOvershootXConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL));
501 mScrollMainInternalOvershootXConstraint.Apply();
503 mScrollMainInternalOvershootYConstraint = Constraint::New<float>(scrollViewActor, Toolkit::ScrollView::Property::OVERSHOOT_Y, OvershootYConstraint(scrollView.mMaxOvershoot.y));
504 mScrollMainInternalOvershootYConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION));
505 mScrollMainInternalOvershootYConstraint.AddSource(LocalSource(Toolkit::ScrollView::Property::SCROLL_POSITION));
506 mScrollMainInternalOvershootYConstraint.AddSource(LocalSource(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL));
507 mScrollMainInternalOvershootYConstraint.Apply();
511 scrollViewActor.SetProperty(Toolkit::ScrollView::Property::OVERSHOOT_X, 0.0f);
512 scrollViewActor.SetProperty(Toolkit::ScrollView::Property::OVERSHOOT_Y, 0.0f);
516 void ScrollViewConstraints::SetInternalConstraints(ScrollView& scrollView)
518 // Internal constraints (applied to target ScrollBase Actor itself) /////////
519 UpdateMainInternalConstraint(scrollView);
521 // User definable constraints to apply to all child actors //////////////////
522 Actor scrollViewActor = scrollView.Self();
524 // Apply some default constraints to ScrollView & its bound actors
525 // Movement + Wrap function
527 Constraint constraint;
529 // MoveActor (scrolling)
530 constraint = Constraint::New<Vector3>(scrollViewActor, Actor::Property::POSITION, MoveActorConstraint);
531 constraint.AddSource(Source(scrollViewActor, Toolkit::ScrollView::Property::SCROLL_POSITION));
532 constraint.SetRemoveAction(Constraint::DISCARD);
533 scrollView.ApplyConstraintToBoundActors(constraint);
535 // WrapActor (wrap functionality)
536 constraint = Constraint::New<Vector3>(scrollViewActor, Actor::Property::POSITION, WrapActorConstraint);
537 constraint.AddSource(LocalSource(Actor::Property::SCALE));
538 constraint.AddSource(LocalSource(Actor::Property::ANCHOR_POINT));
539 constraint.AddSource(LocalSource(Actor::Property::SIZE));
540 constraint.AddSource(Source(scrollViewActor, Toolkit::Scrollable::Property::SCROLL_POSITION_MIN));
541 constraint.AddSource(Source(scrollViewActor, Toolkit::Scrollable::Property::SCROLL_POSITION_MAX));
542 constraint.AddSource(Source(scrollViewActor, Toolkit::ScrollView::Property::WRAP));
543 constraint.SetRemoveAction(Constraint::DISCARD);
544 scrollView.ApplyConstraintToBoundActors(constraint);
548 } // namespace Internal
550 } // namespace Toolkit