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