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