6f85907bb24e9d502791f00687e78e9a75abf531
[platform/core/uifw/dali-core.git] / dali / internal / event / events / rotation-gesture / rotation-gesture-recognizer.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 // CLASS HEADER
19 #include <dali/internal/event/events/rotation-gesture/rotation-gesture-recognizer.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23
24 // INTERNAL INCLUDES
25 #include <dali/devel-api/events/touch-point.h>
26 #include <dali/public-api/math/vector2.h>
27 #include <dali/integration-api/events/touch-event-integ.h>
28 #include <dali/internal/event/events/rotation-gesture/rotation-gesture-event.h>
29 #include <dali/internal/event/common/scene-impl.h>
30
31 // INTERNAL INCLUDES
32
33
34 namespace Dali
35 {
36
37 namespace Internal
38 {
39
40 namespace
41 {
42
43 inline float GetAngle( const Integration::Point& point1, const Integration::Point& point2 )
44 {
45   const Vector2& point1ScreenPosition = point1.GetScreenPosition();
46   const Vector2& point2ScreenPosition = point2.GetScreenPosition();
47   return atan2( point2ScreenPosition.y - point1ScreenPosition.y, point2ScreenPosition.x - point1ScreenPosition.x );
48 }
49
50 inline Vector2 GetCenterPoint( const Integration::Point& point1, const Integration::Point& point2 )
51 {
52   return Vector2( point1.GetScreenPosition() + point2.GetScreenPosition() ) * 0.5f;
53 }
54
55 } // unnamed namespace
56
57 RotationGestureRecognizer::RotationGestureRecognizer( Observer& observer, uint32_t minimumTouchEvents, uint32_t minimumTouchEventsAfterStart )
58 : GestureRecognizer( GestureType::ROTATION ),
59   mObserver( observer ),
60   mState( CLEAR ),
61   mTouchEvents(),
62   mStartingAngle( 0.0f ),
63   mMinimumTouchEvents( minimumTouchEvents ),
64   mMinimumTouchEventsAfterStart( minimumTouchEventsAfterStart )
65 {
66 }
67
68 void RotationGestureRecognizer::SendEvent( const Integration::TouchEvent& event )
69 {
70   int pointCount = event.GetPointCount();
71   GestureRecognizerPtr ptr(this); // To keep us from being destroyed during the life-time of this method
72
73   switch( mState )
74   {
75     case CLEAR:
76     {
77       if( pointCount == 2 )
78       {
79         // Change state to possible as we have two touch points.
80         mState = POSSIBLE;
81         mTouchEvents.push_back( event );
82       }
83       break;
84     }
85
86     case POSSIBLE:
87     {
88       if ( pointCount != 2 )
89       {
90         // We no longer have two touch points so change state back to CLEAR.
91         mState = CLEAR;
92         mTouchEvents.clear();
93       }
94       else
95       {
96         const Integration::Point& currentPoint1 = event.points[0];
97         const Integration::Point& currentPoint2 = event.points[1];
98
99         if( currentPoint1.GetState() == PointState::UP || currentPoint2.GetState() == PointState::UP || currentPoint1.GetState() == PointState::INTERRUPTED )
100         {
101           // One of our touch points has an Up event so change our state back to CLEAR.
102           mState = CLEAR;
103           mTouchEvents.clear();
104         }
105         else
106         {
107           mTouchEvents.push_back( event );
108
109           // We can only determine a rotation after a certain number of touch points have been collected.
110           if( mTouchEvents.size() >= mMinimumTouchEvents )
111           {
112             // Remove the first few events from the vector otherwise values are exaggerated
113             mTouchEvents.erase( mTouchEvents.begin(), mTouchEvents.end() - mMinimumTouchEvents );
114
115             if( !mTouchEvents.empty() )
116             {
117               mStartingAngle = GetAngle( mTouchEvents.begin()->points[0], mTouchEvents.begin()->points[1] );
118
119               // Send rotation started
120               SendRotation( GestureState::STARTED, event );
121
122               mState = STARTED;
123             }
124
125             mTouchEvents.clear();
126
127             if( mState == POSSIBLE )
128             {
129               // No rotation, so restart detection
130               mState = CLEAR;
131               mTouchEvents.clear();
132             }
133           }
134         }
135       }
136       break;
137     }
138
139     case STARTED:
140     {
141       if(event.points[0].GetState() == PointState::INTERRUPTED)
142       {
143         // System interruption occurred, rotation should be cancelled
144         mTouchEvents.clear();
145         SendRotation(GestureState::CANCELLED, event);
146         mState = CLEAR;
147         mTouchEvents.clear();
148       }
149       else if( pointCount != 2 )
150       {
151         // Send rotation finished event
152         SendRotation( GestureState::FINISHED, event );
153
154         mState = CLEAR;
155         mTouchEvents.clear();
156       }
157       else
158       {
159         const Integration::Point& currentPoint1 = event.points[0];
160         const Integration::Point& currentPoint2 = event.points[1];
161
162         if( ( currentPoint1.GetState() == PointState::UP ) ||
163             ( currentPoint2.GetState() == PointState::UP ) )
164         {
165           mTouchEvents.push_back( event );
166           // Send rotation finished event
167           SendRotation( GestureState::FINISHED, event );
168
169           mState = CLEAR;
170           mTouchEvents.clear();
171         }
172         else
173         {
174           mTouchEvents.push_back( event );
175
176           if( mTouchEvents.size() >= mMinimumTouchEventsAfterStart )
177           {
178             // Send rotation continuing
179             SendRotation( GestureState::CONTINUING, event );
180
181             mTouchEvents.clear();
182           }
183         }
184       }
185       break;
186     }
187   }
188 }
189
190 void RotationGestureRecognizer::SetMinimumTouchEvents( uint32_t value )
191 {
192   mMinimumTouchEvents = value;
193 }
194
195 void RotationGestureRecognizer::SetMinimumTouchEventsAfterStart( uint32_t value )
196 {
197   mMinimumTouchEventsAfterStart = value;
198 }
199
200 void RotationGestureRecognizer::SendRotation( GestureState state, const Integration::TouchEvent& currentEvent )
201 {
202   RotationGestureEvent gesture( state );
203   if( !mTouchEvents.empty() )
204   {
205     // Assert if we have been holding TouchEvents that do not have 2 points
206     DALI_ASSERT_DEBUG( mTouchEvents[0].GetPointCount() == 2 );
207
208     // We should use the current event in our calculations unless it does not have two points.
209     // If it does not have two points, then we should use the last point in mTouchEvents.
210     Integration::TouchEvent event( currentEvent );
211     if( event.GetPointCount() != 2 )
212     {
213       event = *mTouchEvents.rbegin();
214     }
215
216     const Integration::Point& currentPoint1( event.points[0] );
217     const Integration::Point& currentPoint2( event.points[1] );
218
219     gesture.rotation = GetAngle( currentPoint1, currentPoint2 ) - mStartingAngle;
220     gesture.centerPoint = GetCenterPoint( currentPoint1, currentPoint2 );
221   }
222   else
223   {
224     // Something has gone wrong, just cancel the gesture.
225     gesture.state = GestureState::CANCELLED;
226   }
227
228   gesture.time = currentEvent.time;
229
230   if( mScene )
231   {
232     // Create another handle so the recognizer cannot be destroyed during process function
233     GestureRecognizerPtr recognizerHandle = this;
234
235     mObserver.Process( *mScene, gesture );
236   }
237 }
238
239 } // namespace Internal
240
241 } // namespace Dali