(ScrollView) Inlined one line Getters/Setters
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / scrollable / scroll-view / scroll-overshoot-indicator-impl.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-overshoot-indicator-impl.h>
20
21 // INTERNAL INCLUDES
22 #include <dali-toolkit/internal/controls/scrollable/bouncing-effect-actor.h>
23 #include <dali-toolkit/internal/controls/scrollable/scrollable-impl.h>
24 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-view.h>
25
26 using namespace Dali;
27
28 namespace
29 {
30 const float OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD = 180.0f;
31
32 // local helper function to resize the height of the bounce actor
33 float GetBounceActorHeight(float width, float defaultHeight)
34 {
35   return (width > OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD) ? defaultHeight : defaultHeight * 0.5f;
36 }
37
38 const float MAX_OVERSHOOT_NOTIFY_AMOUNT = 0.99f;                   // maximum amount to set notification for increased overshoot, beyond this we just wait for it to reduce again
39 const float MIN_OVERSHOOT_NOTIFY_AMOUNT = Math::MACHINE_EPSILON_0; // minimum amount to set notification for reduced overshoot, beyond this we just wait for it to increase again
40 const float OVERSHOOT_NOTIFY_STEP       = 0.01f;                   // amount to set notifications beyond current overshoot value
41
42 } // namespace
43
44 namespace Dali
45 {
46 namespace Toolkit
47 {
48 namespace Internal
49 {
50 ScrollOvershootIndicator::ScrollOvershootIndicator()
51 : mEffectX(NULL),
52   mEffectY(NULL)
53 {
54 }
55
56 ScrollOvershootIndicator::~ScrollOvershootIndicator()
57 {
58 }
59
60 ScrollOvershootIndicator* ScrollOvershootIndicator::New()
61 {
62   ScrollOvershootIndicator* scrollOvershootPtr = new ScrollOvershootIndicator();
63   return scrollOvershootPtr;
64 }
65
66 void ScrollOvershootIndicator::AttachToScrollable(Scrollable& scrollable)
67 {
68   if(!mEffectX)
69   {
70     mEffectX = ScrollOvershootEffectRipple::New(false, scrollable);
71   }
72   mEffectX->Apply();
73   if(!mEffectY)
74   {
75     mEffectY = ScrollOvershootEffectRipple::New(true, scrollable);
76   }
77   mEffectY->Apply();
78 }
79
80 void ScrollOvershootIndicator::DetachFromScrollable(Scrollable& scrollable)
81 {
82   if(mEffectX)
83   {
84     mEffectX->Remove(scrollable);
85   }
86   if(mEffectY)
87   {
88     mEffectY->Remove(scrollable);
89   }
90 }
91
92 void ScrollOvershootIndicator::Reset()
93 {
94   mEffectX->Reset();
95   mEffectY->Reset();
96 }
97
98 void ScrollOvershootIndicator::SetOvershootEffectColor(const Vector4& color)
99 {
100   if(mEffectX)
101   {
102     mEffectX->SetOvershootEffectColor(color);
103   }
104   if(mEffectY)
105   {
106     mEffectY->SetOvershootEffectColor(color);
107   }
108 }
109
110 ScrollOvershootEffect::ScrollOvershootEffect(bool vertical)
111 : mVertical(vertical)
112 {
113 }
114
115 bool ScrollOvershootEffect::IsVertical() const
116 {
117   return mVertical;
118 }
119
120 ScrollOvershootEffectRipple::ScrollOvershootEffectRipple(bool vertical, Scrollable& scrollable)
121 : ScrollOvershootEffect(vertical),
122   mAttachedScrollView(scrollable),
123   mOvershootProperty(Property::INVALID_INDEX),
124   mEffectOvershootProperty(Property::INVALID_INDEX),
125   mOvershoot(0.0f),
126   mOvershootSize(scrollable.GetOvershootSize()),
127   mAnimationStateFlags(0)
128 {
129   mOvershootOverlay = CreateBouncingEffectActor(mEffectOvershootProperty);
130   mOvershootOverlay.SetProperty(Actor::Property::COLOR, mAttachedScrollView.GetOvershootEffectColor());
131   mOvershootOverlay.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
132   mOvershootOverlay.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
133   mOvershootOverlay.SetProperty(Actor::Property::VISIBLE, false);
134 }
135
136 void ScrollOvershootEffectRipple::Apply()
137 {
138   Actor self         = mAttachedScrollView.Self();
139   mOvershootProperty = IsVertical() ? Toolkit::ScrollView::Property::OVERSHOOT_Y : Toolkit::ScrollView::Property::OVERSHOOT_X;
140
141   // make sure height is set, since we only create a constraint for image width
142   mOvershootSize = mAttachedScrollView.GetOvershootSize();
143   mOvershootOverlay.SetProperty(Actor::Property::SIZE, mOvershootSize);
144
145   mAttachedScrollView.AddOverlay(mOvershootOverlay);
146
147   UpdatePropertyNotifications();
148 }
149
150 void ScrollOvershootEffectRipple::Remove(Scrollable& scrollable)
151 {
152   if(mOvershootOverlay)
153   {
154     if(mOvershootIncreaseNotification)
155     {
156       scrollable.Self().RemovePropertyNotification(mOvershootIncreaseNotification);
157       mOvershootIncreaseNotification.Reset();
158     }
159     if(mOvershootDecreaseNotification)
160     {
161       scrollable.Self().RemovePropertyNotification(mOvershootDecreaseNotification);
162       mOvershootDecreaseNotification.Reset();
163     }
164     scrollable.RemoveOverlay(mOvershootOverlay);
165   }
166 }
167
168 void ScrollOvershootEffectRipple::Reset()
169 {
170   mOvershootOverlay.SetProperty(Actor::Property::VISIBLE, false);
171   mOvershootOverlay.SetProperty(mEffectOvershootProperty, 0.f);
172 }
173
174 void ScrollOvershootEffectRipple::UpdatePropertyNotifications()
175 {
176   float absOvershoot = fabsf(mOvershoot);
177
178   Actor self = mAttachedScrollView.Self();
179   // update overshoot increase notify
180   if(mOvershootIncreaseNotification)
181   {
182     self.RemovePropertyNotification(mOvershootIncreaseNotification);
183     mOvershootIncreaseNotification.Reset();
184   }
185   if(absOvershoot < MAX_OVERSHOOT_NOTIFY_AMOUNT)
186   {
187     float increaseStep = absOvershoot + OVERSHOOT_NOTIFY_STEP;
188     if(increaseStep > MAX_OVERSHOOT_NOTIFY_AMOUNT)
189     {
190       increaseStep = MAX_OVERSHOOT_NOTIFY_AMOUNT;
191     }
192     mOvershootIncreaseNotification = self.AddPropertyNotification(mOvershootProperty, OutsideCondition(-increaseStep, increaseStep));
193     mOvershootIncreaseNotification.SetNotifyMode(PropertyNotification::NOTIFY_ON_TRUE);
194     mOvershootIncreaseNotification.NotifySignal().Connect(this, &ScrollOvershootEffectRipple::OnOvershootNotification);
195   }
196
197   // update overshoot decrease notify
198   if(mOvershootDecreaseNotification)
199   {
200     self.RemovePropertyNotification(mOvershootDecreaseNotification);
201     mOvershootDecreaseNotification.Reset();
202   }
203   if(absOvershoot > MIN_OVERSHOOT_NOTIFY_AMOUNT)
204   {
205     float reduceStep = absOvershoot - OVERSHOOT_NOTIFY_STEP;
206     if(reduceStep < MIN_OVERSHOOT_NOTIFY_AMOUNT)
207     {
208       reduceStep = MIN_OVERSHOOT_NOTIFY_AMOUNT;
209     }
210     mOvershootDecreaseNotification = self.AddPropertyNotification(mOvershootProperty, InsideCondition(-reduceStep, reduceStep));
211     mOvershootDecreaseNotification.SetNotifyMode(PropertyNotification::NOTIFY_ON_TRUE);
212     mOvershootDecreaseNotification.NotifySignal().Connect(this, &ScrollOvershootEffectRipple::OnOvershootNotification);
213   }
214 }
215
216 void ScrollOvershootEffectRipple::SetOvershootEffectColor(const Vector4& color)
217 {
218   if(mOvershootOverlay)
219   {
220     mOvershootOverlay.SetProperty(Actor::Property::COLOR, color);
221   }
222 }
223
224 void ScrollOvershootEffectRipple::UpdateVisibility(bool visible)
225 {
226   mOvershootOverlay.SetProperty(Actor::Property::VISIBLE, visible);
227   // make sure overshoot image is correctly placed
228   if(visible)
229   {
230     Actor self = mAttachedScrollView.Self();
231     if(mOvershoot > 0.0f)
232     {
233       // positive overshoot
234       const Vector3 size = mOvershootOverlay.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
235       Vector3       relativeOffset;
236       const Vector3 parentSize = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
237       if(IsVertical())
238       {
239         mOvershootOverlay.SetProperty(Actor::Property::ORIENTATION, Quaternion(Quaternion(Radian(0.0f), Vector3::ZAXIS)));
240         mOvershootOverlay.SetProperty(Actor::Property::SIZE, Vector3(parentSize.width, GetBounceActorHeight(parentSize.width, mOvershootSize.height), size.depth));
241       }
242       else
243       {
244         mOvershootOverlay.SetProperty(Actor::Property::ORIENTATION, Quaternion(Quaternion(Radian(1.5f * Math::PI), Vector3::ZAXIS)));
245         mOvershootOverlay.SetProperty(Actor::Property::SIZE, Vector3(parentSize.height, GetBounceActorHeight(parentSize.height, mOvershootSize.height), size.depth));
246         relativeOffset = Vector3(0.0f, 1.0f, 0.0f);
247       }
248       mOvershootOverlay.SetProperty(Actor::Property::POSITION, relativeOffset * parentSize);
249     }
250     else
251     {
252       // negative overshoot
253       const Vector3 size = mOvershootOverlay.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
254       Vector3       relativeOffset;
255       const Vector3 parentSize = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
256       if(IsVertical())
257       {
258         mOvershootOverlay.SetProperty(Actor::Property::ORIENTATION, Quaternion(Quaternion(Radian(Math::PI), Vector3::ZAXIS)));
259         mOvershootOverlay.SetProperty(Actor::Property::SIZE, Vector3(parentSize.width, GetBounceActorHeight(parentSize.width, mOvershootSize.height), size.depth));
260         relativeOffset = Vector3(1.0f, 1.0f, 0.0f);
261       }
262       else
263       {
264         mOvershootOverlay.SetProperty(Actor::Property::ORIENTATION, Quaternion(Quaternion(Radian(0.5f * Math::PI), Vector3::ZAXIS)));
265         mOvershootOverlay.SetProperty(Actor::Property::SIZE, Vector3(parentSize.height, GetBounceActorHeight(parentSize.height, mOvershootSize.height), size.depth));
266         relativeOffset = Vector3(1.0f, 0.0f, 0.0f);
267       }
268       mOvershootOverlay.SetProperty(Actor::Property::POSITION, relativeOffset * parentSize);
269     }
270   }
271 }
272
273 void ScrollOvershootEffectRipple::OnOvershootNotification(PropertyNotification& source)
274 {
275   Actor self = mAttachedScrollView.Self();
276   mOvershoot = self.GetCurrentProperty<float>(mOvershootProperty);
277   SetOvershoot(mOvershoot, false);
278   UpdatePropertyNotifications();
279 }
280
281 void ScrollOvershootEffectRipple::SetOvershoot(float amount, bool animate)
282 {
283   float absAmount   = fabsf(amount);
284   bool  animatingOn = absAmount > Math::MACHINE_EPSILON_0;
285   if((animatingOn && (mAnimationStateFlags & AnimatingIn)))
286   {
287     // trying to do what we are already doing
288     if(mAnimationStateFlags & AnimateBack)
289     {
290       mAnimationStateFlags &= ~AnimateBack;
291     }
292     return;
293   }
294   if((!animatingOn && (mAnimationStateFlags & AnimatingOut)))
295   {
296     // trying to do what we are already doing
297     return;
298   }
299   if(!animatingOn && (mAnimationStateFlags & AnimatingIn))
300   {
301     // dont interrupt while animating on
302     mAnimationStateFlags |= AnimateBack;
303     return;
304   }
305
306   if(absAmount > Math::MACHINE_EPSILON_1)
307   {
308     UpdateVisibility(true);
309   }
310
311   float overshootAnimationSpeed = mAttachedScrollView.Self().GetProperty<float>(Toolkit::Scrollable::Property::OVERSHOOT_ANIMATION_SPEED);
312
313   if(animate && overshootAnimationSpeed > Math::MACHINE_EPSILON_0)
314   {
315     float currentOvershoot = fabsf(mOvershootOverlay.GetProperty(mEffectOvershootProperty).Get<float>());
316     float duration         = mOvershootOverlay.GetCurrentProperty<Vector3>(Actor::Property::SIZE).height * (animatingOn ? (1.0f - currentOvershoot) : currentOvershoot) / overshootAnimationSpeed;
317
318     if(duration > Math::MACHINE_EPSILON_0)
319     {
320       if(mScrollOvershootAnimation)
321       {
322         mScrollOvershootAnimation.FinishedSignal().Disconnect(this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished);
323         mScrollOvershootAnimation.Stop();
324         mScrollOvershootAnimation.Reset();
325       }
326       mScrollOvershootAnimation = Animation::New(duration);
327       mScrollOvershootAnimation.FinishedSignal().Connect(this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished);
328       mScrollOvershootAnimation.AnimateTo(Property(mOvershootOverlay, mEffectOvershootProperty), amount, TimePeriod(duration));
329       mScrollOvershootAnimation.Play();
330       mAnimationStateFlags = animatingOn ? AnimatingIn : AnimatingOut;
331     }
332   }
333   else
334   {
335     mOvershootOverlay.SetProperty(mEffectOvershootProperty, amount);
336   }
337 }
338
339 void ScrollOvershootEffectRipple::OnOvershootAnimFinished(Animation& animation)
340 {
341   bool animateOff = false;
342   if(mAnimationStateFlags & AnimatingOut)
343   {
344     // should now be offscreen
345     mOvershootOverlay.SetProperty(Actor::Property::VISIBLE, false);
346   }
347   if((mAnimationStateFlags & AnimateBack))
348   {
349     animateOff = true;
350   }
351   mScrollOvershootAnimation.FinishedSignal().Disconnect(this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished);
352   mScrollOvershootAnimation.Stop();
353   mScrollOvershootAnimation.Reset();
354   mAnimationStateFlags = 0;
355   if(animateOff)
356   {
357     SetOvershoot(0.0f, true);
358   }
359 }
360
361 ScrollOvershootEffectRipplePtr ScrollOvershootEffectRipple::New(bool vertical, Scrollable& scrollable)
362 {
363   return new ScrollOvershootEffectRipple(vertical, scrollable);
364 }
365
366 } // namespace Internal
367
368 } // namespace Toolkit
369
370 } // namespace Dali