375 строки
12 KiB
C#
375 строки
12 KiB
C#
|
using Gtk;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Collections.ObjectModel;
|
|||
|
using System.Collections.Specialized;
|
|||
|
using System.ComponentModel;
|
|||
|
using System.Linq;
|
|||
|
using Xamarin.Forms.Internals;
|
|||
|
using Xamarin.Forms.Platform.GTK.Extensions;
|
|||
|
|
|||
|
namespace Xamarin.Forms.Platform.GTK
|
|||
|
{
|
|||
|
public class VisualElementTracker<TElement, TNativeElement> : IDisposable where TElement : VisualElement where TNativeElement : Widget
|
|||
|
{
|
|||
|
private bool _isDisposed;
|
|||
|
private TNativeElement _control;
|
|||
|
private TElement _element;
|
|||
|
private EventBox _container;
|
|||
|
private bool _invalidateArrangeNeeded;
|
|||
|
|
|||
|
private readonly NotifyCollectionChangedEventHandler _collectionChangedHandler;
|
|||
|
|
|||
|
public event EventHandler Updated;
|
|||
|
|
|||
|
public VisualElementTracker()
|
|||
|
{
|
|||
|
_collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged;
|
|||
|
}
|
|||
|
|
|||
|
public EventBox Container
|
|||
|
{
|
|||
|
get { return _container; }
|
|||
|
set
|
|||
|
{
|
|||
|
if (_container == value)
|
|||
|
return;
|
|||
|
|
|||
|
if (_container != null)
|
|||
|
{
|
|||
|
_container.ButtonPressEvent -= OnContainerButtonPressEvent;
|
|||
|
}
|
|||
|
|
|||
|
_container = value;
|
|||
|
|
|||
|
UpdatingGestureRecognizers();
|
|||
|
|
|||
|
UpdateNativeControl();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public TNativeElement Control
|
|||
|
{
|
|||
|
get { return _control; }
|
|||
|
set
|
|||
|
{
|
|||
|
if (_control == value)
|
|||
|
return;
|
|||
|
|
|||
|
if (_control != null)
|
|||
|
{
|
|||
|
_control.ButtonPressEvent -= OnControlButtonPressEvent;
|
|||
|
}
|
|||
|
|
|||
|
_control = value;
|
|||
|
UpdateNativeControl();
|
|||
|
|
|||
|
if (PreventGestureBubbling)
|
|||
|
{
|
|||
|
UpdatingGestureRecognizers();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public bool PreventGestureBubbling { get; set; }
|
|||
|
|
|||
|
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();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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();
|
|||
|
}
|
|||
|
|
|||
|
public void Dispose()
|
|||
|
{
|
|||
|
Dispose(true);
|
|||
|
}
|
|||
|
|
|||
|
protected virtual void Dispose(bool disposing)
|
|||
|
{
|
|||
|
if (_isDisposed)
|
|||
|
return;
|
|||
|
|
|||
|
_isDisposed = true;
|
|||
|
|
|||
|
if (!disposing)
|
|||
|
return;
|
|||
|
|
|||
|
if (_container != null)
|
|||
|
{
|
|||
|
_container.ButtonPressEvent -= OnContainerButtonPressEvent;
|
|||
|
}
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (_control != null)
|
|||
|
{
|
|||
|
_control.ButtonPressEvent -= OnControlButtonPressEvent;
|
|||
|
}
|
|||
|
|
|||
|
Container.Dispose();
|
|||
|
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.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);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ModelGestureRecognizersOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
|
|||
|
{
|
|||
|
UpdatingGestureRecognizers();
|
|||
|
}
|
|||
|
|
|||
|
private void OnUpdated()
|
|||
|
{
|
|||
|
Updated?.Invoke(this, EventArgs.Empty);
|
|||
|
}
|
|||
|
|
|||
|
private void OnRedrawNeeded(object sender, EventArgs e)
|
|||
|
{
|
|||
|
UpdateNativeControl();
|
|||
|
}
|
|||
|
|
|||
|
private void UpdatingGestureRecognizers()
|
|||
|
{
|
|||
|
var view = Element as View;
|
|||
|
IList<IGestureRecognizer> gestures = view?.GestureRecognizers;
|
|||
|
|
|||
|
if (_container == null || gestures == null)
|
|||
|
return;
|
|||
|
|
|||
|
_container.ButtonPressEvent -= OnContainerButtonPressEvent;
|
|||
|
|
|||
|
if (gestures.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 1).Any())
|
|||
|
{
|
|||
|
_container.ButtonPressEvent += OnContainerButtonPressEvent;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (_control != null && PreventGestureBubbling)
|
|||
|
{
|
|||
|
_control.ButtonPressEvent += OnControlButtonPressEvent;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
bool hasPinchGesture = gestures.GetGesturesFor<PinchGestureRecognizer>().GetEnumerator().MoveNext();
|
|||
|
bool hasPanGesture = gestures.GetGesturesFor<PanGestureRecognizer>().GetEnumerator().MoveNext();
|
|||
|
|
|||
|
if (!hasPinchGesture && !hasPanGesture)
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
private void MaybeInvalidate()
|
|||
|
{
|
|||
|
if (Element.IsInNativeLayout)
|
|||
|
return;
|
|||
|
|
|||
|
var parent = Container.Parent;
|
|||
|
parent?.QueueDraw();
|
|||
|
Container.QueueDraw();
|
|||
|
}
|
|||
|
|
|||
|
// TODO: Implement Scale
|
|||
|
private static void UpdateScaleAndRotation(VisualElement view, EventBox eventBox)
|
|||
|
{
|
|||
|
double anchorX = view.AnchorX;
|
|||
|
double anchorY = view.AnchorY;
|
|||
|
double scale = view.Scale;
|
|||
|
|
|||
|
UpdateRotation(view, eventBox);
|
|||
|
}
|
|||
|
|
|||
|
// TODO: Implement Rotation
|
|||
|
private static void UpdateRotation(VisualElement view, EventBox eventBox)
|
|||
|
{
|
|||
|
if (view == null)
|
|||
|
return;
|
|||
|
|
|||
|
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;
|
|||
|
|
|||
|
var viewRenderer = Platform.GetRenderer(view) as Widget;
|
|||
|
|
|||
|
if (viewRenderer == null)
|
|||
|
return;
|
|||
|
|
|||
|
if (rotationX % 360 == 0 &&
|
|||
|
rotationY % 360 == 0 &&
|
|||
|
rotation % 360 == 0 &&
|
|||
|
translationX == 0 &&
|
|||
|
translationY == 0 &&
|
|||
|
scale == 1)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
viewRenderer.MoveTo(
|
|||
|
scale == 0 ? 0 : translationX / scale,
|
|||
|
scale == 0 ? 0 : translationY / scale);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static void UpdateVisibility(VisualElement view, EventBox eventBox)
|
|||
|
{
|
|||
|
eventBox.Visible = view.IsVisible;
|
|||
|
}
|
|||
|
|
|||
|
// TODO: Implement Opacity
|
|||
|
private static void UpdateOpacity(VisualElement view, EventBox eventBox)
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// TODO: Implement InputTransparent
|
|||
|
private static void UpdateInputTransparent(VisualElement view, EventBox eventBox)
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
private void OnContainerButtonPressEvent(object o, ButtonPressEventArgs args)
|
|||
|
{
|
|||
|
if (args.Event.Button != 1)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var view = Element as View;
|
|||
|
|
|||
|
if (view == null)
|
|||
|
return;
|
|||
|
|
|||
|
if (args.Event.Type == Gdk.EventType.TwoButtonPress)
|
|||
|
{
|
|||
|
IEnumerable<TapGestureRecognizer> doubleTapGestures = view.GestureRecognizers
|
|||
|
.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 2);
|
|||
|
|
|||
|
foreach (TapGestureRecognizer recognizer in doubleTapGestures)
|
|||
|
recognizer.SendTapped(view);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
IEnumerable<TapGestureRecognizer> tapGestures = view.GestureRecognizers
|
|||
|
.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 1);
|
|||
|
|
|||
|
foreach (TapGestureRecognizer recognizer in tapGestures)
|
|||
|
recognizer.SendTapped(view);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void OnControlButtonPressEvent(object o, ButtonPressEventArgs args)
|
|||
|
{
|
|||
|
args.RetVal = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|