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