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
|
||||
{
|
||||
public abstract class ViewRenderer : ViewRenderer<View, Ooui.Element>
|
||||
{
|
||||
}
|
||||
|
||||
public class ViewRenderer<TElement, TNativeElement> : VisualElementRenderer<TElement> where TElement : View where TNativeElement : Ooui.Element
|
||||
{
|
||||
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
|
||||
{
|
||||
[Flags]
|
||||
public enum VisualElementRendererFlags
|
||||
{
|
||||
Disposed = 1 << 0,
|
||||
AutoTrack = 1 << 1,
|
||||
AutoPackage = 1 << 2
|
||||
}
|
||||
|
||||
public class VisualElementRenderer<TElement> : 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<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers =
|
||||
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")
|
||||
{
|
||||
_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);
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue