Conversion to Apache 2.0 license
[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
42 ScrollOvershootIndicator::ScrollOvershootIndicator(Scrollable& scrollable) :
43   mScrollable(scrollable),
44   mEffectX(NULL),
45   mEffectY(NULL)
46 {
47 }
48
49 ScrollOvershootIndicator::~ScrollOvershootIndicator()
50 {
51
52 }
53
54 ScrollOvershootIndicator* ScrollOvershootIndicator::New(Scrollable& scrollable)
55 {
56   ScrollOvershootIndicator* scrollOvershootPtr = new ScrollOvershootIndicator(scrollable);
57   return scrollOvershootPtr;
58 }
59
60 void ScrollOvershootIndicator::Enable(bool enable)
61 {
62   if(enable)
63   {
64     Actor scrollableActor = mScrollable.Self();
65     if(!mEffectX)
66     {
67       mEffectX = ScrollOvershootEffectRipple::New(false);
68     }
69     mEffectX->Apply(mScrollable);
70     if(!mEffectY)
71     {
72       mEffectY = ScrollOvershootEffectRipple::New(true);
73     }
74     mEffectY->Apply(mScrollable);
75     mEffectY->SetPropertyNotifications(scrollableActor);
76   }
77   else
78   {
79     if(mEffectX)
80     {
81       mEffectX->Remove(mScrollable);
82     }
83     if(mEffectY)
84     {
85       mEffectY->Remove(mScrollable);
86     }
87   }
88 }
89
90 void ScrollOvershootIndicator::Reset()
91 {
92   mEffectX->Reset();
93   mEffectY->Reset();
94 }
95
96 ScrollOvershootEffect::ScrollOvershootEffect(bool vertical) :
97     mVertical(vertical)
98 {
99
100 }
101
102 ScrollOvershootEffectRipple::ScrollOvershootEffectRipple(bool vertical) :
103     ScrollOvershootEffect(vertical),
104     mMaxOvershootImageSize(DEFAULT_MAX_OVERSHOOT_HEIGHT)
105 {
106   mRippleEffect = BouncingEffect::New(Scrollable::DEFAULT_OVERSHOOT_COLOUR);
107   mOvershootImage = CreateSolidColorActor(Vector4::ONE);
108   mOvershootImage.SetParentOrigin(ParentOrigin::TOP_LEFT);
109   mOvershootImage.SetAnchorPoint(AnchorPoint::TOP_LEFT);
110   mOvershootImage.SetDrawMode(DrawMode::OVERLAY);
111   mOvershootImage.SetShaderEffect(mRippleEffect);
112   mOvershootImage.SetVisible(false);
113   mAnimatingOvershootOn = false;
114   mAnimateOvershootOff = false;
115 }
116
117 void ScrollOvershootEffectRipple::Apply(Scrollable& scrollable)
118 {
119   Actor scrollableActor = scrollable.Self();
120
121   // make sure height is set, since we only create a constraint for image width
122   mOvershootImage.SetSize(OVERSHOOT_RIPPLE_IMAGE_1_PIXEL_AREA.width, OVERSHOOT_RIPPLE_IMAGE_1_PIXEL_AREA.height);
123
124   UpdateConstraints(scrollableActor);
125   scrollable.AddOverlay(mOvershootImage);
126
127   SetPropertyNotifications(scrollableActor);
128 }
129
130 void ScrollOvershootEffectRipple::Remove(Scrollable& scrollable)
131 {
132   if(mOvershootImage)
133   {
134     if(mSizeConstraint)
135     {
136       mOvershootImage.RemoveConstraint(mSizeConstraint);
137       mSizeConstraint = NULL;
138     }
139     if(mPositionConstraint)
140     {
141       mOvershootImage.RemoveConstraint(mPositionConstraint);
142       mPositionConstraint = NULL;
143     }
144     scrollable.RemoveOverlay(mOvershootImage);
145   }
146 }
147
148 void ScrollOvershootEffectRipple::Reset()
149 {
150   mAnimatingOvershootOn = false;
151   mAnimateOvershootOff = false;
152   mOvershootImage.SetVisible(false);
153   mRippleEffect.SetProgressRate(0.0f);
154   if(mScrollOvershootAnimation)
155   {
156     mScrollOvershootAnimation.Clear();
157     mScrollOvershootAnimation.Reset();
158   }
159 }
160
161 void ScrollOvershootEffectRipple::UpdateConstraints(Actor& scrollable)
162 {
163   int overshootPropertyIndex = mRippleEffect.GetPropertyIndex(mRippleEffect.GetProgressRatePropertyName());
164   Constraint constraint;
165   if(!mSizeConstraint)
166   {
167     constraint = Constraint::New<float>( Actor::SIZE_WIDTH,
168                                                       Source( scrollable, IsVertical() ? Actor::SIZE_WIDTH : Actor::SIZE_HEIGHT),
169                                                       EqualToConstraint() );
170     mSizeConstraint = mOvershootImage.ApplyConstraint(constraint);
171   }
172
173   if(!mPositionConstraint)
174   {
175     constraint = Constraint::New<Vector3>( Actor::POSITION,
176                                            Source( scrollable, Actor::SIZE ),
177                                            Source( mRippleEffect, overshootPropertyIndex ),
178                                            boost::bind( &ScrollOvershootEffectRipple::PositionConstraint, this, _1, _2, _3) );
179     mPositionConstraint = mOvershootImage.ApplyConstraint(constraint);
180   }
181 }
182
183 void ScrollOvershootEffectRipple::SetPropertyNotifications(Actor& scrollable)
184 {
185   int overshootXPropertyIndex = scrollable.GetPropertyIndex(Toolkit::ScrollView::SCROLL_OVERSHOOT_X_PROPERTY_NAME);
186   int overshootYPropertyIndex = scrollable.GetPropertyIndex(Toolkit::ScrollView::SCROLL_OVERSHOOT_Y_PROPERTY_NAME);
187   mCanScrollPropertyIndex = scrollable.GetPropertyIndex(IsVertical() ? Scrollable::SCROLLABLE_CAN_SCROLL_VERTICAL : Scrollable::SCROLLABLE_CAN_SCROLL_HORIZONTAL);
188
189   if(!mOvershootNegativeNotification)
190   {
191     mOvershootNegativeNotification = scrollable.AddPropertyNotification(IsVertical() ? overshootYPropertyIndex : overshootXPropertyIndex, LessThanCondition(-Math::MACHINE_EPSILON_1));
192     mOvershootNegativeNotification.SetNotifyMode(PropertyNotification::NotifyOnChanged);
193     mOvershootNegativeNotification.NotifySignal().Connect(this, &ScrollOvershootEffectRipple::OnNegativeOvershootNotification);
194   }
195
196   if(!mOvershootPositiveNotification)
197   {
198     mOvershootPositiveNotification = scrollable.AddPropertyNotification(IsVertical() ? overshootYPropertyIndex : overshootXPropertyIndex, GreaterThanCondition(Math::MACHINE_EPSILON_1));
199     mOvershootPositiveNotification.SetNotifyMode(PropertyNotification::NotifyOnChanged);
200     mOvershootPositiveNotification.NotifySignal().Connect(this, &ScrollOvershootEffectRipple::OnPositiveOvershootNotification);
201   }
202 }
203
204 Vector3 ScrollOvershootEffectRipple::PositionConstraint(const Vector3& current,
205     const PropertyInput& parentSizeProperty, const PropertyInput& overshootProperty)
206 {
207   float overshoot = overshootProperty.GetFloat();
208   const Vector3 parentSize = parentSizeProperty.GetVector3();
209
210   Vector3 relativeOffset = Vector3::ZERO;
211
212   if(IsVertical())
213   {
214     if(overshoot > Math::MACHINE_EPSILON_0)
215     {
216       relativeOffset = Vector3(0.0f, 0.0f, 0.0f);
217     }
218     else if (overshoot < -Math::MACHINE_EPSILON_0)
219     {
220       relativeOffset = Vector3(1.0f, 1.0f, 0.0f);
221     }
222   }
223   else
224   {
225     if(overshoot > Math::MACHINE_EPSILON_0)
226     {
227       relativeOffset = Vector3(0.0f, 1.0f, 0.0f);
228     }
229     else if (overshoot < -Math::MACHINE_EPSILON_0)
230     {
231       relativeOffset = Vector3(1.0f, 0.0f, 0.0f);
232     }
233   }
234
235   return relativeOffset * parentSize;
236 }
237
238 void ScrollOvershootEffectRipple::OnPositiveOvershootNotification(PropertyNotification& source)
239 {
240   Actor delegate = Actor::DownCast(source.GetTarget());
241   float overshoot = delegate.GetProperty<float>(source.GetTargetProperty());
242   bool canScroll = delegate.GetProperty<bool>(mCanScrollPropertyIndex);
243   if(!canScroll)
244   {
245     mOvershootImage.SetVisible(false);
246     return;
247   }
248   mOvershootImage.SetVisible(true);
249
250   if (fabsf(overshoot) < Math::MACHINE_EPSILON_1)
251   {
252     AnimateScrollOvershoot(0.0f);
253     return;
254   }
255   if(overshoot > 0.0f)
256   {
257     const Vector3 imageSize = mOvershootImage.GetCurrentSize();
258     Vector3 relativeOffset = Vector3::ZERO;
259     const Vector3 parentSize = delegate.GetCurrentSize();
260     AnimateScrollOvershoot(1.0f);
261     if(IsVertical())
262     {
263       mOvershootImage.SetRotation(Quaternion(0.0f, Vector3::ZAXIS));
264       mOvershootImage.SetSize(parentSize.width, imageSize.height, imageSize.depth);
265       relativeOffset = Vector3(0.0f, 0.0f, 0.0f);
266     }
267     else
268     {
269       mOvershootImage.SetRotation(Quaternion(1.5f * Math::PI, Vector3::ZAXIS));
270       mOvershootImage.SetSize(parentSize.height, imageSize.height, imageSize.depth);
271       relativeOffset = Vector3(0.0f, 1.0f, 0.0f);
272     }
273     mOvershootImage.SetPosition(relativeOffset * parentSize);
274   }
275 }
276
277 void ScrollOvershootEffectRipple::OnNegativeOvershootNotification(PropertyNotification& source)
278 {
279   Actor delegate = Actor::DownCast(source.GetTarget());
280   float overshoot = delegate.GetProperty<float>(source.GetTargetProperty());
281   bool canScroll = delegate.GetProperty<bool>(mCanScrollPropertyIndex);
282   if(!canScroll)
283   {
284     mOvershootImage.SetVisible(false);
285     return;
286   }
287   mOvershootImage.SetVisible(true);
288
289   if (fabsf(overshoot) < Math::MACHINE_EPSILON_1)
290   {
291     AnimateScrollOvershoot(0.0f);
292     return;
293   }
294
295   if(overshoot < 0.0f)
296   {
297     const Vector3 imageSize = mOvershootImage.GetCurrentSize();
298     Vector3 relativeOffset = Vector3::ZERO;
299     const Vector3 parentSize = delegate.GetCurrentSize();
300     AnimateScrollOvershoot(-1.0f);
301     if(IsVertical())
302     {
303       mOvershootImage.SetRotation(Quaternion(Math::PI, Vector3::ZAXIS));
304       mOvershootImage.SetSize(parentSize.width, imageSize.height, imageSize.depth);
305       relativeOffset = Vector3(1.0f, 1.0f, 0.0f);
306     }
307     else
308     {
309       mOvershootImage.SetRotation(Quaternion(0.5f * Math::PI, Vector3::ZAXIS));
310       mOvershootImage.SetSize(parentSize.height, imageSize.height, imageSize.depth);
311       relativeOffset = Vector3(1.0f, 0.0f, 0.0f);
312     }
313     mOvershootImage.SetPosition(relativeOffset * parentSize);
314   }
315 }
316
317 void ScrollOvershootEffectRipple::AnimateScrollOvershoot(float overshootAmount)
318 {
319   bool animatingOn = fabsf(overshootAmount) > Math::MACHINE_EPSILON_1;
320
321   // make sure we animate back if needed
322   mAnimateOvershootOff = (!animatingOn && mAnimatingOvershootOn);
323
324   int overShootProperty = mRippleEffect.GetPropertyIndex(mRippleEffect.GetProgressRatePropertyName());
325   float currentOvershoot = mRippleEffect.GetProperty<float>(overShootProperty);
326   if(((currentOvershoot < 0.0f && overshootAmount > 0.0f)
327       || (currentOvershoot > 0.0f && overshootAmount < 0.0f)))
328   {
329     // cancel current animation
330     mAnimatingOvershootOn = false;
331     // reset currentOvershoot to 0.0f
332     mRippleEffect.SetProperty(overShootProperty, 0.0f);
333     currentOvershoot = 0.0f;
334   }
335   if( mAnimatingOvershootOn )
336   {
337     // animating on in same direction, do not allow animate off
338     return;
339   }
340   float duration = DEFAULT_OVERSHOOT_ANIMATION_DURATION * (animatingOn ? (1.0f - fabsf(currentOvershoot)) : fabsf(currentOvershoot));
341
342   if(mScrollOvershootAnimation)
343   {
344     mScrollOvershootAnimation.Clear();
345     mScrollOvershootAnimation.Reset();
346   }
347   mScrollOvershootAnimation = Animation::New(duration);
348   mScrollOvershootAnimation.FinishedSignal().Connect(this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished);
349   mScrollOvershootAnimation.AnimateTo( Property(mRippleEffect, overShootProperty), overshootAmount, TimePeriod(0.0f, duration) );
350   mScrollOvershootAnimation.Play();
351
352   mOvershootImage.SetVisible(true);
353
354   mAnimatingOvershootOn = animatingOn;
355 }
356
357 void ScrollOvershootEffectRipple::OnOvershootAnimFinished(Animation& animation)
358 {
359   if(!mAnimatingOvershootOn && !mAnimateOvershootOff)
360   {
361     // just finished animating overshoot to 0
362     mOvershootImage.SetVisible(false);
363   }
364   mAnimatingOvershootOn = false;
365   mScrollOvershootAnimation.FinishedSignal().Disconnect(this, &ScrollOvershootEffectRipple::OnOvershootAnimFinished);
366   mScrollOvershootAnimation.Clear();
367   mScrollOvershootAnimation.Reset();
368   if(mAnimateOvershootOff)
369   {
370     AnimateScrollOvershoot(0.0f);
371   }
372 }
373
374 ScrollOvershootEffectRipplePtr ScrollOvershootEffectRipple::New(bool vertical)
375 {
376   return new ScrollOvershootEffectRipple(vertical);
377 }
378
379 } // namespace Internal
380
381 } // namespace Toolkit
382
383 } // namespace Dali