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