Create the visual hierarchy
This commit is contained in:
parent
b8740d1974
commit
27c58d3449
|
@ -0,0 +1,149 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Ooui.Forms
|
||||||
|
{
|
||||||
|
public sealed class RendererPool
|
||||||
|
{
|
||||||
|
readonly Dictionary<Type, Stack<IVisualElementRenderer>> _freeRenderers =
|
||||||
|
new Dictionary<Type, Stack<IVisualElementRenderer>> ();
|
||||||
|
|
||||||
|
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<IVisualElementRenderer> 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<IVisualElementRenderer> renderers;
|
||||||
|
if (!_freeRenderers.TryGetValue (rendererType, out renderers))
|
||||||
|
_freeRenderers[rendererType] = renderers = new Stack<IVisualElementRenderer> ();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,10 @@ using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Ooui.Forms.Renderers
|
namespace Ooui.Forms.Renderers
|
||||||
{
|
{
|
||||||
|
public abstract class ViewRenderer : ViewRenderer<View, Ooui.Element>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public class ViewRenderer<TElement, TNativeElement> : VisualElementRenderer<TElement> where TElement : View where TNativeElement : Ooui.Element
|
public class ViewRenderer<TElement, TNativeElement> : VisualElementRenderer<TElement> where TElement : View where TNativeElement : Ooui.Element
|
||||||
{
|
{
|
||||||
Color _defaultColor;
|
Color _defaultColor;
|
||||||
|
|
|
@ -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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,17 @@ using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Ooui.Forms
|
namespace Ooui.Forms
|
||||||
{
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum VisualElementRendererFlags
|
||||||
|
{
|
||||||
|
Disposed = 1 << 0,
|
||||||
|
AutoTrack = 1 << 1,
|
||||||
|
AutoPackage = 1 << 2
|
||||||
|
}
|
||||||
|
|
||||||
public class VisualElementRenderer<TElement> : Ooui.Element, IVisualElementRenderer where TElement : VisualElement
|
public class VisualElementRenderer<TElement> : Ooui.Element, IVisualElementRenderer where TElement : VisualElement
|
||||||
{
|
{
|
||||||
bool disposedValue = false; // To detect redundant calls
|
bool disposedValue = false; // To detect redundant calls
|
||||||
VisualElementTracker _tracker;
|
|
||||||
|
|
||||||
readonly Color _defaultColor = Color.Clear;
|
readonly Color _defaultColor = Color.Clear;
|
||||||
|
|
||||||
|
@ -29,6 +36,31 @@ namespace Ooui.Forms
|
||||||
readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers =
|
readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers =
|
||||||
new List<EventHandler<VisualElementChangedEventArgs>> ();
|
new List<EventHandler<VisualElementChangedEventArgs>> ();
|
||||||
|
|
||||||
|
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")
|
public VisualElementRenderer () : base ("div")
|
||||||
{
|
{
|
||||||
_propertyChangedHandler = OnElementPropertyChanged;
|
_propertyChangedHandler = OnElementPropertyChanged;
|
||||||
|
@ -70,10 +102,10 @@ namespace Ooui.Forms
|
||||||
_tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget ();
|
_tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget ();
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (AutoPackage && _packager == null) {
|
if (AutoPackage && _packager == null) {
|
||||||
// _packager = new VisualElementPackager (this);
|
_packager = new VisualElementPackager (this);
|
||||||
// _packager.Load ();
|
_packager.Load ();
|
||||||
//}
|
}
|
||||||
|
|
||||||
//if (AutoTrack && _events == null) {
|
//if (AutoTrack && _events == null) {
|
||||||
// _events = new EventTracker (this);
|
// _events = new EventTracker (this);
|
||||||
|
|
|
@ -140,13 +140,9 @@ namespace Ooui.Forms
|
||||||
parentBoundsChanged = true;
|
parentBoundsChanged = true;
|
||||||
bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged);
|
bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged);
|
||||||
if (shouldUpdate) {
|
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.Position = "absolute";
|
||||||
uiview.Style.Left = x + "px";
|
uiview.Style.Left = x + "px";
|
||||||
uiview.Style.Top = newY + "px";
|
uiview.Style.Top = y + "px";
|
||||||
uiview.Style.Width = width + "px";
|
uiview.Style.Width = width + "px";
|
||||||
uiview.Style.Height = height + "px";
|
uiview.Style.Height = height + "px";
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,6 +312,11 @@ namespace Ooui
|
||||||
set => this["width"] = value;
|
set => this["width"] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Value ZIndex {
|
||||||
|
get => this["z-index"];
|
||||||
|
set => this["z-index"] = value;
|
||||||
|
}
|
||||||
|
|
||||||
public Value this[string propertyName] {
|
public Value this[string propertyName] {
|
||||||
get {
|
get {
|
||||||
lock (properties) {
|
lock (properties) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Samples
|
||||||
|
|
||||||
var countLabel = new Label {
|
var countLabel = new Label {
|
||||||
Text = "0",
|
Text = "0",
|
||||||
|
BackgroundColor = Color.Gold,
|
||||||
};
|
};
|
||||||
var countButton = new Button {
|
var countButton = new Button {
|
||||||
};
|
};
|
||||||
|
@ -21,6 +22,7 @@ namespace Samples
|
||||||
};
|
};
|
||||||
var page = new ContentPage {
|
var page = new ContentPage {
|
||||||
Content = new StackLayout {
|
Content = new StackLayout {
|
||||||
|
BackgroundColor = Color.Khaki,
|
||||||
Children = {
|
Children = {
|
||||||
new Label { Text = "Hello World!" },
|
new Label { Text = "Hello World!" },
|
||||||
countLabel,
|
countLabel,
|
||||||
|
|
Loading…
Reference in New Issue