Use new animatable registered properties in ScrollView
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / scrollable / scroll-view / scroll-overshoot-indicator-impl.cpp
1 /*
2  * Copyright (c) 2014 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 <boost/bind.hpp>
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 const Vector2 OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE( 720.0f, 42.0f );
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 )
38 {
39   return (width > OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD) ? OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.height : OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.height * 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 void ScrollOvershootIndicator::ClearOvershoot()
119 {
120   if(mEffectX)
121   {
122     mEffectX->SetOvershoot(0.0f);
123   }
124   if(mEffectY)
125   {
126     mEffectY->SetOvershoot(0.0f);
127   }
128 }
129
130 ScrollOvershootEffect::ScrollOvershootEffect( bool vertical ) :
131     mVertical(vertical)
132 {
133
134 }
135
136 bool ScrollOvershootEffect::IsVertical() const
137 {
138   return mVertical;
139 }
140
141 ScrollOvershootEffectRipple::ScrollOvershootEffectRipple( bool vertical, Scrollable& scrollable ) :
142     ScrollOvershootEffect( vertical ),
143     mAttachedScrollView(scrollable),
144     mOvershootProperty(Property::INVALID_INDEX),
145     mEffectOvershootProperty(Property::INVALID_INDEX),
146     mOvershoot(0.0f),
147     mAnimationStateFlags(0)
148 {
149   mOvershootOverlay = CreateBouncingEffectActor(mEffectOvershootProperty);
150   mOvershootOverlay.SetColor(mAttachedScrollView.GetOvershootEffectColor());
151   mOvershootOverlay.SetParentOrigin(ParentOrigin::TOP_LEFT);
152   mOvershootOverlay.SetAnchorPoint(AnchorPoint::TOP_LEFT);
153   mOvershootOverlay.SetDrawMode(DrawMode::OVERLAY);
154   mOvershootOverlay.SetVisible(false);
155
156 }
157
158 void ScrollOvershootEffectRipple::Apply()
159 {
160   Actor self = mAttachedScrollView.Self();
161   mOvershootProperty = IsVertical() ? Toolkit::ScrollView::Property::OVERSHOOT_Y : Toolkit::ScrollView::Property::OVERSHOOT_X;
162
163   // make sure height is set, since we only create a constraint for image width
164   mOvershootOverlay.SetSize(OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.width, OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.height);
165
166   mAttachedScrollView.AddOverlay(mOvershootOverlay);
167
168   UpdatePropertyNotifications();
169 }
170
171 void ScrollOvershootEffectRipple::Remove( Scrollable& scrollable )
172 {
173   if(mOvershootOverlay)
174   {
175     if(mOvershootIncreaseNotification)
176     {
177       scrollable.Self().RemovePropertyNotification(mOvershootIncreaseNotification);
178       mOvershootIncreaseNotification.Reset();
179     }
180     if(mOvershootDecreaseNotification)
181     {
182       scrollable.Self().RemovePropertyNotification(mOvershootDecreaseNotification);
183       mOvershootDecreaseNotification.Reset();
184     }
185     scrollable.RemoveOverlay(mOvershootOverlay);
186   }
187 }
188
189 void ScrollOvershootEffectRipple::Reset()
190 {
191   mOvershootOverlay.SetVisible(false);
192   mOvershootOverlay.SetProperty( mEffectOvershootProperty, 0.f);
193 }
194
195 void ScrollOvershootEffectRipple::UpdatePropertyNotifications()
196 {
197   float absOvershoot = fabsf(mOvershoot);
198
199   Actor self = mAttachedScrollView.Self();
200   // update overshoot increase notify
201   if( mOvershootIncreaseNotification )
202   {
203     self.RemovePropertyNotification( mOvershootIncreaseNotification );
204     mOvershootIncreaseNotification.Reset();
205   }
206   if( absOvershoot < MAX_OVERSHOOT_NOTIFY_AMOUNT )
207   {
208     float increaseStep = absOvershoot + OVERSHOOT_NOTIFY_STEP;
209     if( increaseStep > MAX_OVERSHOOT_NOTIFY_AMOUNT )
210     {
211       increaseStep = MAX_OVERSHOOT_NOTIFY_AMOUNT;
212     }
213     mOvershootIncreaseNotification = self.AddPropertyNotification( mOvershootProperty, OutsideCondition(-increaseStep, increaseStep) );
214     mOvershootIncreaseNotification.SetNotifyMode(PropertyNotification::NotifyOnTrue);
215     mOvershootIncreaseNotification.NotifySignal().Connect(this, &ScrollOvershootEffectRipple::OnOvershootNotification);
216   }
217
218   // update overshoot decrease notify
219   if( mOvershootDecreaseNotification )
220   {
221     self.RemovePropertyNotification( mOvershootDecreaseNotification );
222     mOvershootDecreaseNotification.Reset();
223   }
224   if( absOvershoot > MIN_OVERSHOOT_NOTIFY_AMOUNT )
225   {
226     float reduceStep = absOvershoot - OVERSHOOT_NOTIFY_STEP;
227     if( reduceStep < MIN_OVERSHOOT_NOTIFY_AMOUNT )
228     {
229       reduceStep = MIN_OVERSHOOT_NOTIFY_AMOUNT;
230     }
231     mOvershootDecreaseNotification = self.AddPropertyNotification( mOvershootProperty, InsideCondition(-reduceStep, reduceStep) );
232     mOvershootDecreaseNotification.SetNotifyMode(PropertyNotification::NotifyOnTrue);
233     mOvershootDecreaseNotification.NotifySignal().Connect(this, &ScrollOvershootEffectRipple::OnOvershootNotification);
234   }
235 }
236
237 void ScrollOvershootEffectRipple::SetOvershootEffectColor( const Vector4& color )
238 {
239   if(mOvershootOverlay)
240   {
241     mOvershootOverlay.SetColor(color);
242   }
243 }
244
245 void ScrollOvershootEffectRipple::UpdateVisibility( bool visible )
246 {
247   mOvershootOverlay.SetVisible(visible);
248   // make sure overshoot image is correctly placed
249   if( visible )
250   {
251     Actor self = mAttachedScrollView.Self();
252     if(mOvershoot > 0.0f)
253     {
254       // positive overshoot
255       const Vector3 size = mOvershootOverlay.GetCurrentSize();
256       Vector3 relativeOffset;
257       const Vector3 parentSize = self.GetCurrentSize();
258       if(IsVertical())
259       {
260         mOvershootOverlay.SetOrientation(Quaternion(0.0f, Vector3::ZAXIS));
261         mOvershootOverlay.SetSize(parentSize.width, GetBounceActorHeight(parentSize.width), size.depth);
262       }
263       else
264       {
265         mOvershootOverlay.SetOrientation(Quaternion(1.5f * Math::PI, Vector3::ZAXIS));
266         mOvershootOverlay.SetSize(parentSize.height, GetBounceActorHeight(parentSize.height), size.depth);
267         relativeOffset = Vector3(0.0f, 1.0f, 0.0f);
268       }
269       mOvershootOverlay.SetPosition(relativeOffset * parentSize);
270     }
271     else
272     {
273       // negative overshoot
274       const Vector3 size = mOvershootOverlay.GetCurrentSize();
275       Vector3 relativeOffset;
276       const Vector3 parentSize = self.GetCurrentSize();
277       if(IsVertical())
278       {
279         mOvershootOverlay.SetOrientation(Quaternion(Math::PI, Vector3::ZAXIS));
280         mOvershootOverlay.SetSize(parentSize.width, GetBounceActorHeight(parentSize.width), size.depth);
281         relativeOffset = Vector3(1.0f, 1.0f, 0.0f);
282       }
283       else
284       {
285         mOvershootOverlay.SetOrientation(Quaternion(0.5f * Math::PI, Vector3::ZAXIS));
286         mOvershootOverlay.SetSize(parentSize.height, GetBounceActorHeight(parentSize.height), size.depth);
287         relativeOffset = Vector3(1.0f, 0.0f, 0.0f);
288       }
289       mOvershootOverlay.SetPosition(relativeOffset * parentSize);
290     }
291   }
292 }
293
294 void ScrollOvershootEffectRipple::OnOvershootNotification(PropertyNotification& source)
295 {
296   Actor self = mAttachedScrollView.Self();
297   mOvershoot = self.GetProperty<float>(mOvershootProperty);
298   SetOvershoot(mOvershoot, false);
299   UpdatePropertyNotifications();
300 }
301
302 void ScrollOvershootEffectRipple::SetOvershoot(float amount, bool animate)
303 {
304   float absAmount = fabsf(amount);
305   bool animatingOn = absAmount > Math::MACHINE_EPSILON_0;
306   if( (animatingOn && (mAnimationStateFlags & AnimatingIn)) )
307   {
308     // trying to do what we are already doing
309     if( mAnimationStateFlags & AnimateBack )
310     {
311       mAnimationStateFlags &= ~AnimateBack;
312     }
313     return;
314   }
315   if( (!animatingOn && (mAnimationStateFlags & AnimatingOut)) )
316   {
317     // trying to do what we are already doing
318     return;
319   }
320   if( !animatingOn && (mAnimationStateFlags & AnimatingIn) )
321   {
322     // dont interrupt while animating on
323     mAnimationStateFlags |= AnimateBack;
324     return;
325   }
326
327   if( absAmount > Math::MACHINE_EPSILON_1 )
328   {
329     UpdateVisibility(true);
330   }
331
332   float overshootAnimationSpeed = mAttachedScrollView.Self().GetProperty<float>(Toolkit::Scrollable::Property::OVERSHOOT_ANIMATION_SPEED);
333
334   if( animate && overshootAnimationSpeed > Math::MACHINE_EPSILON_0 )
335   {
336     float currentOvershoot = fabsf( mOvershootOverlay.GetProperty( mEffectOvershootProperty ).Get<float>() );
337     float duration = mOvershootOverlay.GetCurrentSize().height * (animatingOn ? (1.0f - currentOvershoot) : currentOvershoot) / overshootAnimationSpeed;
338
339     if( duration > Math::MACHINE_EPSILON_0 )
340     {
341       if(mScrollOvershootAnimation)
342       {
343         mScrollOvershootAnimation.FinishedSignal().Disconnect( this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished );
344         mScrollOvershootAnimation.Stop();
345         mScrollOvershootAnimation.Reset();
346       }
347       mScrollOvershootAnimation = Animation::New(duration);
348       mScrollOvershootAnimation.FinishedSignal().Connect( this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished );
349       mScrollOvershootAnimation.AnimateTo( Property(mOvershootOverlay, mEffectOvershootProperty), amount, TimePeriod(duration) );
350       mScrollOvershootAnimation.Play();
351       mAnimationStateFlags = animatingOn ? AnimatingIn : AnimatingOut;
352     }
353   }
354   else
355   {
356     mOvershootOverlay.SetProperty( mEffectOvershootProperty, amount);
357   }
358 }
359
360 void ScrollOvershootEffectRipple::OnOvershootAnimFinished(Animation& animation)
361 {
362   bool animateOff = false;
363   if( mAnimationStateFlags & AnimatingOut )
364   {
365     // should now be offscreen
366     mOvershootOverlay.SetVisible(false);
367   }
368   if( (mAnimationStateFlags & AnimateBack) )
369   {
370     animateOff = true;
371   }
372   mScrollOvershootAnimation.FinishedSignal().Disconnect( this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished );
373   mScrollOvershootAnimation.Stop();
374   mScrollOvershootAnimation.Reset();
375   mAnimationStateFlags = 0;
376   if( animateOff )
377   {
378     SetOvershoot(0.0f, true);
379   }
380 }
381
382 ScrollOvershootEffectRipplePtr ScrollOvershootEffectRipple::New( bool vertical, Scrollable& scrollable )
383 {
384   return new ScrollOvershootEffectRipple(vertical, scrollable);
385 }
386
387 } // namespace Internal
388
389 } // namespace Toolkit
390
391 } // namespace Dali