Create the visual hierarchy

This commit is contained in:
Frank A. Krueger 2017-11-09 14:17:36 -08:00
parent b8740d1974
commit 27c58d3449
7 changed files with 380 additions and 10 deletions

149
Ooui.Forms/RendererPool.cs Normal file
View File

@ -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);
}
}
}
}

View File

@ -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;

View File

@ -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 ();
}
}
}

View File

@ -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);

View File

@ -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";
}

View File

@ -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) {

View File

@ -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,