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