summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Tizen/GestureHandler.cs
blob: 209da3e3eb41d2f19a10898baecd9b3bc85da3e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
using System.Linq;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections.Generic;
using ElmSharp;
using EColor = ElmSharp.Color;

namespace Xamarin.Forms.Platform.Tizen
{
	internal class GestureHandler
	{
		internal readonly IVisualElementRenderer _renderer;
		internal GestureLayer _gestureLayer;
		View _view => _renderer.Element as View;
		IPanGestureController _currentPanGestureController;
		int _currentPanGestureId;
		IPinchGestureController _currentPinchGestureController;
		Point _currentScalePoint;
		int _previousPinchRadius;
		double _originalPinchScale;
		Polygon _hitBox;

		public GestureHandler(IVisualElementRenderer renderer)
		{
			_renderer = renderer;
			// Whenever a GestureRecognizer is added to the View, it will be connected to GestureLayer
			(_view.GestureRecognizers as ObservableCollection<IGestureRecognizer>).CollectionChanged += OnGestureRecognizersChanged;
			// handle GestureRecognizers which were already set by the time we got here
			if (_view.GestureRecognizers.Count > 0)
			{
				CreateGestureLayer();
				foreach (var item in _view.GestureRecognizers)
					ToggleRecognizer(item, true);
			}
		}

		public void Clear()
		{
			// this will clear all callbacks in ElmSharp GestureLayer
			_gestureLayer.Unrealize();
			(_view.GestureRecognizers as ObservableCollection<IGestureRecognizer>).CollectionChanged -= OnGestureRecognizersChanged;
			if (_hitBox != null)
			{
				_hitBox.Unrealize();
				_hitBox = null;
			}
		}

		public void UpdateHitBox()
		{
			if (_hitBox == null)
				return;
			// _hitBox has to be used because gestures do not work well with transformations (EvasMap)
			// so we create additional object which has the same shape as tranformed target, but does not have EvasMap on it
			EvasObject target = _renderer.NativeView;
			_hitBox.ClearPoints();
			if (target.IsMapEnabled)
			{
				var map = target.EvasMap;
				Point3D point;
				for (var i = 0; i < 4; i++)
				{
					point = map.GetPointCoordinate(i);
					_hitBox.AddPoint(point.X, point.Y);
				}
			}
			else
			{
				var geometry = target.Geometry;
				if (geometry.Width == 0 || geometry.Height == 0)
					return;
				_hitBox.AddPoint(geometry.Left, geometry.Top);
				_hitBox.AddPoint(geometry.Right, geometry.Top);
				_hitBox.AddPoint(geometry.Right, geometry.Bottom);
				_hitBox.AddPoint(geometry.Left, geometry.Bottom);
			}
		}

		protected void ToggleRecognizer(IGestureRecognizer recognizer, bool enable)
		{
			TapGestureRecognizer tapRecognizer;
			PanGestureRecognizer panRecognizer;
			PinchGestureRecognizer pinchRecognizer;

			if ((tapRecognizer = recognizer as TapGestureRecognizer) != null)
			{
				ToggleTapRecognizer(tapRecognizer, enable);
			}
			else if ((panRecognizer = recognizer as PanGestureRecognizer) != null)
			{
				if (enable)
					AddPanRecognizer(panRecognizer);
				else
					RemovePanRecognizer(panRecognizer);
			}
			else if ((pinchRecognizer = recognizer as PinchGestureRecognizer) != null)
			{
				if (enable)
					AddPinchRecognizer(pinchRecognizer);
				else
					RemovePinchRecognizer(pinchRecognizer);
			}
			else
			{
				Log.Error("Unknown GestureRecognizer will be ignored: {0}", recognizer);
			}
		}

		void ToggleTapRecognizer(TapGestureRecognizer recognizer, bool enable)
		{
			GestureLayer.GestureType type;
			switch (recognizer.NumberOfTapsRequired)
			{
				case 1:
					type = GestureLayer.GestureType.Tap;
					break;
				case 2:
					type = GestureLayer.GestureType.DoubleTap;
					break;
				default:
					type = GestureLayer.GestureType.TripleTap;
					break;
			}
			if (enable)
				_gestureLayer.SetTapCallback(type, GestureLayer.GestureState.End, (data) => recognizer.SendTapped(_view));
			else
				_gestureLayer.SetTapCallback(type, GestureLayer.GestureState.End, null);
		}

		void AddPanRecognizer(PanGestureRecognizer recognizer)
		{
			if (_currentPanGestureController != null)
				Log.Warn("More than one PanGestureRecognizer on {0}. Only the last one will work.", _view);
			EnsureHitBoxExists();
			_currentPanGestureController = recognizer;
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.Start, OnPanStarted);
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.Move, OnPanMoved);
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.End, OnPanCompleted);
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.Abort, OnPanCancelled);
		}

		void RemovePanRecognizer(PanGestureRecognizer recognizer)
		{
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.Start, null);
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.Move, null);
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.End, null);
			_gestureLayer.SetLineCallback(GestureLayer.GestureState.Abort, null);
			_currentPanGestureController = null;
		}

		void AddPinchRecognizer(PinchGestureRecognizer recognizer)
		{
			if (_currentPinchGestureController != null)
				Log.Warn("More than one PinchGestureRecognizer on {0}. Only the last one will work.", _view);
			EnsureHitBoxExists();
			_currentPinchGestureController = recognizer;
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.Start, OnPinchStarted);
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.Move, OnPinchMoved);
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.End, OnPinchCompleted);
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.Abort, OnPinchCancelled);
		}

		void RemovePinchRecognizer(PinchGestureRecognizer recognizer)
		{
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.Start, null);
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.Move, null);
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.End, null);
			_gestureLayer.SetZoomCallback(GestureLayer.GestureState.Abort, null);
			_currentPinchGestureController = null;
		}

		void CreateGestureLayer()
		{
			_gestureLayer = new GestureLayer(_renderer.NativeView);
			_gestureLayer.Attach(_renderer.NativeView);
		}

		void EnsureHitBoxExists()
		{
			if (_hitBox == null)
			{
				Box parent = (Platform.GetRenderer(_renderer.Element.RealParent) as LayoutRenderer).Control;
				_hitBox = new Polygon(parent)
				{
					Color = EColor.Transparent
				};
				_hitBox.Show();
				UpdateHitBox();
				parent.PackAfter(_hitBox, _renderer.NativeView);
				_gestureLayer.Attach(_hitBox);
			}
		}

		void AddAndRemoveRecognizers(IEnumerable<IGestureRecognizer> removed, IEnumerable<IGestureRecognizer> added)
		{
			if (_hitBox == null &&
				added != null &&
				added.Any(item => (item is IPanGestureController || item is IPinchGestureController)))
			{
				// at least one of the added recognizers requires _hitBot, which is not ready
				_gestureLayer.ClearCallbacks();
				EnsureHitBoxExists();
				// as _gestureLayer was reattached, register all callbacks, not only new ones
				removed = null;
				added = _view.GestureRecognizers;
			}

			if (removed != null)
			{
				foreach (var item in removed)
					ToggleRecognizer(item, false);
			}
			if (added != null)
			{
				foreach (var item in added)
					ToggleRecognizer(item, true);
			}
		}

		void OnGestureRecognizersChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			// Gestures will be registered/unregistered according to changes in the GestureRecognizers list
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					if (_gestureLayer == null)
						CreateGestureLayer();
					AddAndRemoveRecognizers(null, e.NewItems.OfType<IGestureRecognizer>());
					break;

				case NotifyCollectionChangedAction.Replace:
					AddAndRemoveRecognizers(e.OldItems.OfType<IGestureRecognizer>(), e.NewItems.OfType<IGestureRecognizer>());
					break;

				case NotifyCollectionChangedAction.Remove:
					AddAndRemoveRecognizers(e.OldItems.OfType<IGestureRecognizer>(), null);
					break;

				case NotifyCollectionChangedAction.Reset:
					AddAndRemoveRecognizers(_view.GestureRecognizers, null);
					break;
			}
		}

		void OnPanStarted(GestureLayer.LineData data)
		{
			_currentPanGestureId++;
			_currentPanGestureController.SendPanStarted(_view, _currentPanGestureId);
		}

		void OnPanMoved(GestureLayer.LineData data)
		{
			_currentPanGestureController.SendPan(_view, data.X2 - data.X1, data.Y2 - data.Y1, _currentPanGestureId);
		}

		void OnPanCompleted(GestureLayer.LineData data)
		{
			_currentPanGestureController.SendPanCompleted(_view, _currentPanGestureId);
		}

		void OnPanCancelled(GestureLayer.LineData data)
		{
			// don't trust ElmSharp that the gesture has been aborted, report that it is completed
			_currentPanGestureController.SendPanCompleted(_view, _currentPanGestureId);
		}

		void OnPinchStarted(GestureLayer.ZoomData data)
		{
			var geometry = _renderer.NativeView.Geometry;
			_currentScalePoint = new Point((data.X - geometry.X) / (double)geometry.Width, (data.Y - geometry.Y) / (double)geometry.Height);
			_originalPinchScale = _view.Scale;
			_previousPinchRadius = data.Radius;
			_currentPinchGestureController.SendPinchStarted(_view, _currentScalePoint);
		}

		void OnPinchMoved(GestureLayer.ZoomData data)
		{
			if (_previousPinchRadius <= 0)
				_previousPinchRadius = 1;
			// functionality limitation: _currentScalePoint is not updated
			_currentPinchGestureController.SendPinch(_view,
				1 + _originalPinchScale * (data.Radius - _previousPinchRadius) / _previousPinchRadius,
				_currentScalePoint
			);
			_previousPinchRadius = data.Radius;
		}

		void OnPinchCompleted(GestureLayer.ZoomData data)
		{
			_currentPinchGestureController.SendPinchEnded(_view);
		}

		void OnPinchCancelled(GestureLayer.ZoomData data)
		{
			// ElmSharp says the gesture has been aborted really too often, report completion instead
			_currentPinchGestureController.SendPinchEnded(_view);
		}
	}
}