diff --git a/Ooui.Forms/RendererPool.cs b/Ooui.Forms/RendererPool.cs new file mode 100644 index 0000000..b0d5958 --- /dev/null +++ b/Ooui.Forms/RendererPool.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms; + +namespace Ooui.Forms +{ + public sealed class RendererPool + { + readonly Dictionary> _freeRenderers = + new Dictionary> (); + + readonly VisualElement _oldElement; + + readonly IVisualElementRenderer _parent; + + public RendererPool (IVisualElementRenderer renderer, VisualElement oldElement) + { + if (renderer == null) + throw new ArgumentNullException (nameof (renderer)); + + if (oldElement == null) + throw new ArgumentNullException (nameof (oldElement)); + + _oldElement = oldElement; + _parent = renderer; + } + + public IVisualElementRenderer GetFreeRenderer (VisualElement view) + { + if (view == null) + throw new ArgumentNullException (nameof (view)); + + var rendererType = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerType (view.GetType()) ?? typeof (Renderers.ViewRenderer); + + Stack renderers; + if (!_freeRenderers.TryGetValue (rendererType, out renderers) || renderers.Count == 0) + return null; + + var renderer = renderers.Pop (); + renderer.SetElement (view); + return renderer; + } + + public void UpdateNewElement (VisualElement newElement) + { + if (newElement == null) + throw new ArgumentNullException ("newElement"); + + var sameChildrenTypes = true; + + var oldChildren = ((IElementController)_oldElement).LogicalChildren; + var newChildren = ((IElementController)newElement).LogicalChildren; + + if (oldChildren.Count == newChildren.Count) { + for (var i = 0; i < oldChildren.Count; i++) { + if (oldChildren[i].GetType () != newChildren[i].GetType ()) { + sameChildrenTypes = false; + break; + } + } + } + else + sameChildrenTypes = false; + + if (!sameChildrenTypes) { + ClearRenderers (_parent); + FillChildrenWithRenderers (newElement); + } + else + UpdateRenderers (newElement); + } + + void ClearRenderers (IVisualElementRenderer renderer) + { + if (renderer == null) + return; + + var subviews = renderer.NativeView.Children; + for (var i = 0; i < subviews.Count; i++) { + var childRenderer = subviews[i] as IVisualElementRenderer; + if (childRenderer != null) { + PushRenderer (childRenderer); + + // The ListView CalculateHeightForCell method can create renderers and dispose its child renderers before this is called. + // Thus, it is possible that this work is already completed. + if (childRenderer.Element != null && ReferenceEquals (childRenderer, Platform.GetRenderer (childRenderer.Element))) + childRenderer.Element.ClearValue (Platform.RendererProperty); + } + renderer.NativeView.RemoveChild (subviews[i]); + } + } + + void FillChildrenWithRenderers (VisualElement element) + { + foreach (var logicalChild in ((IElementController)element).LogicalChildren) { + var child = logicalChild as VisualElement; + if (child != null) { + //if (CompressedLayout.GetIsHeadless (child)) { + // child.IsPlatformEnabled = true; + // FillChildrenWithRenderers (child); + //} + //else { + var renderer = GetFreeRenderer (child) ?? Platform.CreateRenderer (child); + Platform.SetRenderer (child, renderer); + _parent.NativeView.AppendChild (renderer.NativeView); + //} + } + } + } + + void PushRenderer (IVisualElementRenderer renderer) + { + var reflectableType = renderer as System.Reflection.IReflectableType; + var rendererType = reflectableType != null ? reflectableType.GetTypeInfo ().AsType () : renderer.GetType (); + + Stack renderers; + if (!_freeRenderers.TryGetValue (rendererType, out renderers)) + _freeRenderers[rendererType] = renderers = new Stack (); + + renderers.Push (renderer); + } + + void UpdateRenderers (Xamarin.Forms.Element newElement) + { + var newElementController = (IElementController)newElement; + + if (newElementController.LogicalChildren.Count == 0) + return; + + var subviews = _parent.NativeView.Children; + for (var i = 0; i < subviews.Count; i++) { + var childRenderer = subviews[i] as IVisualElementRenderer; + if (childRenderer == null) + continue; + + var x = (int)childRenderer.NativeView.Style.ZIndex / 1000; + var element = newElementController.LogicalChildren[x] as VisualElement; + if (element == null) + continue; + + if (childRenderer.Element != null && ReferenceEquals (childRenderer, Platform.GetRenderer (childRenderer.Element))) + childRenderer.Element.ClearValue (Platform.RendererProperty); + + childRenderer.SetElement (element); + Platform.SetRenderer (element, childRenderer); + } + } + } +} diff --git a/Ooui.Forms/Renderers/ViewRenderer.cs b/Ooui.Forms/Renderers/ViewRenderer.cs index 1c2c7c6..04f85f7 100644 --- a/Ooui.Forms/Renderers/ViewRenderer.cs +++ b/Ooui.Forms/Renderers/ViewRenderer.cs @@ -5,6 +5,10 @@ using Xamarin.Forms; namespace Ooui.Forms.Renderers { + public abstract class ViewRenderer : ViewRenderer + { + } + public class ViewRenderer : VisualElementRenderer where TElement : View where TNativeElement : Ooui.Element { Color _defaultColor; diff --git a/Ooui.Forms/VisualElementPackager.cs b/Ooui.Forms/VisualElementPackager.cs new file mode 100644 index 0000000..b95c1b3 --- /dev/null +++ b/Ooui.Forms/VisualElementPackager.cs @@ -0,0 +1,182 @@ +using System; +using Xamarin.Forms; + +namespace Ooui.Forms +{ + public class VisualElementPackager : IDisposable + { + VisualElement _element; + + bool _isDisposed; + + IElementController ElementController => _element; + + public VisualElementPackager (IVisualElementRenderer renderer) : this (renderer, null) + { + } + + VisualElementPackager (IVisualElementRenderer renderer, VisualElement element) + { + if (renderer == null) + throw new ArgumentNullException (nameof (renderer)); + + Renderer = renderer; + renderer.ElementChanged += OnRendererElementChanged; + SetElement (null, element ?? renderer.Element); + } + + protected IVisualElementRenderer Renderer { get; set; } + + public void Dispose () + { + Dispose (true); + } + + public void Load () + { + for (var i = 0; i < ElementController.LogicalChildren.Count; i++) { + var child = ElementController.LogicalChildren[i] as VisualElement; + if (child != null) + OnChildAdded (child); + } + } + + protected virtual void Dispose (bool disposing) + { + if (_isDisposed) + return; + + if (disposing) { + SetElement (_element, null); + if (Renderer != null) { + Renderer.ElementChanged -= OnRendererElementChanged; + Renderer = null; + } + } + + _isDisposed = true; + } + + protected virtual void OnChildAdded (VisualElement view) + { + if (_isDisposed) + return; + + //if (CompressedLayout.GetIsHeadless (view)) { + // var packager = new VisualElementPackager (Renderer, view); + // view.IsPlatformEnabled = true; + // packager.Load (); + //} + //else { + var viewRenderer = Platform.CreateRenderer (view); + Platform.SetRenderer (view, viewRenderer); + + var uiview = Renderer.NativeView; + uiview.AppendChild (viewRenderer.NativeView); + + EnsureChildrenOrder (); + //} + } + + protected virtual void OnChildRemoved (VisualElement view) + { + if (_element == null) + return; + + var viewRenderer = Platform.GetRenderer (view); + if (viewRenderer == null || viewRenderer.NativeView == null) + return; + + var parentRenderer = Platform.GetRenderer (_element); + if (parentRenderer == null || parentRenderer.NativeView == null) + return; + + parentRenderer.NativeView.RemoveChild (viewRenderer.NativeView); + } + + void EnsureChildrenOrder () + { + if (ElementController.LogicalChildren.Count == 0) + return; + + for (var z = 0; z < ElementController.LogicalChildren.Count; z++) { + var child = ElementController.LogicalChildren[z] as VisualElement; + if (child == null) + continue; + var childRenderer = Platform.GetRenderer (child); + + if (childRenderer == null) + continue; + + var nativeControl = childRenderer.NativeView; +#if __MOBILE__ + Renderer.NativeView.BringSubviewToFront(nativeControl); +#endif + nativeControl.Style.ZIndex = z * 1000; + } + } + + void OnChildAdded (object sender, ElementEventArgs e) + { + var view = e.Element as VisualElement; + if (view != null) + OnChildAdded (view); + } + + void OnChildRemoved (object sender, ElementEventArgs e) + { + var view = e.Element as VisualElement; + if (view != null) + OnChildRemoved (view); + } + + void OnRendererElementChanged (object sender, VisualElementChangedEventArgs args) + { + if (args.NewElement == _element) + return; + + SetElement (_element, args.NewElement); + } + + void SetElement (VisualElement oldElement, VisualElement newElement) + { + if (oldElement == newElement) + return; + + if (oldElement != null) { + oldElement.ChildAdded -= OnChildAdded; + oldElement.ChildRemoved -= OnChildRemoved; + oldElement.ChildrenReordered -= UpdateChildrenOrder; + + if (newElement != null) { + var pool = new RendererPool (Renderer, oldElement); + pool.UpdateNewElement (newElement); + + EnsureChildrenOrder (); + } + else { + var elementController = ((IElementController)oldElement); + + for (var i = 0; i < elementController.LogicalChildren.Count; i++) { + var child = elementController.LogicalChildren[i] as VisualElement; + if (child != null) + OnChildRemoved (child); + } + } + } + + _element = newElement; + + if (newElement != null) { + newElement.ChildAdded += OnChildAdded; + newElement.ChildRemoved += OnChildRemoved; + newElement.ChildrenReordered += UpdateChildrenOrder; + } + } + + void UpdateChildrenOrder (object sender, EventArgs e) + { + EnsureChildrenOrder (); + } + } +} diff --git a/Ooui.Forms/VisualElementRenderer.cs b/Ooui.Forms/VisualElementRenderer.cs index 88b7428..484e5ac 100644 --- a/Ooui.Forms/VisualElementRenderer.cs +++ b/Ooui.Forms/VisualElementRenderer.cs @@ -6,10 +6,17 @@ using Xamarin.Forms; namespace Ooui.Forms { + [Flags] + public enum VisualElementRendererFlags + { + Disposed = 1 << 0, + AutoTrack = 1 << 1, + AutoPackage = 1 << 2 + } + public class VisualElementRenderer : Ooui.Element, IVisualElementRenderer where TElement : VisualElement { bool disposedValue = false; // To detect redundant calls - VisualElementTracker _tracker; readonly Color _defaultColor = Color.Clear; @@ -29,6 +36,31 @@ namespace Ooui.Forms readonly List> _elementChangedHandlers = new List> (); + VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack; + + VisualElementPackager _packager; + VisualElementTracker _tracker; + + protected bool AutoPackage { + get { return (_flags & VisualElementRendererFlags.AutoPackage) != 0; } + set { + if (value) + _flags |= VisualElementRendererFlags.AutoPackage; + else + _flags &= ~VisualElementRendererFlags.AutoPackage; + } + } + + protected bool AutoTrack { + get { return (_flags & VisualElementRendererFlags.AutoTrack) != 0; } + set { + if (value) + _flags |= VisualElementRendererFlags.AutoTrack; + else + _flags &= ~VisualElementRendererFlags.AutoTrack; + } + } + public VisualElementRenderer () : base ("div") { _propertyChangedHandler = OnElementPropertyChanged; @@ -70,10 +102,10 @@ namespace Ooui.Forms _tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget (); } - //if (AutoPackage && _packager == null) { - // _packager = new VisualElementPackager (this); - // _packager.Load (); - //} + if (AutoPackage && _packager == null) { + _packager = new VisualElementPackager (this); + _packager.Load (); + } //if (AutoTrack && _events == null) { // _events = new EventTracker (this); diff --git a/Ooui.Forms/VisualElementTracker.cs b/Ooui.Forms/VisualElementTracker.cs index 504cf0d..18a5cc9 100644 --- a/Ooui.Forms/VisualElementTracker.cs +++ b/Ooui.Forms/VisualElementTracker.cs @@ -140,13 +140,9 @@ namespace Ooui.Forms parentBoundsChanged = true; bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); if (shouldUpdate) { - var visualParent = parent as VisualElement; - float newY = visualParent == null ? y : Math.Max (0, (float)(visualParent.Height - y - view.Height)); - var target = new Rectangle (x, newY, width, height); - uiview.Style.Position = "absolute"; uiview.Style.Left = x + "px"; - uiview.Style.Top = newY + "px"; + uiview.Style.Top = y + "px"; uiview.Style.Width = width + "px"; uiview.Style.Height = height + "px"; } diff --git a/Ooui/Style.cs b/Ooui/Style.cs index f19abf7..1f97952 100644 --- a/Ooui/Style.cs +++ b/Ooui/Style.cs @@ -312,6 +312,11 @@ namespace Ooui set => this["width"] = value; } + public Value ZIndex { + get => this["z-index"]; + set => this["z-index"] = value; + } + public Value this[string propertyName] { get { lock (properties) { diff --git a/Samples/XamarinFormsSample.cs b/Samples/XamarinFormsSample.cs index 644a7ed..0e644ed 100644 --- a/Samples/XamarinFormsSample.cs +++ b/Samples/XamarinFormsSample.cs @@ -12,6 +12,7 @@ namespace Samples var countLabel = new Label { Text = "0", + BackgroundColor = Color.Gold, }; var countButton = new Button { }; @@ -21,6 +22,7 @@ namespace Samples }; var page = new ContentPage { Content = new StackLayout { + BackgroundColor = Color.Khaki, Children = { new Label { Text = "Hello World!" }, countLabel,