diff options
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen/GestureHandler.cs')
-rw-r--r-- | Xamarin.Forms.Platform.Tizen/GestureHandler.cs | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Tizen/GestureHandler.cs b/Xamarin.Forms.Platform.Tizen/GestureHandler.cs new file mode 100644 index 00000000..209da3e3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/GestureHandler.cs @@ -0,0 +1,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); + } + } +} |