diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Platform.WinRT/VisualElementTracker.cs | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Platform.WinRT/VisualElementTracker.cs')
-rw-r--r-- | Xamarin.Forms.Platform.WinRT/VisualElementTracker.cs | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.WinRT/VisualElementTracker.cs b/Xamarin.Forms.Platform.WinRT/VisualElementTracker.cs new file mode 100644 index 00000000..0c3f7595 --- /dev/null +++ b/Xamarin.Forms.Platform.WinRT/VisualElementTracker.cs @@ -0,0 +1,534 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; + +#if WINDOWS_UWP + +namespace Xamarin.Forms.Platform.UWP +#else + +namespace Xamarin.Forms.Platform.WinRT +#endif +{ + public class VisualElementTracker<TElement, TNativeElement> : IDisposable where TElement : VisualElement where TNativeElement : FrameworkElement + { + readonly NotifyCollectionChangedEventHandler _collectionChangedHandler; + readonly List<uint> _fingers = new List<uint>(); + FrameworkElement _container; + TNativeElement _control; + TElement _element; + + bool _invalidateArrangeNeeded; + + bool _isDisposed; + bool _isPanning; + bool _isPinching; + bool _wasPanGestureStartedSent; + bool _wasPinchGestureStartedSent; + + public VisualElementTracker() + { + _collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged; + } + + public FrameworkElement Container + { + get { return _container; } + set + { + if (_container == value) + return; + + if (_container != null) + { + _container.Tapped -= OnTap; + _container.DoubleTapped -= OnDoubleTap; + _container.ManipulationDelta -= OnManipulationDelta; + _container.ManipulationStarted -= OnManipulationStarted; + _container.ManipulationCompleted -= OnManipulationCompleted; + _container.PointerPressed -= OnPointerPressed; + _container.PointerExited -= OnPointerExited; + _container.PointerReleased -= OnPointerReleased; + _container.PointerCanceled -= OnPointerCanceled; + } + + _container = value; + + UpdatingGestureRecognizers(); + + UpdateNativeControl(); + } + } + + public TNativeElement Control + { + get { return _control; } + set + { + if (_control == value) + return; + + _control = value; + UpdateNativeControl(); + } + } + + public TElement Element + { + get { return _element; } + set + { + if (_element == value) + return; + + if (_element != null) + { + _element.BatchCommitted -= OnRedrawNeeded; + _element.PropertyChanged -= OnPropertyChanged; + + var view = _element as View; + if (view != null) + { + var oldRecognizers = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers; + oldRecognizers.CollectionChanged -= _collectionChangedHandler; + } + } + + _element = value; + + if (_element != null) + { + _element.BatchCommitted += OnRedrawNeeded; + _element.PropertyChanged += OnPropertyChanged; + + var view = _element as View; + if (view != null) + { + var newRecognizers = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers; + newRecognizers.CollectionChanged += _collectionChangedHandler; + } + } + + UpdateNativeControl(); + } + } + + public void Dispose() + { + Dispose(true); + } + + public event EventHandler Updated; + + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + _isDisposed = true; + + if (!disposing) + return; + + if (_container != null) + { + _container.PointerPressed -= OnPointerPressed; + _container.PointerExited -= OnPointerExited; + _container.PointerReleased -= OnPointerReleased; + _container.PointerCanceled -= OnPointerCanceled; + _container.Tapped -= OnTap; + _container.DoubleTapped -= OnDoubleTap; + _container.ManipulationDelta -= OnManipulationDelta; + _container.ManipulationStarted -= OnManipulationStarted; + _container.ManipulationCompleted -= OnManipulationCompleted; + } + + if (_element != null) + { + _element.BatchCommitted -= OnRedrawNeeded; + _element.PropertyChanged -= OnPropertyChanged; + + var view = _element as View; + if (view != null) + { + var oldRecognizers = (ObservableCollection<IGestureRecognizer>)view.GestureRecognizers; + oldRecognizers.CollectionChanged -= _collectionChangedHandler; + } + } + + Control = null; + Element = null; + Container = null; + } + + protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (Element.Batched) + { + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + { + _invalidateArrangeNeeded = true; + } + return; + } + + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + { + MaybeInvalidate(); + } + else if (e.PropertyName == VisualElement.AnchorXProperty.PropertyName || e.PropertyName == VisualElement.AnchorYProperty.PropertyName) + { + UpdateScaleAndRotation(Element, Container); + } + else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName) + { + UpdateScaleAndRotation(Element, Container); + } + else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName || e.PropertyName == VisualElement.TranslationYProperty.PropertyName || + e.PropertyName == VisualElement.RotationProperty.PropertyName || e.PropertyName == VisualElement.RotationXProperty.PropertyName || e.PropertyName == VisualElement.RotationYProperty.PropertyName) + { + UpdateRotation(Element, Container); + } + else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName) + { + UpdateVisibility(Element, Container); + } + else if (e.PropertyName == VisualElement.OpacityProperty.PropertyName) + { + UpdateOpacity(Element, Container); + } + else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName) + { + UpdateInputTransparent(Element, Container); + } + } + + protected virtual void UpdateNativeControl() + { + if (Element == null || Container == null) + return; + + UpdateVisibility(Element, Container); + UpdateOpacity(Element, Container); + UpdateScaleAndRotation(Element, Container); + UpdateInputTransparent(Element, Container); + + if (_invalidateArrangeNeeded) + { + MaybeInvalidate(); + } + _invalidateArrangeNeeded = false; + + OnUpdated(); + } + + void HandlePan(ManipulationDeltaRoutedEventArgs e, View view) + { + if (view == null) + return; + + _isPanning = true; + + foreach (PanGestureRecognizer recognizer in view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Where(g => g.TouchPoints == _fingers.Count)) + { + if (!_wasPanGestureStartedSent) + { + ((IPanGestureController)recognizer).SendPanStarted(view, Application.Current.PanGestureId); + } + ((IPanGestureController)recognizer).SendPan(view, e.Delta.Translation.X + e.Cumulative.Translation.X, e.Delta.Translation.Y + e.Cumulative.Translation.Y, Application.Current.PanGestureId); + } + _wasPanGestureStartedSent = true; + } + + void HandlePinch(ManipulationDeltaRoutedEventArgs e, View view) + { + if (_fingers.Count < 2 || view == null) + return; + + _isPinching = true; + + Windows.Foundation.Point translationPoint = e.Container.TransformToVisual(Container).TransformPoint(e.Position); + + var scaleOriginPoint = new Point(translationPoint.X / view.Width, translationPoint.Y / view.Height); + IEnumerable<PinchGestureRecognizer> pinchGestures = view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>(); + foreach (PinchGestureRecognizer recognizer in pinchGestures) + { + if (!_wasPinchGestureStartedSent) + { + ((IPinchGestureController)recognizer).SendPinchStarted(view, scaleOriginPoint); + } + ((IPinchGestureController)recognizer).SendPinch(view, e.Delta.Scale, scaleOriginPoint); + } + _wasPinchGestureStartedSent = true; + } + + void MaybeInvalidate() + { + if (Element.IsInNativeLayout) + return; + var parent = (FrameworkElement)Container.Parent; + parent?.InvalidateMeasure(); + Container.InvalidateMeasure(); + } + + void ModelGestureRecognizersOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + UpdatingGestureRecognizers(); + } + + void OnDoubleTap(object sender, DoubleTappedRoutedEventArgs e) + { + var view = Element as View; + if (view == null) + return; + + IEnumerable<TapGestureRecognizer> doubleTapGestures = view.GestureRecognizers.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 2); + foreach (TapGestureRecognizer recognizer in doubleTapGestures) + recognizer.SendTapped(view); + } + + void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + { + PinchComplete(true); + PanComplete(true); + } + + void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + var view = Element as View; + if (view == null) + return; + + HandlePinch(e, view); + HandlePan(e, view); + } + + void OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) + { + var view = Element as View; + if (view == null) + return; + _wasPinchGestureStartedSent = false; + _wasPanGestureStartedSent = false; + } + + void OnPointerCanceled(object sender, PointerRoutedEventArgs e) + { + uint id = e.Pointer.PointerId; + if (_fingers.Contains(id)) + _fingers.Remove(id); + + PinchComplete(false); + PanComplete(false); + } + + void OnPointerExited(object sender, PointerRoutedEventArgs e) + { + uint id = e.Pointer.PointerId; + if (_fingers.Contains(id)) + _fingers.Remove(id); + + PinchComplete(true); + PanComplete(true); + } + + void OnPointerPressed(object sender, PointerRoutedEventArgs e) + { + uint id = e.Pointer.PointerId; + if (!_fingers.Contains(id)) + _fingers.Add(id); + } + + void OnPointerReleased(object sender, PointerRoutedEventArgs e) + { + uint id = e.Pointer.PointerId; + if (_fingers.Contains(id)) + _fingers.Remove(id); + + PinchComplete(true); + PanComplete(true); + } + + void OnRedrawNeeded(object sender, EventArgs e) + { + UpdateNativeControl(); + } + + void OnTap(object sender, TappedRoutedEventArgs e) + { + var view = Element as View; + if (view == null) + return; + + IEnumerable<TapGestureRecognizer> tapGestures = view.GestureRecognizers.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 1); + foreach (TapGestureRecognizer recognizer in tapGestures) + { + recognizer.SendTapped(view); + e.Handled = true; + } + } + + void OnUpdated() + { + if (Updated != null) + Updated(this, EventArgs.Empty); + } + + void PanComplete(bool success) + { + var view = Element as View; + if (view == null || !_isPanning) + return; + + foreach (PanGestureRecognizer recognizer in view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Where(g => g.TouchPoints == _fingers.Count)) + { + if (success) + { + ((IPanGestureController)recognizer).SendPanCompleted(view, Application.Current.PanGestureId); + } + else + { + ((IPanGestureController)recognizer).SendPanCanceled(view, Application.Current.PanGestureId); + } + } + + Application.Current.PanGestureId++; + _isPanning = false; + } + + void PinchComplete(bool success) + { + var view = Element as View; + if (view == null || !_isPinching) + return; + + IEnumerable<PinchGestureRecognizer> pinchGestures = view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>(); + foreach (PinchGestureRecognizer recognizer in pinchGestures) + { + if (success) + { + ((IPinchGestureController)recognizer).SendPinchEnded(view); + } + else + { + ((IPinchGestureController)recognizer).SendPinchCanceled(view); + } + } + + _isPinching = false; + } + + static void UpdateInputTransparent(VisualElement view, FrameworkElement frameworkElement) + { + frameworkElement.IsHitTestVisible = !view.InputTransparent; + } + + static void UpdateOpacity(VisualElement view, FrameworkElement frameworkElement) + { + frameworkElement.Opacity = view.Opacity; + } + + static void UpdateRotation(VisualElement view, FrameworkElement frameworkElement) + { + double anchorX = view.AnchorX; + double anchorY = view.AnchorY; + double rotationX = view.RotationX; + double rotationY = view.RotationY; + double rotation = view.Rotation; + double translationX = view.TranslationX; + double translationY = view.TranslationY; + double scale = view.Scale; + + if (rotationX % 360 == 0 && rotationY % 360 == 0 && rotation % 360 == 0 && translationX == 0 && translationY == 0 && scale == 1) + { + frameworkElement.Projection = null; + } + else + { + frameworkElement.Projection = new PlaneProjection + { + CenterOfRotationX = anchorX, + CenterOfRotationY = anchorY, + GlobalOffsetX = translationX / scale, + GlobalOffsetY = translationY / scale, + RotationX = -rotationX, + RotationY = -rotationY, + RotationZ = -rotation + }; + } + } + + static void UpdateScaleAndRotation(VisualElement view, FrameworkElement frameworkElement) + { + double anchorX = view.AnchorX; + double anchorY = view.AnchorY; + double scale = view.Scale; + frameworkElement.RenderTransformOrigin = new Windows.Foundation.Point(anchorX, anchorY); + frameworkElement.RenderTransform = new ScaleTransform { ScaleX = scale, ScaleY = scale }; + + UpdateRotation(view, frameworkElement); + } + + static void UpdateVisibility(VisualElement view, FrameworkElement frameworkElement) + { + frameworkElement.Visibility = view.IsVisible ? Visibility.Visible : Visibility.Collapsed; + } + + void UpdatingGestureRecognizers() + { + var view = Element as View; + IList<IGestureRecognizer> gestures = view?.GestureRecognizers; + + if (_container == null || gestures == null) + return; + + _container.Tapped -= OnTap; + _container.DoubleTapped -= OnDoubleTap; + _container.ManipulationDelta -= OnManipulationDelta; + _container.ManipulationStarted -= OnManipulationStarted; + _container.ManipulationCompleted -= OnManipulationCompleted; + _container.PointerPressed -= OnPointerPressed; + _container.PointerExited -= OnPointerExited; + _container.PointerReleased -= OnPointerReleased; + _container.PointerCanceled -= OnPointerCanceled; + + if (gestures.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 1).GetEnumerator().MoveNext()) + _container.Tapped += OnTap; + + if (gestures.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 2).GetEnumerator().MoveNext()) + _container.DoubleTapped += OnDoubleTap; + + bool hasPinchGesture = gestures.GetGesturesFor<PinchGestureRecognizer>().GetEnumerator().MoveNext(); + bool hasPanGesture = gestures.GetGesturesFor<PanGestureRecognizer>().GetEnumerator().MoveNext(); + if (!hasPinchGesture && !hasPanGesture) + return; + + //We can't handle ManipulationMode.Scale and System , so we don't support pinch/pan on a scrollview + if (Element is ScrollView) + { + if (hasPinchGesture) + Log.Warning("Gestures", "PinchGestureRecognizer is not supported on a ScrollView in Windows Platforms"); + if (hasPanGesture) + Log.Warning("Gestures", "PanGestureRecognizer is not supported on a ScrollView in Windows Platforms"); + return; + } + + _container.ManipulationMode = ManipulationModes.Scale | ManipulationModes.TranslateX | ManipulationModes.TranslateY; + _container.ManipulationDelta += OnManipulationDelta; + _container.ManipulationStarted += OnManipulationStarted; + _container.ManipulationCompleted += OnManipulationCompleted; + _container.PointerPressed += OnPointerPressed; + _container.PointerExited += OnPointerExited; + _container.PointerReleased += OnPointerReleased; + _container.PointerCanceled += OnPointerCanceled; + } + } +}
\ No newline at end of file |