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.iOS/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.iOS/VisualElementTracker.cs')
-rw-r--r-- | Xamarin.Forms.Platform.iOS/VisualElementTracker.cs | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs new file mode 100644 index 00000000..0dbd7f9a --- /dev/null +++ b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs @@ -0,0 +1,259 @@ +using System; +using System.Drawing; +using System.ComponentModel; +using System.Threading; +#if __UNIFIED__ +using UIKit; +using CoreAnimation; + +#else +using MonoTouch.UIKit; +using MonoTouch.CoreAnimation; +#endif + +namespace Xamarin.Forms.Platform.iOS +{ + public class VisualElementTracker : IDisposable + { + readonly EventHandler<EventArg<VisualElement>> _batchCommittedHandler; + + readonly PropertyChangedEventHandler _propertyChangedHandler; + readonly EventHandler _sizeChangedEventHandler; + bool _disposed; + VisualElement _element; + + // Track these by hand because the calls down into iOS are too expensive + bool _isInteractive; + Rectangle _lastBounds; + + CALayer _layer; + int _updateCount; + + public VisualElementTracker(IVisualElementRenderer renderer) + { + if (renderer == null) + throw new ArgumentNullException("renderer"); + + _propertyChangedHandler = HandlePropertyChanged; + _sizeChangedEventHandler = HandleSizeChanged; + _batchCommittedHandler = HandleRedrawNeeded; + + Renderer = renderer; + renderer.ElementChanged += OnRendererElementChanged; + SetElement(null, renderer.Element); + } + + IVisualElementRenderer Renderer { get; set; } + + public void Dispose() + { + Dispose(true); + } + + public event EventHandler NativeControlUpdated; + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + SetElement(_element, null); + + if (_layer != null) + { + _layer.Dispose(); + _layer = null; + } + + Renderer.ElementChanged -= OnRendererElementChanged; + Renderer = null; + } + } + + void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName || e.PropertyName == VisualElement.AnchorXProperty.PropertyName || e.PropertyName == VisualElement.AnchorYProperty.PropertyName || + e.PropertyName == VisualElement.TranslationXProperty.PropertyName || e.PropertyName == VisualElement.TranslationYProperty.PropertyName || e.PropertyName == VisualElement.ScaleProperty.PropertyName || + e.PropertyName == VisualElement.RotationProperty.PropertyName || e.PropertyName == VisualElement.RotationXProperty.PropertyName || e.PropertyName == VisualElement.RotationYProperty.PropertyName || + e.PropertyName == VisualElement.IsVisibleProperty.PropertyName || e.PropertyName == VisualElement.IsEnabledProperty.PropertyName || + e.PropertyName == VisualElement.InputTransparentProperty.PropertyName || e.PropertyName == VisualElement.OpacityProperty.PropertyName) + UpdateNativeControl(); // poorly optimized + } + + void HandleRedrawNeeded(object sender, EventArgs e) + { + UpdateNativeControl(); + } + + void HandleSizeChanged(object sender, EventArgs e) + { + UpdateNativeControl(); + } + + void OnRendererElementChanged(object s, VisualElementChangedEventArgs e) + { + if (_element == e.NewElement) + return; + + SetElement(_element, e.NewElement); + } + + void OnUpdateNativeControl(CALayer caLayer) + { + var view = Renderer.Element; + var uiview = Renderer.NativeView; + + if (view == null || view.Batched) + return; + + var shouldInteract = !view.InputTransparent && view.IsEnabled; + if (_isInteractive != shouldInteract) + { + uiview.UserInteractionEnabled = shouldInteract; + _isInteractive = shouldInteract; + } + + var boundsChanged = _lastBounds != view.Bounds; + + var thread = !boundsChanged && !caLayer.Frame.IsEmpty; + + var anchorX = (float)view.AnchorX; + var anchorY = (float)view.AnchorY; + var translationX = (float)view.TranslationX; + var translationY = (float)view.TranslationY; + var rotationX = (float)view.RotationX; + var rotationY = (float)view.RotationY; + var rotation = (float)view.Rotation; + var scale = (float)view.Scale; + var width = (float)view.Width; + var height = (float)view.Height; + var x = (float)view.X; + var y = (float)view.Y; + var opacity = (float)view.Opacity; + var isVisible = view.IsVisible; + + var updateTarget = Interlocked.Increment(ref _updateCount); + + Action update = () => + { + if (updateTarget != _updateCount) + return; + + var visualElement = view; + var parent = view.RealParent; + + var shouldRelayoutSublayers = false; + if (isVisible && caLayer.Hidden) + { + caLayer.Hidden = false; + if (!caLayer.Frame.IsEmpty) + shouldRelayoutSublayers = true; + } + + if (!isVisible && !caLayer.Hidden) + { + caLayer.Hidden = true; + shouldRelayoutSublayers = true; + } + + // ripe for optimization + var transform = CATransform3D.Identity; + + // Dont ever attempt to actually change the layout of a Page unless it is a ContentPage + // iOS is a really big fan of you not actually modifying the View's of the UIViewControllers + if ((!(visualElement is Page) || visualElement is ContentPage) && width > 0 && height > 0 && parent != null && boundsChanged) + { + var target = new RectangleF(x, y, width, height); + // must reset transform prior to setting frame... + caLayer.Transform = transform; + caLayer.Frame = target; + if (shouldRelayoutSublayers) + caLayer.LayoutSublayers(); + } + else if (width <= 0 || height <= 0) + { + caLayer.Hidden = true; + return; + } + + caLayer.AnchorPoint = new PointF(anchorX, anchorY); + caLayer.Opacity = opacity; + const double epsilon = 0.001; + + // position is relative to anchor point + if (Math.Abs(anchorX - .5) > epsilon) + transform = transform.Translate((anchorX - .5f) * width, 0, 0); + if (Math.Abs(anchorY - .5) > epsilon) + transform = transform.Translate(0, (anchorY - .5f) * height, 0); + + if (Math.Abs(translationX) > epsilon || Math.Abs(translationY) > epsilon) + transform = transform.Translate(translationX, translationY, 0); + + if (Math.Abs(scale - 1) > epsilon) + transform = transform.Scale(scale); + + // not just an optimization, iOS will not "pixel align" a view which has m34 set + if (Math.Abs(rotationY % 180) > epsilon || Math.Abs(rotationX % 180) > epsilon) + transform.m34 = 1.0f / -400f; + + if (Math.Abs(rotationX % 360) > epsilon) + transform = transform.Rotate(rotationX * (float)Math.PI / 180.0f, 1.0f, 0.0f, 0.0f); + if (Math.Abs(rotationY % 360) > epsilon) + transform = transform.Rotate(rotationY * (float)Math.PI / 180.0f, 0.0f, 1.0f, 0.0f); + + transform = transform.Rotate(rotation * (float)Math.PI / 180.0f, 0.0f, 0.0f, 1.0f); + caLayer.Transform = transform; + }; + + if (thread) + CADisplayLinkTicker.Default.Invoke(update); + else + update(); + + _lastBounds = view.Bounds; + } + + void SetElement(VisualElement oldElement, VisualElement newElement) + { + if (oldElement != null) + { + oldElement.PropertyChanged -= _propertyChangedHandler; + oldElement.SizeChanged -= _sizeChangedEventHandler; + oldElement.BatchCommitted -= _batchCommittedHandler; + } + + _element = newElement; + + if (newElement != null) + { + newElement.BatchCommitted += _batchCommittedHandler; + newElement.SizeChanged += _sizeChangedEventHandler; + newElement.PropertyChanged += _propertyChangedHandler; + + UpdateNativeControl(); + } + } + + void UpdateNativeControl() + { + if (_disposed) + return; + + if (_layer == null) + { + _layer = Renderer.NativeView.Layer; + _isInteractive = Renderer.NativeView.UserInteractionEnabled; + } + + OnUpdateNativeControl(_layer); + + if (NativeControlUpdated != null) + NativeControlUpdated(this, EventArgs.Empty); + } + } +}
\ No newline at end of file |