386 строки
11 KiB
C#
386 строки
11 KiB
C#
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;
|
|
using Xamarin.Forms.Internals;
|
|
|
|
namespace Xamarin.Forms.Platform.WinPhone
|
|
{
|
|
public abstract class VisualElementTracker : IDisposable
|
|
{
|
|
public abstract FrameworkElement Child { get; set; }
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
_disposed = true;
|
|
|
|
if (disposing)
|
|
{
|
|
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;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
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 = scale == 0 ? 0 : translationX / scale,
|
|
GlobalOffsetY = scale == 0 ? 0 : 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;
|
|
}
|
|
}
|
|
} |