summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.WP8/VisualElementTracker.cs')
-rw-r--r--Xamarin.Forms.Platform.WP8/VisualElementTracker.cs370
1 files changed, 370 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs b/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs
new file mode 100644
index 00000000..8211ec63
--- /dev/null
+++ b/Xamarin.Forms.Platform.WP8/VisualElementTracker.cs
@@ -0,0 +1,370 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace Xamarin.Forms.Platform.WinPhone
+{
+ public abstract class VisualElementTracker : IDisposable
+ {
+ public abstract FrameworkElement Child { get; set; }
+
+ public abstract void Dispose();
+
+ public event EventHandler Updated;
+
+ protected void OnUpdated()
+ {
+ if (Updated != null)
+ Updated(this, EventArgs.Empty);
+ }
+ }
+
+ public class VisualElementTracker<TModel, TElement> : VisualElementTracker where TModel : VisualElement where TElement : FrameworkElement
+ {
+ FrameworkElement _child;
+ bool _disposed;
+ TElement _element;
+
+ bool _invalidateArrangeNeeded;
+ bool _isPanning;
+ bool _isPinching;
+
+ TModel _model;
+ bool _touchFrameReportedEventSet;
+ int _touchPoints = 1;
+
+ public override FrameworkElement Child
+ {
+ get { return _child; }
+ set
+ {
+ if (_child == value)
+ return;
+ _child = value;
+ UpdateNativeControl();
+ }
+ }
+
+ public TElement Element
+ {
+ get { return _element; }
+ set
+ {
+ if (_element == value)
+ return;
+
+ if (_element != null)
+ {
+ _element.Tap -= ElementOnTap;
+ _element.DoubleTap -= ElementOnDoubleTap;
+ _element.ManipulationDelta -= OnManipulationDelta;
+ _element.ManipulationCompleted -= OnManipulationCompleted;
+ }
+
+ _element = value;
+
+ if (_element != null)
+ {
+ _element.Tap += ElementOnTap;
+ _element.DoubleTap += ElementOnDoubleTap;
+ _element.ManipulationDelta += OnManipulationDelta;
+ _element.ManipulationCompleted += OnManipulationCompleted;
+ }
+
+ UpdateNativeControl();
+ }
+ }
+
+ public TModel Model
+ {
+ get { return _model; }
+ set
+ {
+ if (_model == value)
+ return;
+
+ if (_model != null)
+ {
+ _model.BatchCommitted -= HandleRedrawNeeded;
+ _model.PropertyChanged -= HandlePropertyChanged;
+ }
+
+ _model = value;
+
+ if (_model != null)
+ {
+ _model.BatchCommitted += HandleRedrawNeeded;
+ _model.PropertyChanged += HandlePropertyChanged;
+ }
+
+ UpdateNativeControl();
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (_disposed)
+ return;
+ _disposed = true;
+
+ if (_element != null)
+ {
+ _element.Tap -= ElementOnTap;
+ _element.DoubleTap -= ElementOnDoubleTap;
+ _element.ManipulationDelta -= OnManipulationDelta;
+ _element.ManipulationCompleted -= OnManipulationCompleted;
+ }
+
+ if (_model != null)
+ {
+ _model.BatchCommitted -= HandleRedrawNeeded;
+ _model.PropertyChanged -= HandlePropertyChanged;
+ }
+
+ Child = null;
+ Model = null;
+ Element = null;
+ }
+
+ protected virtual void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (Model.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(Model, Element);
+ else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName)
+ UpdateScaleAndRotation(Model, Element);
+ 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(Model, Element);
+ else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName)
+ UpdateVisibility(Model, Element);
+ else if (e.PropertyName == VisualElement.OpacityProperty.PropertyName)
+ UpdateOpacity(Model, Element);
+ else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+ UpdateInputTransparent(Model, Element);
+ }
+
+ protected virtual void UpdateNativeControl()
+ {
+ if (Model == null || Element == null)
+ return;
+
+ UpdateOpacity(_model, _element);
+ UpdateScaleAndRotation(_model, _element);
+ UpdateInputTransparent(_model, _element);
+
+ if (_invalidateArrangeNeeded)
+ MaybeInvalidate();
+ _invalidateArrangeNeeded = false;
+
+ UpdateTouchFrameReportedEvent(_model);
+
+ OnUpdated();
+ }
+
+ void ElementOnDoubleTap(object sender, GestureEventArgs gestureEventArgs)
+ {
+ var view = Model as View;
+ if (view == null)
+ return;
+
+ foreach (TapGestureRecognizer gestureRecognizer in
+ view.GestureRecognizers.OfType<TapGestureRecognizer>().Where(g => g.NumberOfTapsRequired == 2))
+ {
+ gestureRecognizer.SendTapped(view);
+ gestureEventArgs.Handled = true;
+ }
+ }
+
+ void ElementOnTap(object sender, GestureEventArgs gestureEventArgs)
+ {
+ var view = Model as View;
+ if (view == null)
+ return;
+
+ foreach (TapGestureRecognizer gestureRecognizer in
+ view.GestureRecognizers.OfType<TapGestureRecognizer>().Where(g => g.NumberOfTapsRequired == 1))
+ {
+ gestureRecognizer.SendTapped(view);
+ gestureEventArgs.Handled = true;
+ }
+ }
+
+ void HandlePan(ManipulationDeltaEventArgs e, View view)
+ {
+ foreach (PanGestureRecognizer recognizer in
+ view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Where(g => g.TouchPoints == _touchPoints))
+ {
+ if (!_isPanning)
+ ((IPanGestureController)recognizer).SendPanStarted(view, Application.Current.PanGestureId);
+
+ double totalX = 0;
+ double totalY = 0;
+
+ // Translation and CumulativeManipulation will be 0 if we have more than one touch point because it thinks we're pinching,
+ // so we'll just go ahead and use the center point of the pinch gesture to figure out how much we're panning.
+ if (_touchPoints > 1 && e.PinchManipulation != null)
+ {
+ totalX = e.PinchManipulation.Current.Center.X - e.PinchManipulation.Original.Center.X;
+ totalY = e.PinchManipulation.Current.Center.Y - e.PinchManipulation.Original.Center.Y;
+ }
+ else
+ {
+ totalX = e.DeltaManipulation.Translation.X + e.CumulativeManipulation.Translation.X;
+ totalY = e.DeltaManipulation.Translation.Y + e.CumulativeManipulation.Translation.Y;
+ }
+
+ ((IPanGestureController)recognizer).SendPan(view, totalX, totalY, Application.Current.PanGestureId);
+ _isPanning = true;
+ }
+ }
+
+ void HandlePinch(ManipulationDeltaEventArgs e, View view)
+ {
+ if (e.PinchManipulation == null)
+ return;
+
+ IEnumerable<PinchGestureRecognizer> pinchGestures = view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>();
+ System.Windows.Point translationPoint = e.ManipulationContainer.TransformToVisual(Element).Transform(e.PinchManipulation.Current.Center);
+ var scaleOriginPoint = new Point(translationPoint.X / view.Width, translationPoint.Y / view.Height);
+ foreach (var recognizer in pinchGestures)
+ {
+ if (!_isPinching)
+ ((IPinchGestureController)recognizer).SendPinchStarted(view, scaleOriginPoint);
+ ((IPinchGestureController)recognizer).SendPinch(view, e.PinchManipulation.DeltaScale, scaleOriginPoint);
+ }
+ _isPinching = true;
+ }
+
+ void HandleRedrawNeeded(object sender, EventArgs e)
+ {
+ UpdateNativeControl();
+ }
+
+ void MaybeInvalidate()
+ {
+ if (Model.IsInNativeLayout)
+ return;
+ var parent = (FrameworkElement)Element.Parent;
+ parent?.InvalidateMeasure();
+ Element.InvalidateMeasure();
+ }
+
+ void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
+ {
+ var view = Model as View;
+ if (view == null)
+ return;
+
+ IEnumerable pinchGestures = view.GestureRecognizers.GetGesturesFor<PinchGestureRecognizer>();
+ foreach (var recognizer in pinchGestures)
+ ((IPinchGestureController)recognizer).SendPinchEnded(view);
+ _isPinching = false;
+
+ IEnumerable<PanGestureRecognizer> panGestures = view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Where(g => g.TouchPoints == _touchPoints);
+ foreach (PanGestureRecognizer recognizer in panGestures)
+ ((IPanGestureController)recognizer).SendPanCompleted(view, Application.Current.PanGestureId);
+ Application.Current.PanGestureId++;
+ _isPanning = false;
+ }
+
+ void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
+ {
+ var view = Model as View;
+ if (view == null)
+ return;
+
+ HandlePinch(e, view);
+
+ HandlePan(e, view);
+ }
+
+ void Touch_FrameReported(object sender, TouchFrameEventArgs e)
+ {
+ _touchPoints = e.GetTouchPoints(Child).Count;
+ }
+
+ 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;
+
+ 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 System.Windows.Point(anchorX, anchorY);
+ frameworkElement.RenderTransform = new ScaleTransform { ScaleX = scale, ScaleY = scale };
+
+ UpdateRotation(view, frameworkElement);
+ }
+
+ void UpdateTouchFrameReportedEvent(VisualElement model)
+ {
+ if (_touchFrameReportedEventSet)
+ return;
+
+ Touch.FrameReported -= Touch_FrameReported;
+ _touchFrameReportedEventSet = false;
+
+ var view = model as View;
+ if (view == null)
+ return;
+
+ if (!view.GestureRecognizers.GetGesturesFor<PanGestureRecognizer>().Any(g => g.TouchPoints > 1))
+ return;
+
+ Touch.FrameReported += Touch_FrameReported;
+ _touchFrameReportedEventSet = true;
+ }
+
+ static void UpdateVisibility(VisualElement view, FrameworkElement frameworkElement)
+ {
+ frameworkElement.Visibility = view.IsVisible ? Visibility.Visible : Visibility.Collapsed;
+ }
+ }
+} \ No newline at end of file