From dda73249dfe81dfabf85603c0da736f3a85ba031 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Fri, 16 Jul 2021 14:07:31 -0600 Subject: [PATCH] IContainer proposal --- .../src/Core/HandlerImpl/Layout.Impl.cs | 76 ++++++++++++++++ src/Controls/src/Core/Layout.cs | 26 +----- src/Controls/src/Core/Layout/GridLayout.cs | 4 +- src/Controls/src/Core/Layout/Layout.cs | 91 ++++++++++++++----- src/Core/src/Core/IContainer.cs | 7 +- src/Core/src/Core/ILayout.cs | 13 --- .../Handlers/Layout/LayoutHandler.Android.cs | 2 +- .../src/Handlers/Layout/LayoutHandler.iOS.cs | 2 +- src/Core/src/HotReload/HotReloadExtensions.cs | 2 +- src/Core/src/Layouts/FlexLayoutManager.cs | 2 +- src/Core/src/Layouts/GridLayoutManager.cs | 6 +- .../Layouts/HorizontalStackLayoutManager.cs | 12 +-- .../src/Layouts/VerticalStackLayoutManager.cs | 6 +- 13 files changed, 169 insertions(+), 80 deletions(-) create mode 100644 src/Controls/src/Core/HandlerImpl/Layout.Impl.cs diff --git a/src/Controls/src/Core/HandlerImpl/Layout.Impl.cs b/src/Controls/src/Core/HandlerImpl/Layout.Impl.cs new file mode 100644 index 000000000000..b4dcaa99460f --- /dev/null +++ b/src/Controls/src/Core/HandlerImpl/Layout.Impl.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Maui.Controls +{ + public abstract partial class Layout + { + int ICollection.Count => _children.Count; + bool ICollection.IsReadOnly => ((ICollection) _children).IsReadOnly; + public IView this[int index] { get => _children[index]; set => _children[index] = (T)value; } + + public void Add(IView child) + { + if (child is T view) + { + _children.Add(view); + } + } + + public bool Remove(IView child) + { + if (child is T view) + { + _children.Remove(view); + return true; + } + + return false; + } + + int IList.IndexOf(IView child) + { + return _children.IndexOf(child); + } + + void IList.Insert(int index, IView child) + { + if (child is T view) + { + _children.Insert(index, view); + } + } + + void IList.RemoveAt(int index) + { + _children.RemoveAt(index); + } + + void ICollection.Clear() + { + _children.Clear(); + } + + bool ICollection.Contains(IView child) + { + return _children.Contains(child); + } + + void ICollection.CopyTo(IView[] array, int arrayIndex) + { + _children.CopyTo(array, arrayIndex); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _children.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _children.GetEnumerator(); + } + } +} diff --git a/src/Controls/src/Core/Layout.cs b/src/Controls/src/Core/Layout.cs index aaff46af7881..2dc719b0de78 100644 --- a/src/Controls/src/Core/Layout.cs +++ b/src/Controls/src/Core/Layout.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -10,11 +11,8 @@ namespace Microsoft.Maui.Controls { [ContentProperty(nameof(Children))] - public abstract class Layout : Layout, Microsoft.Maui.ILayout, IViewContainer where T : View + public abstract partial class Layout : Layout, Microsoft.Maui.ILayout, IViewContainer where T : View { - // TODO ezhart We should look for a way to optimize this a bit - IReadOnlyList Microsoft.Maui.IContainer.Children => Children.ToList(); - readonly ElementCollection _children; protected Layout() => _children = new ElementCollection(InternalChildren); @@ -46,28 +44,10 @@ protected virtual void OnAdded(T view) protected virtual void OnRemoved(T view) { } - - public void Add(IView child) - { - if (child is T view) - { - Children.Add(view); - } - } - - public void Remove(IView child) - { - if (child is T view) - { - Children.Remove(view); - } - } } - public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IFrameworkElement, Microsoft.Maui.IContainer + public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IFrameworkElement//, Microsoft.Maui.IContainer { - IReadOnlyList Microsoft.Maui.IContainer.Children => InternalChildren.OfType().ToList(); - public static readonly BindableProperty IsClippedToBoundsProperty = BindableProperty.Create(nameof(IsClippedToBounds), typeof(bool), typeof(Layout), false); diff --git a/src/Controls/src/Core/Layout/GridLayout.cs b/src/Controls/src/Core/Layout/GridLayout.cs index 8c6a0cd3df54..f1180ae58225 100644 --- a/src/Controls/src/Core/Layout/GridLayout.cs +++ b/src/Controls/src/Core/Layout/GridLayout.cs @@ -231,10 +231,10 @@ public override void Add(IView child) } } - public override void Remove(IView child) + public override bool Remove(IView child) { _viewInfo.Remove(child); - base.Remove(child); + return base.Remove(child); } protected override ILayoutManager CreateLayoutManager() => new GridLayoutManager(this); diff --git a/src/Controls/src/Core/Layout/Layout.cs b/src/Controls/src/Core/Layout/Layout.cs index d6e2ff1d4ae4..cad47661ea3a 100644 --- a/src/Controls/src/Core/Layout/Layout.cs +++ b/src/Controls/src/Core/Layout/Layout.cs @@ -8,23 +8,29 @@ namespace Microsoft.Maui.Controls.Layout2 { [ContentProperty(nameof(Children))] - public abstract class Layout : View, Microsoft.Maui.ILayout, IEnumerable + public abstract class Layout : View, Maui.ILayout, IList { ILayoutManager _layoutManager; ILayoutManager LayoutManager => _layoutManager ??= CreateLayoutManager(); - readonly List _children = new List(); + // The actual backing store for the IViews in the ILayout + readonly List _children = new(); - public IReadOnlyList Children { get => _children.AsReadOnly(); } + // This provides a Children property for XAML + public IList Children => this; public ILayoutHandler LayoutHandler => Handler as ILayoutHandler; + public int Count => _children.Count; + + public bool IsReadOnly => ((ICollection)_children).IsReadOnly; + + public IView this[int index] { get => _children[index]; set => _children[index] = value; } + protected abstract ILayoutManager CreateLayoutManager(); public IEnumerator GetEnumerator() => _children.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _children.GetEnumerator(); - #pragma warning disable CS0672 // Member overrides obsolete member public override SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint) #pragma warning restore CS0672 // Member overrides obsolete member @@ -32,54 +38,76 @@ public override SizeRequest GetSizeRequest(double widthConstraint, double height var size = (this as IFrameworkElement).Measure(widthConstraint, heightConstraint); return new SizeRequest(size); } - protected override Size MeasureOverride(double widthConstraint, double heightConstraint) { var margin = (this as IView)?.Margin ?? Thickness.Zero; - // Adjust the constraints to account for the margins widthConstraint -= margin.HorizontalThickness; heightConstraint -= margin.VerticalThickness; - var sizeWithoutMargins = LayoutManager.Measure(widthConstraint, heightConstraint); DesiredSize = new Size(sizeWithoutMargins.Width + Margin.HorizontalThickness, sizeWithoutMargins.Height + Margin.VerticalThickness); - return DesiredSize; } - protected override Size ArrangeOverride(Rectangle bounds) { base.ArrangeOverride(bounds); - Frame = bounds; - LayoutManager.ArrangeChildren(Frame); - foreach (var child in Children) { child.Handler?.NativeArrange(child.Frame); } - return Frame.Size; } - protected override void InvalidateMeasureOverride() { base.InvalidateMeasureOverride(); - foreach (var child in Children) { child.InvalidateMeasure(); } } - public virtual void Add(IView child) { if (child == null) return; - _children.Add(child); + if (child is Element element) + element.Parent = this; + InvalidateMeasure(); + LayoutHandler?.Add(child); + } + + public void Clear() + { + for (int n = _children.Count - 1; n >= 0; n--) + { + Remove(this[n]); + } + } + + public bool Contains(IView item) + { + return _children.Contains(item); + } + + public void CopyTo(IView[] array, int arrayIndex) + { + _children.CopyTo(array, arrayIndex); + } + + public int IndexOf(IView item) + { + return _children.IndexOf(item); + } + + public void Insert(int index, IView child) + { + if (child == null) + return; + + _children.Insert(index, child); if (child is Element element) element.Parent = this; @@ -89,12 +117,33 @@ public virtual void Add(IView child) LayoutHandler?.Add(child); } - public virtual void Remove(IView child) + public virtual bool Remove(IView child) { if (child == null) + return false; + + var result = _children.Remove(child); + + if (child is Element element) + element.Parent = null; + + InvalidateMeasure(); + + LayoutHandler?.Remove(child); + + return result; + } + + public void RemoveAt(int index) + { + if (index >= Count) + { return; + } + + var child = _children[index]; - _children.Remove(child); + _children.RemoveAt(index); if (child is Element element) element.Parent = null; @@ -104,4 +153,4 @@ public virtual void Remove(IView child) LayoutHandler?.Remove(child); } } -} +} \ No newline at end of file diff --git a/src/Core/src/Core/IContainer.cs b/src/Core/src/Core/IContainer.cs index 78464bdc331a..3440045d4af8 100644 --- a/src/Core/src/Core/IContainer.cs +++ b/src/Core/src/Core/IContainer.cs @@ -5,11 +5,8 @@ namespace Microsoft.Maui /// /// Provides functionality to act as containers for views. /// - public interface IContainer + public interface IContainer : IList { - /// - /// Gets the collection of children that the Container contains. - /// - IReadOnlyList Children { get; } + } } diff --git a/src/Core/src/Core/ILayout.cs b/src/Core/src/Core/ILayout.cs index 3a725e845b50..bab1cc3e158e 100644 --- a/src/Core/src/Core/ILayout.cs +++ b/src/Core/src/Core/ILayout.cs @@ -8,22 +8,9 @@ namespace Microsoft.Maui /// public interface ILayout : IView, IContainer { - /// /// Gets the Layout Handler. /// ILayoutHandler LayoutHandler { get; } - - /// - /// Add a child View to the Layout. - /// - /// The child View to add to the Layout. - void Add(IView child); - - /// - /// Remove a child View from the Layout. - /// - /// The child View to remove from the Layout. - void Remove(IView child); } } \ No newline at end of file diff --git a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs index d215c7047c3a..c4617a65a5ef 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs @@ -33,7 +33,7 @@ public override void SetVirtualView(IView view) NativeView.CrossPlatformArrange = VirtualView.Arrange; NativeView.RemoveAllViews(); - foreach (var child in VirtualView.Children) + foreach (var child in VirtualView) { NativeView.AddView(child.ToNative(MauiContext)); } diff --git a/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs b/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs index cc1f90a6e085..6742952171d2 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs @@ -38,7 +38,7 @@ public override void SetVirtualView(IView view) var oldChildren = NativeView.Subviews.ToList(); oldChildren.ForEach(x => x.RemoveFromSuperview()); - foreach (var child in VirtualView.Children) + foreach (var child in VirtualView) { NativeView.AddSubview(child.ToNative(MauiContext)); } diff --git a/src/Core/src/HotReload/HotReloadExtensions.cs b/src/Core/src/HotReload/HotReloadExtensions.cs index 2a3bd420df49..ba0b9f290bf4 100644 --- a/src/Core/src/HotReload/HotReloadExtensions.cs +++ b/src/Core/src/HotReload/HotReloadExtensions.cs @@ -29,7 +29,7 @@ public static void CheckHandlers(this IView view) if (view is IContainer layout) { - foreach (var v in layout.Children) + foreach (var v in layout) CheckHandlers(v); } } diff --git a/src/Core/src/Layouts/FlexLayoutManager.cs b/src/Core/src/Layouts/FlexLayoutManager.cs index 8c87aef01384..25402d97fc2c 100644 --- a/src/Core/src/Layouts/FlexLayoutManager.cs +++ b/src/Core/src/Layouts/FlexLayoutManager.cs @@ -17,7 +17,7 @@ public void ArrangeChildren(Rectangle childBounds) { FlexLayout.Layout(childBounds.Width, childBounds.Height); - foreach (var child in FlexLayout.Children) + foreach (var child in FlexLayout) { var frame = FlexLayout.GetFlexFrame(child); if (double.IsNaN(frame.X) diff --git a/src/Core/src/Layouts/GridLayoutManager.cs b/src/Core/src/Layouts/GridLayoutManager.cs index 0deae16d4fd0..0b6a3a515b21 100644 --- a/src/Core/src/Layouts/GridLayoutManager.cs +++ b/src/Core/src/Layouts/GridLayoutManager.cs @@ -27,7 +27,7 @@ public override void ArrangeChildren(Rectangle childBounds) { var structure = _gridStructure ?? new GridStructure(Grid, childBounds.Width, childBounds.Height); - foreach (var view in Grid.Children) + foreach (var view in Grid) { if (view.Visibility == Visibility.Collapsed) { @@ -92,7 +92,7 @@ public GridStructure(IGridLayout grid, double widthConstraint, double heightCons } } - _children = _grid.Children.Where(child => child.Visibility != Visibility.Collapsed).ToArray(); + _children = _grid.Where(child => child.Visibility != Visibility.Collapsed).ToArray(); // We'll ignore any collapsed child views during layout _cells = new Cell[_children.Length]; @@ -393,7 +393,7 @@ void ResolveStars(Definition[] defs, double availableSpace, Func cel if (cellCheck(cell)) // Check whether this cell should count toward the type of star value were measuring { // Update the star width if the view in this cell is bigger - starSize = Math.Max(starSize, dimension(_grid.Children[cell.ViewIndex].DesiredSize)); + starSize = Math.Max(starSize, dimension(_grid[cell.ViewIndex].DesiredSize)); } } } diff --git a/src/Core/src/Layouts/HorizontalStackLayoutManager.cs b/src/Core/src/Layouts/HorizontalStackLayoutManager.cs index 1cafca6352b9..7f925cc403d7 100644 --- a/src/Core/src/Layouts/HorizontalStackLayoutManager.cs +++ b/src/Core/src/Layouts/HorizontalStackLayoutManager.cs @@ -12,7 +12,7 @@ public HorizontalStackLayoutManager(IStackLayout layout) : base(layout) public override Size Measure(double widthConstraint, double heightConstraint) { - var measure = Measure(heightConstraint, Stack.Spacing, Stack.Children); + var measure = Measure(heightConstraint, Stack.Spacing, Stack); var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, measure.Width); @@ -23,17 +23,17 @@ public override void ArrangeChildren(Rectangle bounds) { if (Stack.FlowDirection == FlowDirection.LeftToRight) { - ArrangeLeftToRight(bounds.Height, Stack.Spacing, Stack.Children); + ArrangeLeftToRight(bounds.Height, Stack.Spacing, Stack); } else { // We _could_ simply reverse the list of child views when arranging from right to left, // but this way we avoid extra list and enumerator allocations - ArrangeRightToLeft(bounds.Height, Stack.Spacing, Stack.Children); + ArrangeRightToLeft(bounds.Height, Stack.Spacing, Stack); } } - static Size Measure(double heightConstraint, int spacing, IReadOnlyList views) + static Size Measure(double heightConstraint, int spacing, IList views) { double totalRequestedWidth = 0; double requestedHeight = 0; @@ -58,7 +58,7 @@ static Size Measure(double heightConstraint, int spacing, IReadOnlyList v return new Size(totalRequestedWidth, requestedHeight); } - static void ArrangeLeftToRight(double height, int spacing, IReadOnlyList views) + static void ArrangeLeftToRight(double height, int spacing, IList views) { double xPosition = 0; @@ -75,7 +75,7 @@ static void ArrangeLeftToRight(double height, int spacing, IReadOnlyList } } - static void ArrangeRightToLeft(double height, int spacing, IReadOnlyList views) + static void ArrangeRightToLeft(double height, int spacing, IList views) { double xPostition = 0; diff --git a/src/Core/src/Layouts/VerticalStackLayoutManager.cs b/src/Core/src/Layouts/VerticalStackLayoutManager.cs index ec6a78412919..35db2a0022fd 100644 --- a/src/Core/src/Layouts/VerticalStackLayoutManager.cs +++ b/src/Core/src/Layouts/VerticalStackLayoutManager.cs @@ -12,16 +12,16 @@ public VerticalStackLayoutManager(IStackLayout stackLayout) : base(stackLayout) public override Size Measure(double widthConstraint, double heightConstraint) { - var measure = Measure(widthConstraint, Stack.Spacing, Stack.Children); + var measure = Measure(widthConstraint, Stack.Spacing, Stack); var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, measure.Height); return new Size(measure.Width, finalHeight); } - public override void ArrangeChildren(Rectangle bounds) => Arrange(bounds.Width, Stack.Spacing, Stack.Children); + public override void ArrangeChildren(Rectangle bounds) => Arrange(bounds.Width, Stack.Spacing, Stack); - static Size Measure(double widthConstraint, int spacing, IReadOnlyList views) + static Size Measure(double widthConstraint, int spacing, IList views) { double totalRequestedHeight = 0; double requestedWidth = 0;