Updated demos to use DALi clang-format
[platform/core/uifw/dali-demo.git] / examples / gestures / gesture-example.cpp
1 /*
2  * Copyright (c) 2020 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 // EXTERNAL INCLUDES
19 #include <dali-toolkit/dali-toolkit.h>
20 #include <string>
21
22 using namespace Dali;
23 using namespace Dali::Toolkit;
24 using namespace std;
25
26 namespace
27 {
28 const Property::Value BACKGROUND{
29   {Toolkit::Visual::Property::TYPE, Visual::GRADIENT},
30   {GradientVisual::Property::STOP_COLOR, Property::Array{Vector4(167.0f, 207.0f, 223.0f, 255.0f) / 255.0f, Vector4(0.0f, 64.0f, 137.0f, 255.0f) / 255.0f}},
31   {GradientVisual::Property::START_POSITION, Vector2(0.0f, -0.5f)},
32   {GradientVisual::Property::END_POSITION, Vector2(0.0f, 0.5f)}};
33
34 const Property::Value CONTROL_BACKGROUND{
35   {Toolkit::Visual::Property::TYPE, Visual::GRADIENT},
36   {GradientVisual::Property::STOP_COLOR, Property::Array{Vector4(234.0f, 185.0f, 45.0f, 255.0f) / 255.0f, Vector4(199.0f, 152.0f, 16.0f, 255.0f) / 255.0f}},
37   {GradientVisual::Property::CENTER, Vector2::ZERO},
38   {GradientVisual::Property::RADIUS, 0.5f}};
39
40 const float   HELP_ANIMATION_DURATION(25.0f);
41 const float   HELP_ANIMATION_SEGMENT_TIME(5.0f);
42 const float   HELP_ANIMATION_TRANSITION_DURATION(0.75f);
43 const Vector2 HELP_TEXT_POSITION_MULTIPLIER(0.25f, 0.13f);
44
45 const float SHAKY_ANIMATION_DURATION(0.1f);
46 const float SHAKY_ANIMATION_SEGMENT_TIME(0.05f);
47 const float SHAKY_ANIMATION_ANGLE(1.0f);
48
49 const float   TOUCH_MODE_ANIMATION_DURATION(0.1f);
50 const Vector4 TOUCH_MODE_COLOR(1.0f, 0.7f, 0.7f, 1.0f);
51
52 const float   PAN_MODE_CHANGE_ANIMATION_DURATION(0.25f);
53 const Vector3 PAN_MODE_START_ANIMATION_SCALE(1.2f, 1.2f, 1.0f);
54 const Vector3 PAN_MODE_END_ANIMATION_SCALE(0.8f, 0.8f, 1.0f);
55
56 const float   TAP_ANIMATION_DURATION(0.5f);
57 const Vector4 TAP_ANIMATION_COLOR(0.8f, 0.5, 0.2f, 0.6f);
58
59 const Vector3 MINIMUM_SCALE(Vector3::ONE);
60 const Vector3 MAXIMUM_SCALE(Vector3::ONE * 2.0f);
61 const float   SCALE_BACK_ANIMATION_DURATION(0.25f);
62 const float   ROTATE_BACK_ANIMATION_DURATION(0.25f);
63
64 /**
65  * @brief Shows the given string between the given start and end times.
66  *
67  * Appropriately animates the string into and out of the scene.
68  *
69  * @param[in]  string     The label to display, this is an rvalue reference & will be moved
70  * @param[in]  parent     The parent to add the label to
71  * @param[in]  animation  The animation to add the animators created in this function
72  * @param[in]  startTime  When to start the animators
73  * @param[in]  endTime    When to end the animators
74  */
75 void AddHelpInfo(const std::string&& string, const Vector2& windowSize, Actor parent, Animation animation, float startTime, float endTime)
76 {
77   Actor   text = TextLabel::New(std::move(string));
78   Vector3 position(windowSize * HELP_TEXT_POSITION_MULTIPLIER);
79
80   text.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_CENTER);
81   text.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_CENTER);
82   text.SetProperty(Actor::Property::POSITION, position);
83   text.SetProperty(Actor::Property::OPACITY, 0.0f);
84   text.SetProperty(TextLabel::Property::HORIZONTAL_ALIGNMENT, Text::HorizontalAlignment::CENTER);
85   text.SetProperty(TextLabel::Property::MULTI_LINE, true);
86   parent.Add(text);
87
88   // Animate IN
89   TimePeriod timePeriod(startTime, HELP_ANIMATION_TRANSITION_DURATION);
90   animation.AnimateTo(Property(text, Actor::Property::COLOR_ALPHA), 1.0f, timePeriod);
91   animation.AnimateBy(Property(text, Actor::Property::POSITION_X), -position.x, timePeriod);
92
93   // Animate OUT
94   timePeriod.delaySeconds = endTime;
95   animation.AnimateTo(Property(text, Actor::Property::COLOR_ALPHA), 0.0f, timePeriod);
96   animation.AnimateBy(Property(text, Actor::Property::POSITION_X), -position.x, timePeriod);
97 }
98
99 } // unnamed namespace
100
101 /**
102  * @brief This example shows how to use the different gesture detectors available.
103  *
104  * - Tapping on the control shows a small rotation animation.
105  * - A Long press on the control puts it into Pan Mode.
106  * - When in Pan mode, the control can be panned (moved).
107  * - When the pan ends, we exit the Pan mode via an animation.
108  * - Pinching the control changes the scale of the control.
109  */
110 class GestureExample : public ConnectionTracker
111 {
112 public:
113   /**
114    * @brief Constructor.
115    *
116    * @param[in]  application  Reference to the application
117    */
118   GestureExample(Application& application)
119   : mApplication(application)
120   {
121     // Connect to the Application's Init signal
122     application.InitSignal().Connect(this, &GestureExample::Create);
123   }
124
125 private:
126   /**
127    * @brief Creates the scene as described in this class' description.
128    *
129    * @param[in]  application  Reference to the application class
130    */
131   void Create(Application& application)
132   {
133     // Get a handle to the window & connect to the key event signal
134     auto    window     = application.GetWindow();
135     Vector2 windowSize = window.GetSize();
136     window.KeyEventSignal().Connect(this, &GestureExample::OnKeyEvent);
137
138     // Create a background with a linear gradient which matches parent size & is placed in the center.
139     Actor background = Control::New();
140     background.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
141     background.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
142     background.SetProperty(Control::Property::BACKGROUND, BACKGROUND);
143     window.Add(background);
144
145     // Create a control with a circular gradient that we'll use for the gestures and be a quarter of the size of the window.
146     Actor touchControl = Control::New();
147     touchControl.SetProperty(Actor::Property::SIZE, windowSize * 0.25f);
148     touchControl.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
149     touchControl.SetProperty(Control::Property::BACKGROUND, CONTROL_BACKGROUND);
150     background.Add(touchControl);
151
152     // Connect to the touch signal
153     touchControl.TouchedSignal().Connect(this, &GestureExample::OnTouch);
154     touchControl.SetProperty(Actor::Property::LEAVE_REQUIRED, true);
155
156     // Create a long press gesture detector, attach the actor & connect
157     mLongPressDetector = LongPressGestureDetector::New();
158     mLongPressDetector.Attach(touchControl);
159     mLongPressDetector.DetectedSignal().Connect(this, &GestureExample::OnLongPress);
160
161     // Create a pan gesture detector & connect, don't attach the actor as we'll attach it when we detect a long-press
162     mPanDetector = PanGestureDetector::New();
163     mPanDetector.DetectedSignal().Connect(this, &GestureExample::OnPan);
164
165     // Create a tap gesture detector, attach the actor & connect
166     mTapDetector = TapGestureDetector::New();
167     mTapDetector.Attach(touchControl);
168     mTapDetector.DetectedSignal().Connect(this, &GestureExample::OnTap);
169
170     // Create a pinch gesture detector, attach the actor & connect
171     mPinchDetector = PinchGestureDetector::New();
172     mPinchDetector.Attach(touchControl);
173     mPinchDetector.DetectedSignal().Connect(this, &GestureExample::OnPinch);
174
175     // Create a rotation gesture detector, attach the actor & connect
176     mRotationDetector = RotationGestureDetector::New();
177     mRotationDetector.Attach(touchControl);
178     mRotationDetector.DetectedSignal().Connect(this, &GestureExample::OnRotation);
179
180     // Create an animation which shakes the actor when in Pan mode
181     mShakeAnimation = Animation::New(SHAKY_ANIMATION_DURATION);
182     mShakeAnimation.AnimateBy(Property(touchControl, Actor::Property::ORIENTATION),
183                               Quaternion(Degree(SHAKY_ANIMATION_ANGLE), Vector3::ZAXIS),
184                               AlphaFunction::BOUNCE,
185                               TimePeriod(0.0f, SHAKY_ANIMATION_SEGMENT_TIME));
186     mShakeAnimation.AnimateBy(Property(touchControl, Actor::Property::ORIENTATION),
187                               Quaternion(Degree(-SHAKY_ANIMATION_ANGLE), Vector3::ZAXIS),
188                               AlphaFunction::BOUNCE,
189                               TimePeriod(SHAKY_ANIMATION_SEGMENT_TIME, SHAKY_ANIMATION_SEGMENT_TIME));
190
191     // Animate help information
192     // Here we just animate some text on the screen to show tips on how to use this example
193     // The help animation loops
194     Animation helpAnimation = Animation::New(HELP_ANIMATION_DURATION);
195
196     float startTime(0.0f);
197     float endTime(startTime + HELP_ANIMATION_SEGMENT_TIME);
198
199     AddHelpInfo("Tap image for animation", windowSize, background, helpAnimation, startTime, endTime);
200     AddHelpInfo("Press & Hold image to drag", windowSize, background, helpAnimation, startTime += HELP_ANIMATION_SEGMENT_TIME, endTime += HELP_ANIMATION_SEGMENT_TIME);
201     AddHelpInfo("Pinch image to resize", windowSize, background, helpAnimation, startTime += HELP_ANIMATION_SEGMENT_TIME, endTime += HELP_ANIMATION_SEGMENT_TIME);
202     AddHelpInfo("Move fingers in a circular motion on image to rotate", windowSize, background, helpAnimation, startTime += HELP_ANIMATION_SEGMENT_TIME, endTime += HELP_ANIMATION_SEGMENT_TIME);
203     helpAnimation.SetLooping(true);
204     helpAnimation.Play();
205   }
206
207   /**
208    * @brief Called when our actor is touched.
209    *
210    * @param[in]  actor  The touched actor
211    * @param[in]  touch  The touch event
212    */
213   bool OnTouch(Actor actor, const TouchEvent& touch)
214   {
215     switch(touch.GetState(0))
216     {
217       case PointState::DOWN:
218       {
219         // When we get a touch point, change the color of the actor.
220
221         Animation anim = Animation::New(TOUCH_MODE_ANIMATION_DURATION);
222         anim.AnimateTo(Property(actor, Actor::Property::COLOR), TOUCH_MODE_COLOR);
223         anim.Play();
224         break;
225       }
226
227       case PointState::LEAVE:
228       case PointState::UP:
229       case PointState::INTERRUPTED:
230       {
231         if(!mPanStarted)
232         {
233           // If we're not panning, change the color back to normal.
234
235           Animation anim = Animation::New(TOUCH_MODE_ANIMATION_DURATION);
236           anim.AnimateTo(Property(actor, Actor::Property::COLOR), Vector4::ONE);
237           anim.Play();
238
239           // Stop the shake animation from looping.
240           mShakeAnimation.SetLooping(false);
241         }
242         break;
243       }
244
245       default:
246       {
247         break;
248       }
249     }
250     return false;
251   }
252
253   /**
254    * @brief Called when a long-press gesture is detected on our control.
255    *
256    * @param[in]  actor      The actor that's been long-pressed
257    * @param[in]  longPress  The long-press gesture information
258    */
259   void OnLongPress(Actor actor, const LongPressGesture& longPress)
260   {
261     if(longPress.GetState() == GestureState::STARTED)
262     {
263       // When we first receive a long press, attach the actor to the pan detector.
264       mPanDetector.Attach(actor);
265
266       // Do a small animation to indicate to the user that we are in pan mode.
267       Animation anim = Animation::New(PAN_MODE_CHANGE_ANIMATION_DURATION);
268       anim.AnimateTo(Property(actor, Actor::Property::SCALE), actor.GetCurrentProperty<Vector3>(Actor::Property::SCALE) * PAN_MODE_START_ANIMATION_SCALE, AlphaFunction::BOUNCE);
269       anim.Play();
270
271       // Start the shake animation so the user knows when they are in pan mode.
272       mShakeAnimation.SetLooping(true);
273       mShakeAnimation.Play();
274     }
275   }
276
277   /**
278    * @brief Called when a pan gesture is detected on our control.
279    *
280    * @param[in]  actor  The actor that's been panned
281    * @param[in]  pan    The pan gesture information
282    */
283   void OnPan(Actor actor, const PanGesture& pan)
284   {
285     // Just move the actor by the displacement.
286
287     // As the displacement is in local actor coords, we will have to multiply the displacement by the
288     // actor's scale so that it moves the correct amount in the parent's coordinate system.
289     Vector3 scaledDisplacement(pan.GetDisplacement());
290     scaledDisplacement *= actor.GetCurrentProperty<Vector3>(Actor::Property::SCALE);
291
292     Vector3 currentPosition;
293     actor.GetProperty(Actor::Property::POSITION).Get(currentPosition);
294
295     Vector3 newPosition = currentPosition + scaledDisplacement;
296     actor.SetProperty(Actor::Property::POSITION, newPosition);
297
298     switch(pan.GetState())
299     {
300       case GestureState::STARTED:
301       {
302         mPanStarted = true;
303         break;
304       }
305
306       case GestureState::FINISHED:
307       case GestureState::CANCELLED:
308       {
309         // If we cancel or finish the pan, do an animation to indicate this and stop the shake animation.
310
311         Animation anim = Animation::New(PAN_MODE_CHANGE_ANIMATION_DURATION);
312         anim.AnimateTo(Property(actor, Actor::Property::COLOR), Vector4::ONE);
313         anim.AnimateTo(Property(actor, Actor::Property::SCALE), actor.GetCurrentProperty<Vector3>(Actor::Property::SCALE) * PAN_MODE_END_ANIMATION_SCALE, AlphaFunction::BOUNCE);
314
315         // Move actor back to center if we're out of bounds
316         Vector2 halfWindowSize = Vector2(mApplication.GetWindow().GetSize()) * 0.5f;
317         if((abs(newPosition.x) > halfWindowSize.width) ||
318            (abs(newPosition.y) > halfWindowSize.height))
319         {
320           anim.AnimateTo(Property(actor, Actor::Property::POSITION), Vector3::ZERO, AlphaFunction::EASE_IN);
321         }
322         anim.Play();
323
324         // Set end of pan configuration and disconnect the actor from the pan detector
325         mShakeAnimation.SetLooping(false);
326         mPanStarted = false;
327         mPanDetector.Detach(actor);
328         break;
329       }
330
331       default:
332       {
333         break;
334       }
335     }
336   }
337
338   /**
339    * @brief Called when a tap gesture is detected on our control.
340    *
341    * @param[in]  actor The actor that's been tapped
342    * @param[in]  tap   The tap gesture information
343    */
344   void OnTap(Actor actor, const TapGesture& tap)
345   {
346     // Do a short animation to show a tap has happened.
347
348     Animation anim = Animation::New(TAP_ANIMATION_DURATION);
349     anim.AnimateBy(Property(actor, Actor::Property::ORIENTATION), Quaternion(ANGLE_360, Vector3::ZAXIS));
350     anim.AnimateTo(Property(actor, Actor::Property::SCALE), Vector3::ONE, AlphaFunction::LINEAR);
351     anim.AnimateTo(Property(actor, Actor::Property::COLOR), TAP_ANIMATION_COLOR, AlphaFunction::BOUNCE);
352     anim.AnimateTo(Property(actor, Actor::Property::POSITION), Vector3::ZERO, AlphaFunction::EASE_OUT_SQUARE);
353     anim.Play();
354   }
355
356   /**
357    * @brief Called when a pinch gesture is detected on our control.
358    *
359    * @param[in]  actor  The actor that's been pinched
360    * @param[in]  pinch  The pinch gesture information
361    */
362   void OnPinch(Actor actor, const PinchGesture& pinch)
363   {
364     switch(pinch.GetState())
365     {
366       case GestureState::STARTED:
367       {
368         // Starting scale is required so that we know what to multiply the pinch.scale by.
369         mStartingScale = actor.GetCurrentProperty<Vector3>(Actor::Property::SCALE);
370         break;
371       }
372
373       case GestureState::FINISHED:
374       case GestureState::CANCELLED:
375       {
376         Vector3 scale(actor.GetCurrentProperty<Vector3>(Actor::Property::SCALE));
377
378         // Ensure the actor sizes itself to be within the limits defined.
379         if(scale.x < MINIMUM_SCALE.x)
380         {
381           scale = MINIMUM_SCALE;
382         }
383         else if(scale.x > MAXIMUM_SCALE.x)
384         {
385           scale = MAXIMUM_SCALE;
386         }
387
388         // Do an animation to come back to go back to the limits.
389         Animation anim = Animation::New(SCALE_BACK_ANIMATION_DURATION);
390         anim.AnimateTo(Property(actor, Actor::Property::SCALE), scale, AlphaFunction::LINEAR);
391         anim.Play();
392         break;
393       }
394
395       default:
396       {
397         break;
398       }
399     }
400
401     actor.SetProperty(Actor::Property::SCALE, mStartingScale * pinch.GetScale());
402   }
403
404   /**
405    * @brief Called when a rotation gesture is detected on our control.
406    *
407    * @param[in]  actor     The actor that's been pinched
408    * @param[in]  rotation  The rotation gesture information
409    */
410   void OnRotation(Actor actor, const RotationGesture& rotation)
411   {
412     switch(rotation.GetState())
413     {
414       case GestureState::STARTED:
415       {
416         // Starting orientation is required so that we know what to multiply the rotation.rotation by.
417         mStartingOrientation = actor.GetCurrentProperty<Quaternion>(Actor::Property::ORIENTATION);
418         break;
419       }
420
421       case GestureState::FINISHED:
422       case GestureState::CANCELLED:
423       {
424         // Do an animation to come back to go back to the original orientation.
425         Animation anim = Animation::New(ROTATE_BACK_ANIMATION_DURATION);
426         anim.AnimateTo(Property(actor, Actor::Property::ORIENTATION), Quaternion::IDENTITY, AlphaFunction::LINEAR);
427         anim.Play();
428         break;
429       }
430
431       default:
432       {
433         break;
434       }
435     }
436
437     actor.SetProperty(Actor::Property::ORIENTATION, Quaternion(mStartingOrientation * Quaternion(rotation.GetRotation(), Vector3::ZAXIS)));
438   }
439
440   /**
441    * @brief Called when any key event is received.
442    *
443    * Will use this to quit the application if Back or the Escape key is received.
444    * @param[in] event The key event information
445    */
446   void OnKeyEvent(const KeyEvent& event)
447   {
448     if(event.GetState() == KeyEvent::DOWN)
449     {
450       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
451       {
452         mApplication.Quit();
453       }
454     }
455   }
456
457 private:
458   Application& mApplication;
459
460   PanGestureDetector       mPanDetector;
461   LongPressGestureDetector mLongPressDetector;
462   TapGestureDetector       mTapDetector;
463   PinchGestureDetector     mPinchDetector;
464   RotationGestureDetector  mRotationDetector;
465
466   Vector3    mStartingScale;       ///< Set to the scale of the control when pinch starts.
467   Quaternion mStartingOrientation; ///< Set to the orientation of the control when the rotation starts.
468   Animation  mShakeAnimation;      ///< "Shake" animation to show when we are in panning mode.
469   bool       mPanStarted = false;  ///< Set to true to state that panning has started.
470 };
471
472 int DALI_EXPORT_API main(int argc, char** argv)
473 {
474   Application    application = Application::New(&argc, &argv);
475   GestureExample controller(application);
476   application.MainLoop();
477   return 0;
478 }