From 1c0e981343281705ffee4c692bf5036befb55f01 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 24 May 2020 21:53:44 -0300 Subject: [PATCH 01/44] import sources. --- .../RelativePanel.AttachedProperties.cs | 511 ++++++++++++++++++ src/Avalonia.Controls/RelativePanel.cs | 424 +++++++++++++++ 2 files changed, 935 insertions(+) create mode 100644 src/Avalonia.Controls/RelativePanel.AttachedProperties.cs create mode 100644 src/Avalonia.Controls/RelativePanel.cs diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs new file mode 100644 index 0000000000..21baff87ab --- /dev/null +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -0,0 +1,511 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Controls +{ + public partial class RelativePanel + { + private static void OnAlignPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var elm = d as FrameworkElement; + if (elm.Parent is FrameworkElement) + ((FrameworkElement)elm.Parent).InvalidateArrange(); + } + + /// + /// Gets the value of the RelativePanel.Above XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.Above XAML attached property value of the specified object. + /// (The element to position this element above.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAbove(DependencyObject obj) + { + return (object)obj.GetValue(AboveProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to position this element above.) + public static void SetAbove(DependencyObject obj, object value) + { + obj.SetValue(AboveProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AboveProperty = + DependencyProperty.RegisterAttached("Above", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + + /// + /// Gets the value of the RelativePanel.AlignBottomWithPanel XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignBottomWithPanel XAML attached property value of the specified + /// object. (true to align this element's bottom edge with the panel's bottom edge; + /// otherwise, false.) + /// + public static bool GetAlignBottomWithPanel(DependencyObject obj) + { + return (bool)obj.GetValue(AlignBottomWithPanelProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// + /// The value to set. (true to align this element's bottom edge with the panel's + /// bottom edge; otherwise, false.) + /// + public static void SetAlignBottomWithPanel(DependencyObject obj, bool value) + { + obj.SetValue(AlignBottomWithPanelProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignBottomWithPanelProperty = + DependencyProperty.RegisterAttached("AlignBottomWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignBottomWith XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignBottomWith XAML attached property value of the specified object. + /// (The element to align this element's bottom edge with.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAlignBottomWith(DependencyObject obj) + { + return (object)obj.GetValue(AlignBottomWithProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to align this element's bottom edge with.) + public static void SetAlignBottomWith(DependencyObject obj, object value) + { + obj.SetValue(AlignBottomWithProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignBottomWithProperty = + DependencyProperty.RegisterAttached("AlignBottomWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignHorizontalCenterWithPanel XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignHorizontalCenterWithPanel XAML attached property value + /// of the specified object. (true to horizontally center this element in the panel; + /// otherwise, false.) + /// + public static bool GetAlignHorizontalCenterWithPanel(DependencyObject obj) + { + return (bool)obj.GetValue(AlignHorizontalCenterWithPanelProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// + /// The value to set. (true to horizontally center this element in the panel; otherwise, + /// false.) + /// + public static void SetAlignHorizontalCenterWithPanel(DependencyObject obj, bool value) + { + obj.SetValue(AlignHorizontalCenterWithPanelProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignHorizontalCenterWithPanelProperty = + DependencyProperty.RegisterAttached("AlignHorizontalCenterWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignHorizontalCenterWith XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignHorizontalCenterWith XAML attached property value of the + /// specified object. (The element to align this element's horizontal center with.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAlignHorizontalCenterWith(DependencyObject obj) + { + return (object)obj.GetValue(AlignHorizontalCenterWithProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to align this element's horizontal center with.) + public static void SetAlignHorizontalCenterWith(DependencyObject obj, object value) + { + obj.SetValue(AlignHorizontalCenterWithProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignHorizontalCenterWithProperty = + DependencyProperty.RegisterAttached("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignLeftWithPanel XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignLeftWithPanel XAML attached property value of the specified + /// object. (true to align this element's left edge with the panel's left edge; otherwise, + /// false.) + /// + public static bool GetAlignLeftWithPanel(DependencyObject obj) + { + return (bool)obj.GetValue(AlignLeftWithPanelProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// + /// The value to set. (true to align this element's left edge with the panel's left + /// edge; otherwise, false.) + /// + public static void SetAlignLeftWithPanel(DependencyObject obj, bool value) + { + obj.SetValue(AlignLeftWithPanelProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignLeftWithPanelProperty = + DependencyProperty.RegisterAttached("AlignLeftWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + + + /// + /// Gets the value of the RelativePanel.AlignLeftWith XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignLeftWith XAML attached property value of the specified + /// object. (The element to align this element's left edge with.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAlignLeftWith(DependencyObject obj) + { + return (object)obj.GetValue(AlignLeftWithProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to align this element's left edge with.) + public static void SetAlignLeftWith(DependencyObject obj, object value) + { + obj.SetValue(AlignLeftWithProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignLeftWithProperty = + DependencyProperty.RegisterAttached("AlignLeftWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + + /// + /// Gets the value of the RelativePanel.AlignRightWithPanel XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignRightWithPanel XAML attached property value of the specified + /// object. (true to align this element's right edge with the panel's right edge; + /// otherwise, false.) + /// + public static bool GetAlignRightWithPanel(DependencyObject obj) + { + return (bool)obj.GetValue(AlignRightWithPanelProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// + /// The value to set. (true to align this element's right edge with the panel's right + /// edge; otherwise, false.) + /// + public static void SetAlignRightWithPanel(DependencyObject obj, bool value) + { + obj.SetValue(AlignRightWithPanelProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignRightWithPanelProperty = + DependencyProperty.RegisterAttached("AlignRightWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignRightWith XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignRightWith XAML attached property value of the specified + /// object. (The element to align this element's right edge with.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAlignRightWith(DependencyObject obj) + { + return (object)obj.GetValue(AlignRightWithProperty); + } + + /// + /// Sets the value of the RelativePanel.AlignRightWith XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to align this element's right edge with.) + public static void SetAlignRightWith(DependencyObject obj, object value) + { + obj.SetValue(AlignRightWithProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignRightWithProperty = + DependencyProperty.RegisterAttached("AlignRightWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignTopWithPanel XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignTopWithPanel XAML attached property value of the specified + /// object. (true to align this element's top edge with the panel's top edge; otherwise, + /// false.) + /// + public static bool GetAlignTopWithPanel(DependencyObject obj) + { + return (bool)obj.GetValue(AlignTopWithPanelProperty); + } + + /// + /// Sets the value of the RelativePanel.AlignTopWithPanel XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// + /// The value to set. (true to align this element's top edge with the panel's top + /// edge; otherwise, false.) + /// + public static void SetAlignTopWithPanel(DependencyObject obj, bool value) + { + obj.SetValue(AlignTopWithPanelProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignTopWithPanelProperty = + DependencyProperty.RegisterAttached("AlignTopWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignTopWith XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// The value to set. (The element to align this element's top edge with.) + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAlignTopWith(DependencyObject obj) + { + return (object)obj.GetValue(AlignTopWithProperty); + } + + /// + /// Sets the value of the RelativePanel.AlignTopWith XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to align this element's top edge with.) + public static void SetAlignTopWith(DependencyObject obj, object value) + { + obj.SetValue(AlignTopWithProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignTopWithProperty = + DependencyProperty.RegisterAttached("AlignTopWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.AlignVerticalCenterWithPanel XAML attached property value of + /// the specified object. (true to vertically center this element in the panel; otherwise, + /// false.) + /// + public static bool GetAlignVerticalCenterWithPanel(DependencyObject obj) + { + return (bool)obj.GetValue(AlignVerticalCenterWithPanelProperty); + } + + /// + /// Sets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// + /// The value to set. (true to vertically center this element in the panel; otherwise, + /// false.) + /// + public static void SetAlignVerticalCenterWithPanel(DependencyObject obj, bool value) + { + obj.SetValue(AlignVerticalCenterWithPanelProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignVerticalCenterWithPanelProperty = + DependencyProperty.RegisterAttached("AlignVerticalCenterWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// The value to set. (The element to align this element's vertical center with.) + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetAlignVerticalCenterWith(DependencyObject obj) + { + return (object)obj.GetValue(AlignVerticalCenterWithProperty); + } + + /// + /// Sets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to align this element's horizontal center with.) + public static void SetAlignVerticalCenterWith(DependencyObject obj, object value) + { + obj.SetValue(AlignVerticalCenterWithProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty AlignVerticalCenterWithProperty = + DependencyProperty.RegisterAttached("AlignVerticalCenterWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.Below XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.Below XAML attached property value of the specified object. + /// (The element to position this element below.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetBelow(DependencyObject obj) + { + return (object)obj.GetValue(BelowProperty); + } + + /// + /// Sets the value of the RelativePanel.Above XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to position this element below.) + public static void SetBelow(DependencyObject obj, object value) + { + obj.SetValue(BelowProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty BelowProperty = + DependencyProperty.RegisterAttached("Below", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.LeftOf XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.LeftOf XAML attached property value of the specified object. + /// (The element to position this element to the left of.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetLeftOf(DependencyObject obj) + { + return (object)obj.GetValue(LeftOfProperty); + } + + /// + /// Sets the value of the RelativePanel.LeftOf XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to position this element to the left of.) + public static void SetLeftOf(DependencyObject obj, object value) + { + obj.SetValue(LeftOfProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty LeftOfProperty = + DependencyProperty.RegisterAttached("LeftOf", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + + /// + /// Gets the value of the RelativePanel.RightOf XAML attached property for the target element. + /// + /// The object from which the property value is read. + /// + /// The RelativePanel.RightOf XAML attached property value of the specified object. + /// (The element to position this element to the right of.) + /// + [TypeConverter(typeof(NameReferenceConverter))] + public static object GetRightOf(DependencyObject obj) + { + return (object)obj.GetValue(RightOfProperty); + } + + /// + /// Sets the value of the RelativePanel.RightOf XAML attached property for a target element. + /// + /// The object to which the property value is written. + /// The value to set. (The element to position this element to the right of.) + public static void SetRightOf(DependencyObject obj, object value) + { + obj.SetValue(RightOfProperty, value); + } + + /// + /// Identifies the XAML attached property. + /// + public static readonly DependencyProperty RightOfProperty = + DependencyProperty.RegisterAttached("RightOf", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + } +} diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs new file mode 100644 index 0000000000..aecd4b7c98 --- /dev/null +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Controls +{ + /// + /// Defines an area within which you can position and align child objects in relation + /// to each other or the parent panel. + /// + /// + /// Default position + /// By default, any unconstrained element declared as a child of the RelativePanel is given the entire + /// available space and positioned at the(0, 0) coordinates(upper left corner) of the panel.So, if you + /// are positioning a second element relative to an unconstrained element, keep in mind that the second + /// element might get pushed out of the panel. + /// + ///Conflicting relationships + /// + /// If you set multiple relationships that target the same edge of an element, you might have conflicting + /// relationships in your layout as a result.When this happens, the relationships are applied in the + /// following order of priority: + /// • Panel alignment relationships (AlignTopWithPanel, AlignLeftWithPanel, …) are applied first. + /// • Sibling alignment relationships(AlignTopWith, AlignLeftWith, …) are applied second. + /// • Sibling positional relationships(Above, Below, RightOf, LeftOf) are applied last. + /// + /// + /// The panel-center alignment properties(AlignVerticalCenterWith, AlignHorizontalCenterWithPanel, ...) are + /// typically used independently of other constraints and are applied if there is no conflict. + /// + /// + /// The HorizontalAlignment and VerticalAlignment properties on UI elements are applied after relationship + /// properties are evaluated and applied. These properties control the placement of the element within the + /// available size for the element, if the desired size is smaller than the available size. + /// + /// + public partial class RelativePanel : Panel + { + // Dependency property for storing intermediate arrange state on the children + private static readonly DependencyProperty ArrangeStateProperty = + DependencyProperty.Register("ArrangeState", typeof(double[]), typeof(RelativePanel), new PropertyMetadata(null)); + + /// + /// When overridden in a derived class, measures the size in layout required for + /// child elements and determines a size for the System.Windows.FrameworkElement-derived + /// class. + /// + /// The available size that this element can give to child elements. Infinity can + /// be specified as a value to indicate that the element will size to whatever content + /// is available. + /// + /// + /// The size that this element determines it needs during layout, based on its calculations + /// of child element sizes. + /// + protected override Size MeasureOverride(Size availableSize) + { + foreach (var child in Children.OfType()) + { + child.Measure(availableSize); + } + foreach (var item in CalculateLocations(availableSize)) + { + if (item.Item2.Size.Width < item.Item1.DesiredSize.Width || item.Item2.Size.Height < item.Item1.DesiredSize.Height) + item.Item1.Measure(item.Item2.Size); + } + return base.MeasureOverride(availableSize); + } + + /// + /// When overridden in a derived class, positions child elements and determines a + /// size for a System.Windows.FrameworkElement derived class. + /// + /// + /// The final area within the parent that this element should use to arrange itself + /// and its children. + /// + /// The actual size used. + protected override Size ArrangeOverride(Size finalSize) + { + foreach (var item in CalculateLocations(finalSize)) + item.Item1.Arrange(item.Item2); + return base.ArrangeOverride(finalSize); + } + + private IEnumerable> CalculateLocations(Size finalSize) + { + //List of margins for each element between the element and panel (left, top, right, bottom) + List arranges = new List(Children.Count); + //First pass aligns all sides that aren't constrained by other elements + int arrangedCount = 0; + foreach (var child in Children.OfType()) + { + //NaN means the arrange value is not constrained yet for that side + double[] rect = new[] { double.NaN, double.NaN, double.NaN, double.NaN }; + arranges.Add(rect); + child.SetValue(ArrangeStateProperty, rect); + + //Align with panels always wins, so do these first, or if no constraints are set at all set margin to 0 + + //Left side + if (GetAlignLeftWithPanel(child)) + { + rect[0] = 0; + } + else if ( + child.GetValue(AlignLeftWithProperty) == null && + child.GetValue(RightOfProperty) == null && + child.GetValue(AlignHorizontalCenterWithProperty) == null && + !GetAlignHorizontalCenterWithPanel(child)) + { + if (GetAlignRightWithPanel(child)) + rect[0] = finalSize.Width - child.DesiredSize.Width; + else if (child.GetValue(AlignRightWithProperty) == null && child.GetValue(AlignHorizontalCenterWithProperty) == null && child.GetValue(LeftOfProperty) == null) + rect[0] = 0; //default fallback to 0 + } + + //Top side + if (GetAlignTopWithPanel(child)) + { + rect[1] = 0; + } + else if ( + child.GetValue(AlignTopWithProperty) == null && + child.GetValue(BelowProperty) == null && + child.GetValue(AlignVerticalCenterWithProperty) == null && + !GetAlignVerticalCenterWithPanel(child)) + { + if (GetAlignBottomWithPanel(child)) + rect[1] = finalSize.Height - child.DesiredSize.Height; + else if (child.GetValue(AlignBottomWithProperty) == null && child.GetValue(AlignVerticalCenterWithProperty) == null && child.GetValue(AboveProperty) == null) + rect[1] = 0; //default fallback to 0 + } + + //Right side + if (GetAlignRightWithPanel(child)) + { + rect[2] = 0; + } + else if (!double.IsNaN(rect[0]) && + child.GetValue(AlignRightWithProperty) == null && + child.GetValue(LeftOfProperty) == null && + child.GetValue(AlignHorizontalCenterWithProperty) == null && + !GetAlignHorizontalCenterWithPanel(child)) + { + rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; + } + + //Bottom side + if (GetAlignBottomWithPanel(child)) + rect[3] = 0; + else if (!double.IsNaN(rect[1]) && + (child.GetValue(AlignBottomWithProperty) == null && + child.GetValue(AboveProperty) == null) && + child.GetValue(AlignVerticalCenterWithProperty) == null && + !GetAlignVerticalCenterWithPanel(child)) + { + rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; + } + + if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && + !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) + arrangedCount++; + } + int i = 0; + //Run iterative layout passes + while (arrangedCount < Children.Count) + { + bool valueChanged = false; + i = 0; + foreach (var child in Children.OfType()) + { + double[] rect = arranges[i++]; + + if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && + !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) + continue; //Control is fully arranged + + //Calculate left side + if (double.IsNaN(rect[0])) + { + var alignLeftWith = GetDependencyElement(RelativePanel.AlignLeftWithProperty, child); + if (alignLeftWith != null) + { + double[] r = (double[])alignLeftWith.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[0])) + { + rect[0] = r[0]; + valueChanged = true; + } + } + else + { + var rightOf = GetDependencyElement(RelativePanel.RightOfProperty, child); + if (rightOf != null) + { + double[] r = (double[])rightOf.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[2])) + { + rect[0] = finalSize.Width - r[2]; + valueChanged = true; + } + } + else if (!double.IsNaN(rect[2])) + { + rect[0] = finalSize.Width - rect[2] - child.DesiredSize.Width; + valueChanged = true; + } + } + } + //Calculate top side + if (double.IsNaN(rect[1])) + { + var alignTopWith = GetDependencyElement(RelativePanel.AlignTopWithProperty, child); + if (alignTopWith != null) + { + double[] r = (double[])alignTopWith.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[1])) + { + rect[1] = r[1]; + valueChanged = true; + } + } + else + { + var below = GetDependencyElement(RelativePanel.BelowProperty, child); + if (below != null) + { + double[] r = (double[])below.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[3])) + { + rect[1] = finalSize.Height - r[3]; + valueChanged = true; + } + } + else if (!double.IsNaN(rect[3])) + { + rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; + valueChanged = true; + } + } + } + //Calculate right side + if (double.IsNaN(rect[2])) + { + var alignRightWith = GetDependencyElement(RelativePanel.AlignRightWithProperty, child); + if (alignRightWith != null) + { + double[] r = (double[])alignRightWith.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[2])) + { + rect[2] = r[2]; + if (double.IsNaN(rect[0])) + { + if (child.GetValue(RelativePanel.AlignLeftWithProperty) == null) + { + rect[0] = rect[2] + child.DesiredSize.Width; + valueChanged = true; + } + } + } + } + else + { + var leftOf = GetDependencyElement(RelativePanel.LeftOfProperty, child); + if (leftOf != null) + { + double[] r = (double[])leftOf.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[0])) + { + rect[2] = finalSize.Width - r[0]; + valueChanged = true; + } + } + else if (!double.IsNaN(rect[0])) + { + rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; + valueChanged = true; + } + } + } + //Calculate bottom side + if (double.IsNaN(rect[3])) + { + var alignBottomWith = GetDependencyElement(RelativePanel.AlignBottomWithProperty, child); + if (alignBottomWith != null) + { + double[] r = (double[])alignBottomWith.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[3])) + { + rect[3] = r[3]; + valueChanged = true; + if (double.IsNaN(rect[1])) + { + if (child.GetValue(RelativePanel.AlignTopWithProperty) == null) + { + rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; + } + } + } + } + else + { + var above = GetDependencyElement(RelativePanel.AboveProperty, child); + if (above != null) + { + double[] r = (double[])above.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[1])) + { + rect[3] = finalSize.Height - r[1]; + valueChanged = true; + } + } + else if (!double.IsNaN(rect[1])) + { + rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; + valueChanged = true; + } + } + } + //Calculate horizontal alignment + if (double.IsNaN(rect[0]) && double.IsNaN(rect[2])) + { + var alignHorizontalCenterWith = GetDependencyElement(RelativePanel.AlignHorizontalCenterWithProperty, child); + if (alignHorizontalCenterWith != null) + { + double[] r = (double[])alignHorizontalCenterWith.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[0]) && !double.IsNaN(r[2])) + { + rect[0] = r[0] + (finalSize.Width - r[2] - r[0]) * .5 - child.DesiredSize.Width * .5; + rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; + valueChanged = true; + } + } + else + { + if (GetAlignHorizontalCenterWithPanel(child)) + { + var roomToSpare = finalSize.Width - child.DesiredSize.Width; + rect[0] = roomToSpare * .5; + rect[2] = roomToSpare * .5; + valueChanged = true; + } + } + } + + //Calculate vertical alignment + if (double.IsNaN(rect[1]) && double.IsNaN(rect[3])) + { + var alignVerticalCenterWith = GetDependencyElement(RelativePanel.AlignVerticalCenterWithProperty, child); + if (alignVerticalCenterWith != null) + { + double[] r = (double[])alignVerticalCenterWith.GetValue(ArrangeStateProperty); + if (!double.IsNaN(r[1]) && !double.IsNaN(r[3])) + { + rect[1] = r[1] + (finalSize.Height - r[3] - r[1]) * .5 - child.DesiredSize.Height * .5; + rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; + valueChanged = true; + } + } + else + { + if (GetAlignVerticalCenterWithPanel(child)) + { + var roomToSpare = finalSize.Height - child.DesiredSize.Height; + rect[1] = roomToSpare * .5; + rect[3] = roomToSpare * .5; + valueChanged = true; + } + } + } + + + //if panel is now fully arranged, increase the counter + if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && + !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) + arrangedCount++; + } + if (!valueChanged) + { + //If a layout pass didn't increase number of arranged elements, + //there must be a circular dependency + throw new ArgumentException("RelativePanel error: Circular dependency detected. Layout could not complete"); + } + } + + i = 0; + //Arrange iterations complete - Apply the results to the child elements + foreach (var child in Children.OfType()) + { + double[] rect = arranges[i++]; + //Measure child again with the new calculated available size + //this helps for instance textblocks to reflow the text wrapping + //We should probably have done this during the measure step but it would cause a more expensive + //measure+arrange layout cycle + //if(child is TextBlock) + // child.Measure(new Size(Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); + + //if(child is TextBlock tb) + //{ + // tb.ArrangeOverride(new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); + //} + //else + yield return new Tuple(child, new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); + } + } + + //Gets the element that's referred to in the alignment attached properties + private UIElement GetDependencyElement(DependencyProperty property, DependencyObject child) + { + var dependency = child.GetValue(property); + if (dependency == null) + return null; + if (dependency is UIElement) + { + if (Children.Contains((UIElement)dependency)) + return (UIElement)dependency; + throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); + } + + throw new ArgumentException("RelativePanel error: Value must be of type UIElement"); + } + } +} From 289463e5e15e2e277d795bfef34564d830861a8b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 24 May 2020 22:45:56 -0300 Subject: [PATCH 02/44] port relativepanel. --- .../RelativePanel.AttachedProperties.cs | 159 ++++++++++-------- src/Avalonia.Controls/RelativePanel.cs | 29 ++-- .../Avalonia.Markup.Xaml.csproj | 1 + .../Converters/NameReferenceConverter.cs | 82 +++++++++ 4 files changed, 189 insertions(+), 82 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index 21baff87ab..a42eb6d428 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -1,16 +1,39 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Text; +using Avalonia.Layout; +using Avalonia.Markup.Xaml.Converters; namespace Avalonia.Controls { public partial class RelativePanel { - private static void OnAlignPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { - var elm = d as FrameworkElement; - if (elm.Parent is FrameworkElement) - ((FrameworkElement)elm.Parent).InvalidateArrange(); + var elm = d as Layoutable; + if (elm.Parent is Layoutable) + ((Layoutable)elm.Parent).InvalidateArrange(); + } + + static RelativePanel() + { + AboveProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignBottomWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignBottomWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignHorizontalCenterWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignHorizontalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignLeftWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignLeftWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignRightWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignRightWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignTopWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignTopWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignVerticalCenterWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); } /// @@ -22,7 +45,7 @@ namespace Avalonia.Controls /// (The element to position this element above.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAbove(DependencyObject obj) + public static object GetAbove(AvaloniaObject obj) { return (object)obj.GetValue(AboveProperty); } @@ -32,7 +55,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to position this element above.) - public static void SetAbove(DependencyObject obj, object value) + public static void SetAbove(AvaloniaObject obj, object value) { obj.SetValue(AboveProperty, value); } @@ -40,8 +63,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AboveProperty = - DependencyProperty.RegisterAttached("Above", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AboveProperty = + AvaloniaProperty.RegisterAttached("Above", typeof(RelativePanel)); /// @@ -53,7 +76,7 @@ namespace Avalonia.Controls /// object. (true to align this element's bottom edge with the panel's bottom edge; /// otherwise, false.) /// - public static bool GetAlignBottomWithPanel(DependencyObject obj) + public static bool GetAlignBottomWithPanel(AvaloniaObject obj) { return (bool)obj.GetValue(AlignBottomWithPanelProperty); } @@ -66,7 +89,7 @@ namespace Avalonia.Controls /// The value to set. (true to align this element's bottom edge with the panel's /// bottom edge; otherwise, false.) /// - public static void SetAlignBottomWithPanel(DependencyObject obj, bool value) + public static void SetAlignBottomWithPanel(AvaloniaObject obj, bool value) { obj.SetValue(AlignBottomWithPanelProperty, value); } @@ -74,8 +97,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignBottomWithPanelProperty = - DependencyProperty.RegisterAttached("AlignBottomWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignBottomWithPanelProperty = + AvaloniaProperty.RegisterAttached("AlignBottomWithPanel", typeof(RelativePanel)); /// /// Gets the value of the RelativePanel.AlignBottomWith XAML attached property for the target element. @@ -86,7 +109,7 @@ namespace Avalonia.Controls /// (The element to align this element's bottom edge with.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAlignBottomWith(DependencyObject obj) + public static object GetAlignBottomWith(AvaloniaObject obj) { return (object)obj.GetValue(AlignBottomWithProperty); } @@ -96,7 +119,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's bottom edge with.) - public static void SetAlignBottomWith(DependencyObject obj, object value) + public static void SetAlignBottomWith(AvaloniaObject obj, object value) { obj.SetValue(AlignBottomWithProperty, value); } @@ -104,8 +127,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignBottomWithProperty = - DependencyProperty.RegisterAttached("AlignBottomWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignBottomWithProperty = + AvaloniaProperty.RegisterAttached("AlignBottomWith", typeof(RelativePanel)); /// /// Gets the value of the RelativePanel.AlignHorizontalCenterWithPanel XAML attached property for the target element. @@ -116,7 +139,7 @@ namespace Avalonia.Controls /// of the specified object. (true to horizontally center this element in the panel; /// otherwise, false.) /// - public static bool GetAlignHorizontalCenterWithPanel(DependencyObject obj) + public static bool GetAlignHorizontalCenterWithPanel(AvaloniaObject obj) { return (bool)obj.GetValue(AlignHorizontalCenterWithPanelProperty); } @@ -129,7 +152,7 @@ namespace Avalonia.Controls /// The value to set. (true to horizontally center this element in the panel; otherwise, /// false.) /// - public static void SetAlignHorizontalCenterWithPanel(DependencyObject obj, bool value) + public static void SetAlignHorizontalCenterWithPanel(AvaloniaObject obj, bool value) { obj.SetValue(AlignHorizontalCenterWithPanelProperty, value); } @@ -137,8 +160,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignHorizontalCenterWithPanelProperty = - DependencyProperty.RegisterAttached("AlignHorizontalCenterWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignHorizontalCenterWithPanelProperty = + AvaloniaProperty.RegisterAttached("AlignHorizontalCenterWithPanel", typeof(RelativePanel), false); /// /// Gets the value of the RelativePanel.AlignHorizontalCenterWith XAML attached property for the target element. @@ -149,7 +172,7 @@ namespace Avalonia.Controls /// specified object. (The element to align this element's horizontal center with.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAlignHorizontalCenterWith(DependencyObject obj) + public static object GetAlignHorizontalCenterWith(AvaloniaObject obj) { return (object)obj.GetValue(AlignHorizontalCenterWithProperty); } @@ -159,7 +182,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's horizontal center with.) - public static void SetAlignHorizontalCenterWith(DependencyObject obj, object value) + public static void SetAlignHorizontalCenterWith(AvaloniaObject obj, object value) { obj.SetValue(AlignHorizontalCenterWithProperty, value); } @@ -167,8 +190,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignHorizontalCenterWithProperty = - DependencyProperty.RegisterAttached("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignHorizontalCenterWithProperty = + AvaloniaProperty.RegisterAttached("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel)); /// /// Gets the value of the RelativePanel.AlignLeftWithPanel XAML attached property for the target element. @@ -179,7 +202,7 @@ namespace Avalonia.Controls /// object. (true to align this element's left edge with the panel's left edge; otherwise, /// false.) /// - public static bool GetAlignLeftWithPanel(DependencyObject obj) + public static bool GetAlignLeftWithPanel(AvaloniaObject obj) { return (bool)obj.GetValue(AlignLeftWithPanelProperty); } @@ -192,7 +215,7 @@ namespace Avalonia.Controls /// The value to set. (true to align this element's left edge with the panel's left /// edge; otherwise, false.) /// - public static void SetAlignLeftWithPanel(DependencyObject obj, bool value) + public static void SetAlignLeftWithPanel(AvaloniaObject obj, bool value) { obj.SetValue(AlignLeftWithPanelProperty, value); } @@ -200,8 +223,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignLeftWithPanelProperty = - DependencyProperty.RegisterAttached("AlignLeftWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignLeftWithPanelProperty = + AvaloniaProperty.RegisterAttached("AlignLeftWithPanel", typeof(RelativePanel), false); /// @@ -213,7 +236,7 @@ namespace Avalonia.Controls /// object. (The element to align this element's left edge with.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAlignLeftWith(DependencyObject obj) + public static object GetAlignLeftWith(AvaloniaObject obj) { return (object)obj.GetValue(AlignLeftWithProperty); } @@ -223,7 +246,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's left edge with.) - public static void SetAlignLeftWith(DependencyObject obj, object value) + public static void SetAlignLeftWith(AvaloniaObject obj, object value) { obj.SetValue(AlignLeftWithProperty, value); } @@ -231,8 +254,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignLeftWithProperty = - DependencyProperty.RegisterAttached("AlignLeftWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignLeftWithProperty = + AvaloniaProperty.RegisterAttached("AlignLeftWith"); /// @@ -244,7 +267,7 @@ namespace Avalonia.Controls /// object. (true to align this element's right edge with the panel's right edge; /// otherwise, false.) /// - public static bool GetAlignRightWithPanel(DependencyObject obj) + public static bool GetAlignRightWithPanel(AvaloniaObject obj) { return (bool)obj.GetValue(AlignRightWithPanelProperty); } @@ -257,7 +280,7 @@ namespace Avalonia.Controls /// The value to set. (true to align this element's right edge with the panel's right /// edge; otherwise, false.) /// - public static void SetAlignRightWithPanel(DependencyObject obj, bool value) + public static void SetAlignRightWithPanel(AvaloniaObject obj, bool value) { obj.SetValue(AlignRightWithPanelProperty, value); } @@ -265,8 +288,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignRightWithPanelProperty = - DependencyProperty.RegisterAttached("AlignRightWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignRightWithPanelProperty = + AvaloniaProperty.RegisterAttached("AlignRightWithPanel", false); /// /// Gets the value of the RelativePanel.AlignRightWith XAML attached property for the target element. @@ -277,7 +300,7 @@ namespace Avalonia.Controls /// object. (The element to align this element's right edge with.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAlignRightWith(DependencyObject obj) + public static object GetAlignRightWith(AvaloniaObject obj) { return (object)obj.GetValue(AlignRightWithProperty); } @@ -287,7 +310,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's right edge with.) - public static void SetAlignRightWith(DependencyObject obj, object value) + public static void SetAlignRightWith(AvaloniaObject obj, object value) { obj.SetValue(AlignRightWithProperty, value); } @@ -295,8 +318,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignRightWithProperty = - DependencyProperty.RegisterAttached("AlignRightWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignRightWithProperty = + AvaloniaProperty.RegisterAttached("AlignRightWith"); /// /// Gets the value of the RelativePanel.AlignTopWithPanel XAML attached property for the target element. @@ -307,7 +330,7 @@ namespace Avalonia.Controls /// object. (true to align this element's top edge with the panel's top edge; otherwise, /// false.) /// - public static bool GetAlignTopWithPanel(DependencyObject obj) + public static bool GetAlignTopWithPanel(AvaloniaObject obj) { return (bool)obj.GetValue(AlignTopWithPanelProperty); } @@ -320,7 +343,7 @@ namespace Avalonia.Controls /// The value to set. (true to align this element's top edge with the panel's top /// edge; otherwise, false.) /// - public static void SetAlignTopWithPanel(DependencyObject obj, bool value) + public static void SetAlignTopWithPanel(AvaloniaObject obj, bool value) { obj.SetValue(AlignTopWithPanelProperty, value); } @@ -328,8 +351,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignTopWithPanelProperty = - DependencyProperty.RegisterAttached("AlignTopWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignTopWithPanelProperty = + AvaloniaProperty.RegisterAttached("AlignTopWithPanel", false); /// /// Gets the value of the RelativePanel.AlignTopWith XAML attached property for the target element. @@ -337,7 +360,7 @@ namespace Avalonia.Controls /// The object from which the property value is read. /// The value to set. (The element to align this element's top edge with.) [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAlignTopWith(DependencyObject obj) + public static object GetAlignTopWith(AvaloniaObject obj) { return (object)obj.GetValue(AlignTopWithProperty); } @@ -347,7 +370,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's top edge with.) - public static void SetAlignTopWith(DependencyObject obj, object value) + public static void SetAlignTopWith(AvaloniaObject obj, object value) { obj.SetValue(AlignTopWithProperty, value); } @@ -355,8 +378,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignTopWithProperty = - DependencyProperty.RegisterAttached("AlignTopWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignTopWithProperty = + AvaloniaProperty.RegisterAttached("AlignTopWith"); /// /// Gets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for the target element. @@ -367,7 +390,7 @@ namespace Avalonia.Controls /// the specified object. (true to vertically center this element in the panel; otherwise, /// false.) /// - public static bool GetAlignVerticalCenterWithPanel(DependencyObject obj) + public static bool GetAlignVerticalCenterWithPanel(AvaloniaObject obj) { return (bool)obj.GetValue(AlignVerticalCenterWithPanelProperty); } @@ -380,7 +403,7 @@ namespace Avalonia.Controls /// The value to set. (true to vertically center this element in the panel; otherwise, /// false.) /// - public static void SetAlignVerticalCenterWithPanel(DependencyObject obj, bool value) + public static void SetAlignVerticalCenterWithPanel(AvaloniaObject obj, bool value) { obj.SetValue(AlignVerticalCenterWithPanelProperty, value); } @@ -388,8 +411,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignVerticalCenterWithPanelProperty = - DependencyProperty.RegisterAttached("AlignVerticalCenterWithPanel", typeof(bool), typeof(RelativePanel), new PropertyMetadata(false, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignVerticalCenterWithPanelProperty = + AvaloniaProperty.RegisterAttached("AlignVerticalCenterWithPanel", false); /// /// Gets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for the target element. @@ -397,7 +420,7 @@ namespace Avalonia.Controls /// The object from which the property value is read. /// The value to set. (The element to align this element's vertical center with.) [TypeConverter(typeof(NameReferenceConverter))] - public static object GetAlignVerticalCenterWith(DependencyObject obj) + public static object GetAlignVerticalCenterWith(AvaloniaObject obj) { return (object)obj.GetValue(AlignVerticalCenterWithProperty); } @@ -407,7 +430,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's horizontal center with.) - public static void SetAlignVerticalCenterWith(DependencyObject obj, object value) + public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value) { obj.SetValue(AlignVerticalCenterWithProperty, value); } @@ -415,8 +438,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty AlignVerticalCenterWithProperty = - DependencyProperty.RegisterAttached("AlignVerticalCenterWith", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty AlignVerticalCenterWithProperty = + AvaloniaProperty.RegisterAttached("AlignVerticalCenterWith"); /// /// Gets the value of the RelativePanel.Below XAML attached property for the target element. @@ -427,7 +450,7 @@ namespace Avalonia.Controls /// (The element to position this element below.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetBelow(DependencyObject obj) + public static object GetBelow(AvaloniaObject obj) { return (object)obj.GetValue(BelowProperty); } @@ -437,7 +460,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to position this element below.) - public static void SetBelow(DependencyObject obj, object value) + public static void SetBelow(AvaloniaObject obj, object value) { obj.SetValue(BelowProperty, value); } @@ -445,8 +468,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty BelowProperty = - DependencyProperty.RegisterAttached("Below", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty BelowProperty = + AvaloniaProperty.RegisterAttached("Below"); /// /// Gets the value of the RelativePanel.LeftOf XAML attached property for the target element. @@ -457,7 +480,7 @@ namespace Avalonia.Controls /// (The element to position this element to the left of.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetLeftOf(DependencyObject obj) + public static object GetLeftOf(AvaloniaObject obj) { return (object)obj.GetValue(LeftOfProperty); } @@ -467,7 +490,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to position this element to the left of.) - public static void SetLeftOf(DependencyObject obj, object value) + public static void SetLeftOf(AvaloniaObject obj, object value) { obj.SetValue(LeftOfProperty, value); } @@ -475,8 +498,8 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty LeftOfProperty = - DependencyProperty.RegisterAttached("LeftOf", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty LeftOfProperty = + AvaloniaProperty.RegisterAttached("LeftOf"); /// /// Gets the value of the RelativePanel.RightOf XAML attached property for the target element. @@ -487,7 +510,7 @@ namespace Avalonia.Controls /// (The element to position this element to the right of.) /// [TypeConverter(typeof(NameReferenceConverter))] - public static object GetRightOf(DependencyObject obj) + public static object GetRightOf(AvaloniaObject obj) { return (object)obj.GetValue(RightOfProperty); } @@ -497,7 +520,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to position this element to the right of.) - public static void SetRightOf(DependencyObject obj, object value) + public static void SetRightOf(AvaloniaObject obj, object value) { obj.SetValue(RightOfProperty, value); } @@ -505,7 +528,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - public static readonly DependencyProperty RightOfProperty = - DependencyProperty.RegisterAttached("RightOf", typeof(object), typeof(RelativePanel), new PropertyMetadata(null, OnAlignPropertiesChanged)); + public static readonly AttachedProperty RightOfProperty = + AvaloniaProperty.RegisterAttached("RightOf"); } } diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index aecd4b7c98..f1989de570 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -37,8 +38,8 @@ namespace Avalonia.Controls public partial class RelativePanel : Panel { // Dependency property for storing intermediate arrange state on the children - private static readonly DependencyProperty ArrangeStateProperty = - DependencyProperty.Register("ArrangeState", typeof(double[]), typeof(RelativePanel), new PropertyMetadata(null)); + private static readonly StyledProperty ArrangeStateProperty = + AvaloniaProperty.Register("ArrangeState"); /// /// When overridden in a derived class, measures the size in layout required for @@ -55,7 +56,7 @@ namespace Avalonia.Controls /// protected override Size MeasureOverride(Size availableSize) { - foreach (var child in Children.OfType()) + foreach (var child in Children.OfType()) { child.Measure(availableSize); } @@ -83,13 +84,13 @@ namespace Avalonia.Controls return base.ArrangeOverride(finalSize); } - private IEnumerable> CalculateLocations(Size finalSize) + private IEnumerable> CalculateLocations(Size finalSize) { //List of margins for each element between the element and panel (left, top, right, bottom) List arranges = new List(Children.Count); //First pass aligns all sides that aren't constrained by other elements int arrangedCount = 0; - foreach (var child in Children.OfType()) + foreach (var child in Children.OfType()) { //NaN means the arrange value is not constrained yet for that side double[] rect = new[] { double.NaN, double.NaN, double.NaN, double.NaN }; @@ -168,7 +169,7 @@ namespace Avalonia.Controls { bool valueChanged = false; i = 0; - foreach (var child in Children.OfType()) + foreach (var child in Children.OfType()) { double[] rect = arranges[i++]; @@ -386,7 +387,7 @@ namespace Avalonia.Controls i = 0; //Arrange iterations complete - Apply the results to the child elements - foreach (var child in Children.OfType()) + foreach (var child in Children.OfType()) { double[] rect = arranges[i++]; //Measure child again with the new calculated available size @@ -401,24 +402,24 @@ namespace Avalonia.Controls // tb.ArrangeOverride(new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); //} //else - yield return new Tuple(child, new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); + yield return new Tuple(child, new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); } } //Gets the element that's referred to in the alignment attached properties - private UIElement GetDependencyElement(DependencyProperty property, DependencyObject child) + private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { var dependency = child.GetValue(property); if (dependency == null) return null; - if (dependency is UIElement) + if (dependency is Layoutable) { - if (Children.Contains((UIElement)dependency)) - return (UIElement)dependency; + if (Children.Contains((ILayoutable)dependency)) + return (Layoutable)dependency; throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); } - throw new ArgumentException("RelativePanel error: Value must be of type UIElement"); + throw new ArgumentException("RelativePanel error: Value must be of type ILayoutable"); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 06c5375520..724d910b62 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs new file mode 100644 index 0000000000..885648ffde --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs @@ -0,0 +1,82 @@ +using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Avalonia.Markup.Xaml.Converters +{ + public class NameReferenceConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + + return base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + //var nameResolver = (IXamlNameResolver)context.GetService(typeof(IXamlNameResolver)); + //if (nameResolver == null) + //{ + // throw new InvalidOperationException(SR.Get(SRID.MissingNameResolver)); + //} + + //string name = value as string; + //if (String.IsNullOrEmpty(name)) + //{ + // throw new InvalidOperationException(SR.Get(SRID.MustHaveName)); + //} + //object obj = nameResolver.Resolve(name); + //if (obj == null) + //{ + // string[] names = new string[] { name }; + // obj = nameResolver.GetFixupToken(names, true); + //} + return null; //obj; + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + //if (context == null || (context.GetService(typeof(IXamlNameProvider)) as IXamlNameProvider) == null) + //{ + // return false; + //} + + //if (destinationType == typeof(string)) + //{ + // return true; + //} + + return base.CanConvertTo(context, destinationType); + + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + //if (context == null) + //{ + // throw new ArgumentNullException(nameof(context)); + //} + + //var nameProvider = (IXamlNameProvider)context.GetService(typeof(IXamlNameProvider)); + //if (nameProvider == null) + //{ + // throw new InvalidOperationException(SR.Get(SRID.MissingNameProvider)); + //} + + //return nameProvider.GetName(value); + return null; + } + } +} From f545fb2a4dff4e4eefc037aaa20d343ae8d5479e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jul 2020 23:22:42 +0200 Subject: [PATCH 03/44] Ported IElementFactory from UWP/WinUI. --- .../Repeater/IElementFactory.cs | 66 +++++++++++++++++++ .../Repeater/ItemTemplateWrapper.cs | 20 +++++- .../Repeater/ItemsRepeater.cs | 4 +- src/Avalonia.Controls/Repeater/ViewManager.cs | 31 +++++++-- 4 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.Controls/Repeater/IElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs new file mode 100644 index 0000000000..6a899a6f26 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs @@ -0,0 +1,66 @@ +using Avalonia.Controls.Templates; + +namespace Avalonia.Controls +{ + /// + /// Represents the optional arguments to use when calling an implementation of the + /// 's method. + /// + public class ElementFactoryGetArgs + { + /// + /// Gets or sets the data item for which an appropriate element tree should be realized + /// when calling . + /// + public object Data { get; set; } + + /// + /// Gets or sets the that is expected to be the parent of the + /// realized element from . + /// + public IControl Parent { get; set; } + + /// + /// Gets or sets the index of the item that should be realized. + /// + public int Index { get; set; } + } + + /// + /// Represents the optional arguments to use when calling an implementation of the + /// 's method. + /// + public class ElementFactoryRecycleArgs + { + /// + /// Gets or sets the to recycle when calling + /// . + /// + public IControl Element { get; set; } + + /// + /// Gets or sets the that is expected to be the parent of the + /// realized element from . + /// + public IControl Parent { get; set; } + } + + /// + /// A data template that supports creating and recyling elements for an . + /// + public interface IElementFactory : IDataTemplate + { + /// + /// Gets an . + /// + /// The element args. + public IControl GetElement(ElementFactoryGetArgs args); + + /// + /// Recycles an that was previously retrieved using + /// . + /// + /// The recycle args. + public void RecycleElement(ElementFactoryRecycleArgs args); + } +} diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs index 04d859c742..4b784375a9 100644 --- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs +++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs @@ -7,13 +7,27 @@ using Avalonia.Controls.Templates; namespace Avalonia.Controls { - internal class ItemTemplateWrapper + internal class ItemTemplateWrapper : IElementFactory { private readonly IDataTemplate _dataTemplate; public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; - public IControl GetElement(IControl parent, object data) + public bool SupportsRecycling => false; + public IControl Build(object param) => GetElement(null, param); + public bool Match(object data) => _dataTemplate.Match(data); + + public IControl GetElement(ElementFactoryGetArgs args) + { + return GetElement(args.Parent, args.Data); + } + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + RecycleElement(args.Parent, args.Element); + } + + private IControl GetElement(IControl parent, object data) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); @@ -37,7 +51,7 @@ namespace Avalonia.Controls return element; } - public void RecycleElement(IControl parent, IControl element) + private void RecycleElement(IControl parent, IControl element) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 87f4760156..8bc356bdec 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -141,7 +141,7 @@ namespace Avalonia.Controls /// public ItemsSourceView ItemsSourceView { get; private set; } - internal ItemTemplateWrapper ItemTemplateShim { get; set; } + internal IElementFactory ItemTemplateShim { get; set; } internal Point LayoutOrigin { get; set; } internal object LayoutState { get; set; } internal IControl MadeAnchor => _viewportManager.MadeAnchor; @@ -664,7 +664,7 @@ namespace Avalonia.Controls } } - ItemTemplateShim = new ItemTemplateWrapper(newValue); + ItemTemplateShim = newValue as IElementFactory ?? new ItemTemplateWrapper(newValue); InvalidateMeasure(); } diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index eff51804b9..416b1e2824 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -6,11 +6,9 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Layout; using Avalonia.Logging; using Avalonia.VisualTree; @@ -26,6 +24,8 @@ namespace Avalonia.Controls private readonly UniqueIdElementPool _resetPool; private IControl _lastFocusedElement; private bool _isDataSourceStableResetPending; + private ElementFactoryGetArgs _elementFactoryGetArgs; + private ElementFactoryRecycleArgs _elementFactoryRecycleArgs; private int _firstRealizedElementIndexHeldByLayout = FirstRealizedElementIndexDefault; private int _lastRealizedElementIndexHeldByLayout = LastRealizedElementIndexDefault; private bool _eventsSubscribed; @@ -134,7 +134,14 @@ namespace Avalonia.Controls if (_owner.ItemTemplateShim != null) { - _owner.ItemTemplateShim.RecycleElement(_owner, element); + var context = _elementFactoryRecycleArgs ??= new ElementFactoryRecycleArgs(); + context.Element = element; + context.Parent = _owner; + + _owner.ItemTemplateShim.RecycleElement(context); + + context.Element = null; + context.Parent = null; } else { @@ -579,7 +586,7 @@ namespace Avalonia.Controls var data = _owner.ItemsSourceView.GetAt(index); var providedElementFactory = _owner.ItemTemplateShim; - ItemTemplateWrapper GetElementFactory() + IElementFactory GetElementFactory() { if (providedElementFactory == null) { @@ -602,7 +609,20 @@ namespace Avalonia.Controls } var elementFactory = GetElementFactory(); - return elementFactory.GetElement(_owner, data); + var args = _elementFactoryGetArgs ??= new ElementFactoryGetArgs(); + + try + { + args.Data = data; + args.Parent = _owner; + args.Index = index; + return elementFactory.GetElement(args); + } + finally + { + args.Data = null; + args.Parent = null; + } } var element = GetElement(); @@ -732,6 +752,7 @@ namespace Avalonia.Controls { _owner.GotFocus += OnFocusChanged; _owner.LostFocus += OnFocusChanged; + _eventsSubscribed = true; } } From 87d1964e9fc4a62ce4acad8c5b3f6a88562546bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 09:42:47 +0200 Subject: [PATCH 04/44] Ported RecyclingElementFactory from WinUI. --- .../Repeater/ElementFactory.cs | 29 +++++ src/Avalonia.Controls/Repeater/RecyclePool.cs | 12 +- .../Repeater/RecyclingElementFactory.cs | 119 ++++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Controls/Repeater/ElementFactory.cs create mode 100644 src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs new file mode 100644 index 0000000000..1c1b71af88 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs @@ -0,0 +1,29 @@ +using Avalonia.Controls.Templates; + +namespace Avalonia.Controls +{ + public abstract class ElementFactory : IElementFactory + { + bool IDataTemplate.SupportsRecycling => false; + + public IControl Build(object data) + { + return GetElementCore(new ElementFactoryGetArgs { Data = data }); + } + + public IControl GetElement(ElementFactoryGetArgs args) + { + return GetElementCore(args); + } + + public bool Match(object data) => true; + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + RecycleElementCore(args); + } + + protected abstract IControl GetElementCore(ElementFactoryGetArgs args); + protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args); + } +} diff --git a/src/Avalonia.Controls/Repeater/RecyclePool.cs b/src/Avalonia.Controls/Repeater/RecyclePool.cs index 4e5950bdc5..28f299043c 100644 --- a/src/Avalonia.Controls/Repeater/RecyclePool.cs +++ b/src/Avalonia.Controls/Repeater/RecyclePool.cs @@ -11,10 +11,13 @@ using Avalonia.Controls.Templates; namespace Avalonia.Controls { - internal class RecyclePool + public class RecyclePool { - public static readonly AttachedProperty OriginTemplateProperty = - AvaloniaProperty.RegisterAttached("OriginTemplate", typeof(RecyclePool)); + internal static readonly AttachedProperty OriginTemplateProperty = + AvaloniaProperty.RegisterAttached("OriginTemplate"); + + internal static readonly AttachedProperty ReuseKeyProperty = + AvaloniaProperty.RegisterAttached("ReuseKey", string.Empty); private static ConditionalWeakTable s_pools = new ConditionalWeakTable(); private readonly Dictionary> _elements = new Dictionary>(); @@ -77,6 +80,9 @@ namespace Avalonia.Controls return null; } + internal string GetReuseKey(IControl element) => element.GetValue(ReuseKeyProperty); + internal void SetReuseKey(IControl element, string value) => element.SetValue(ReuseKeyProperty, value); + private IPanel EnsureOwnerIsPanelOrNull(IControl owner) { if (owner is IPanel panel) diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs new file mode 100644 index 0000000000..9503239e34 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Templates; + +#nullable enable + +namespace Avalonia.Controls +{ + public class SelectTemplateEventArgs : EventArgs + { + public string? TemplateKey { get; set; } + public object? DataContext { get; internal set; } + public IControl? Owner { get; internal set; } + } + + public class RecyclingElementFactory : ElementFactory + { + private RecyclePool? _recyclePool; + private IDictionary? _templates; + private SelectTemplateEventArgs? _args; + + public RecyclingElementFactory() + { + Templates = new Dictionary(); + } + + public RecyclePool RecyclePool + { + get => _recyclePool ??= new RecyclePool(); + set => _recyclePool = value ?? throw new ArgumentNullException(nameof(value)); + } + + public IDictionary Templates + { + get => _templates ??= new Dictionary(); + set => _templates = value ?? throw new ArgumentNullException(nameof(value)); + } + + public event EventHandler? SelectTemplateKey; + + protected override IControl GetElementCore(ElementFactoryGetArgs args) + { + if (_templates == null || _templates.Count == 0) + { + throw new InvalidOperationException("Templates cannot be empty."); + } + + var templateKey = Templates.Count == 1 ? + Templates.First().Key : + OnSelectTemplateKeyCore(args.Data, args.Parent); + + if (string.IsNullOrEmpty(templateKey)) + { + // Note: We could allow null/whitespace, which would work as long as + // the recycle pool is not shared. in order to make this work in all cases + // currently we validate that a valid template key is provided. + throw new InvalidOperationException("Template key cannot be null or empty."); + } + + // Get an element from the Recycle Pool or create one + var element = RecyclePool.TryGetElement(templateKey, args.Parent); + + if (element is null) + { + // No need to call HasKey if there is only one template. + if (Templates.Count > 1 && !Templates.ContainsKey(templateKey)) + { + var message = $"No templates of key '{templateKey}' were found in the templates collection."; + throw new InvalidOperationException(message); + } + + var dataTemplate = Templates[templateKey]; + element = dataTemplate.Build(args.Data); + + // Associate ReuseKey with element + RecyclePool.SetReuseKey(element, templateKey); + } + + return element; + } + + protected override void RecycleElementCore(ElementFactoryRecycleArgs args) + { + var element = args.Element; + var key = RecyclePool.GetReuseKey(element); + RecyclePool.PutElement(element, key, args.Parent); + } + + protected virtual string OnSelectTemplateKeyCore(object dataContext, IControl owner) + { + if (SelectTemplateKey is object) + { + _args ??= new SelectTemplateEventArgs(); + _args.TemplateKey = null; + _args.DataContext = dataContext; + _args.Owner = owner; + + try + { + SelectTemplateKey(this, _args); + } + finally + { + _args.DataContext = null; + _args.Owner = null; + } + } + + if (string.IsNullOrEmpty(_args?.TemplateKey)) + { + throw new InvalidOperationException( + "Please provide a valid template identifier in the handler for the SelectTemplateKey event."); + } + + return _args!.TemplateKey!; + } + } +} From 4fbc43e785e1609be3d2975568597e3d022b952b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 09:58:42 +0200 Subject: [PATCH 05/44] Use RecyclingElementFactory in ItemsRepeaterPage. --- .../Pages/ItemsRepeaterPage.xaml | 36 +++++++++++++------ .../Pages/ItemsRepeaterPage.xaml.cs | 6 ++++ .../ViewModels/ItemsRepeaterPageViewModel.cs | 12 ++----- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml index e600e644af..304782dbf9 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml @@ -1,6 +1,30 @@ + + + + + + + + + + + + + + + + ItemsRepeater @@ -23,16 +47,8 @@ - - - - - - - + diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs index cce80a2d3c..82c44508b8 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs @@ -38,6 +38,12 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } + public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e) + { + var item = (ItemsRepeaterPageViewModel.Item)e.DataContext; + e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd"; + } + private void LayoutChanged(object sender, SelectionChangedEventArgs e) { if (_repeater == null) diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index 73aaeff994..b859862f1f 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -55,20 +55,16 @@ namespace ControlCatalog.ViewModels return new ObservableCollection( Enumerable.Range(1, 100000).Select(i => new Item(i) { - Text = $"Item {i.ToString()} {suffix}" + Text = $"Item {i} {suffix}" })); } public class Item : ReactiveObject { private double _height = double.NaN; - private int _index; - - public Item(int index) - { - _index = index; - } + public Item(int index) => Index = index; + public int Index { get; } public string Text { get; set; } public double Height @@ -76,8 +72,6 @@ namespace ControlCatalog.ViewModels get => _height; set => this.RaiseAndSetIfChanged(ref _height, value); } - - public IBrush Background => ((_index % 2) == 0) ? Brushes.Yellow : Brushes.Wheat; } } } From 547d3228ea248a253d0e50e2a3d891980990fe7a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 16:08:15 +0200 Subject: [PATCH 06/44] Undo perf regression. --- samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index b859862f1f..f893a6e28e 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -55,7 +55,7 @@ namespace ControlCatalog.ViewModels return new ObservableCollection( Enumerable.Range(1, 100000).Select(i => new Item(i) { - Text = $"Item {i} {suffix}" + Text = $"Item {i.ToString()} {suffix}" })); } From 75c10992a9071f6cd8b1b9e179aa77f5655bfa10 Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Sat, 4 Jul 2020 11:51:50 -0400 Subject: [PATCH 07/44] Added ToggleSwitch dragging --- src/Avalonia.Controls/ToggleSwitch.cs | 97 ++++++++++++++++++++ src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 12 +-- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index 4b42c574cf..15e22d4d36 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -14,6 +14,17 @@ namespace Avalonia.Controls { OffContentProperty.Changed.AddClassHandler((x, e) => x.OffContentChanged(e)); OnContentProperty.Changed.AddClassHandler((x, e) => x.OnContentChanged(e)); + IsCheckedProperty.Changed.AddClassHandler((x, e) => + { + if ((e.NewValue != null) && (e.NewValue is bool val)) + x.UpdateKnobPos(val); + }); + + BoundsProperty.Changed.AddClassHandler((x, e) => + { + if (x.IsChecked != null) + x.UpdateKnobPos(x.IsChecked.Value); + }); } /// @@ -131,6 +142,92 @@ namespace Avalonia.Controls return result; } + + + Panel _knobsPanel; + Panel _switchKnob; + bool _knobsPanelPressed = false; + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + _switchKnob = e.NameScope.Find("SwitchKnob"); + _knobsPanel = e.NameScope.Find("MovingKnobs"); + + _knobsPanel.PointerPressed += KnobsPanel_PointerPressed; + _knobsPanel.PointerReleased += KnobsPanel_PointerReleased; + _knobsPanel.PointerMoved += KnobsPanel_PointerMoved; + + if (IsChecked.HasValue) + UpdateKnobPos(IsChecked.Value); + } + + Point _switchStartPoint = new Point(); + double _initLeft = -1; + private void KnobsPanel_PointerPressed(object sender, Input.PointerPressedEventArgs e) + { + _switchStartPoint = e.GetPosition(_switchKnob); + _initLeft = Canvas.GetLeft(_knobsPanel); + _isDragging = false; + _knobsPanelPressed = true; + } + + private void KnobsPanel_PointerReleased(object sender, Input.PointerReleasedEventArgs e) + { + if (_isDragging) + { + bool shouldBecomeChecked = Canvas.GetLeft(_knobsPanel) >= (_switchKnob.Bounds.Width / 2); + _knobsPanel.ClearValue(Canvas.LeftProperty); + + PseudoClasses.Set(":dragging", false); + + if (shouldBecomeChecked == IsChecked) + UpdateKnobPos(shouldBecomeChecked); + else + IsChecked = shouldBecomeChecked; + } + else + base.Toggle(); + + _isDragging = false; + + _knobsPanelPressed = false; + } + + bool _isDragging = false; + private void KnobsPanel_PointerMoved(object sender, Input.PointerEventArgs e) + { + if (_knobsPanelPressed) + { + var difference = e.GetPosition(_switchKnob) - _switchStartPoint; + + if ((!_isDragging) && (System.Math.Abs(difference.X) > 3)) + { + _isDragging = true; + PseudoClasses.Set(":dragging", true); + } + + if (_isDragging) + Canvas.SetLeft(_knobsPanel, System.Math.Min(_switchKnob.Bounds.Width, System.Math.Max(0, (_initLeft + difference.X)))); + } + } + + protected override void Toggle() + { + if ((_switchKnob != null) && (!_switchKnob.IsPointerOver)) + base.Toggle(); + } + + protected void UpdateKnobPos(bool value) + { + if ((_switchKnob != null) && (_knobsPanel != null)) + { + if (value) + Canvas.SetLeft(_knobsPanel, _switchKnob.Bounds.Width); + else + Canvas.SetLeft(_knobsPanel, 0); + } + } } } diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 88266ac979..a8e848ec33 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -156,8 +156,8 @@ - - + - + - - @@ -264,10 +259,6 @@ - - From 773f3a3584b218c4c3580e2accd34cb474172d9e Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Tue, 7 Jul 2020 13:51:45 -0400 Subject: [PATCH 17/44] Just code style things --- src/Avalonia.Controls/ToggleSwitch.cs | 35 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index 15e22d4d36..74c2408449 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -10,6 +10,12 @@ namespace Avalonia.Controls /// public class ToggleSwitch : ToggleButton { + Panel _knobsPanel; + Panel _switchKnob; + bool _knobsPanelPressed = false; + Point _switchStartPoint = new Point(); + double _initLeft = -1; + static ToggleSwitch() { OffContentProperty.Changed.AddClassHandler((x, e) => x.OffContentChanged(e)); @@ -17,13 +23,17 @@ namespace Avalonia.Controls IsCheckedProperty.Changed.AddClassHandler((x, e) => { if ((e.NewValue != null) && (e.NewValue is bool val)) + { x.UpdateKnobPos(val); + } }); BoundsProperty.Changed.AddClassHandler((x, e) => { if (x.IsChecked != null) + { x.UpdateKnobPos(x.IsChecked.Value); + } }); } @@ -144,9 +154,6 @@ namespace Avalonia.Controls } - Panel _knobsPanel; - Panel _switchKnob; - bool _knobsPanelPressed = false; protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -159,11 +166,11 @@ namespace Avalonia.Controls _knobsPanel.PointerMoved += KnobsPanel_PointerMoved; if (IsChecked.HasValue) + { UpdateKnobPos(IsChecked.Value); + } } - - Point _switchStartPoint = new Point(); - double _initLeft = -1; + private void KnobsPanel_PointerPressed(object sender, Input.PointerPressedEventArgs e) { _switchStartPoint = e.GetPosition(_switchKnob); @@ -178,16 +185,22 @@ namespace Avalonia.Controls { bool shouldBecomeChecked = Canvas.GetLeft(_knobsPanel) >= (_switchKnob.Bounds.Width / 2); _knobsPanel.ClearValue(Canvas.LeftProperty); - + PseudoClasses.Set(":dragging", false); if (shouldBecomeChecked == IsChecked) + { UpdateKnobPos(shouldBecomeChecked); + } else + { IsChecked = shouldBecomeChecked; + } } else + { base.Toggle(); + } _isDragging = false; @@ -208,14 +221,18 @@ namespace Avalonia.Controls } if (_isDragging) + { Canvas.SetLeft(_knobsPanel, System.Math.Min(_switchKnob.Bounds.Width, System.Math.Max(0, (_initLeft + difference.X)))); + } } } protected override void Toggle() { if ((_switchKnob != null) && (!_switchKnob.IsPointerOver)) + { base.Toggle(); + } } protected void UpdateKnobPos(bool value) @@ -223,9 +240,13 @@ namespace Avalonia.Controls if ((_switchKnob != null) && (_knobsPanel != null)) { if (value) + { Canvas.SetLeft(_knobsPanel, _switchKnob.Bounds.Width); + } else + { Canvas.SetLeft(_knobsPanel, 0); + } } } } From 85c63914ed588da026a9b686fcbb45406de8c303 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 15:25:10 -0300 Subject: [PATCH 18/44] fix relative panel measure / arrange. --- src/Avalonia.Controls/RelativePanel.cs | 32 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 8ba36887ba..cf7fa212fd 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -81,7 +81,7 @@ namespace Avalonia.Controls { foreach (var item in CalculateLocations(finalSize)) item.Item1.Arrange(item.Item2); - return base.ArrangeOverride(finalSize); + return finalSize; } private IEnumerable> CalculateLocations(Size finalSize) @@ -207,6 +207,10 @@ namespace Avalonia.Controls rect[0] = finalSize.Width - rect[2] - child.DesiredSize.Width; valueChanged = true; } + else + { + rect[0] = 0; + } } } //Calculate top side @@ -239,6 +243,10 @@ namespace Avalonia.Controls rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; valueChanged = true; } + else + { + rect[1] = 0; + } } } //Calculate right side @@ -278,6 +286,10 @@ namespace Avalonia.Controls rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; valueChanged = true; } + else + { + rect[2] = child.DesiredSize.Width; + } } } //Calculate bottom side @@ -317,6 +329,10 @@ namespace Avalonia.Controls rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; valueChanged = true; } + else + { + rect[3] = child.DesiredSize.Height; + } } } //Calculate horizontal alignment @@ -410,18 +426,16 @@ namespace Avalonia.Controls private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { var dependency = child.GetValue(property); - - if (dependency == null) - return null; - if (dependency is Layoutable) + + if (dependency is Layoutable layoutable) { - if (Children.Contains((ILayoutable)dependency)) - return (Layoutable)dependency; + if (Children.Contains((ILayoutable)layoutable)) + return layoutable; + throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); } - //return null; - throw new ArgumentException("RelativePanel error: Value must be of type ILayoutable"); + return null; } } } From c354bc50dbe3ab13e9a40047d7e15f8b12451fc1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 15:25:36 -0300 Subject: [PATCH 19/44] relative panel should clip to bounds by default. --- src/Avalonia.Controls/RelativePanel.AttachedProperties.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index 50d2ccbc73..ffe6d007aa 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -17,6 +17,8 @@ namespace Avalonia.Controls static RelativePanel() { + ClipToBoundsProperty.OverrideDefaultValue(true); + AboveProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); AlignBottomWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); AlignBottomWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); From 45d543775e3a4068079fbd5b5d49f06ae7569730 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 15:31:01 -0300 Subject: [PATCH 20/44] cleanup --- src/Avalonia.Controls/RelativePanel.AttachedProperties.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index ffe6d007aa..1524626133 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text; -using Avalonia.Layout; +using Avalonia.Layout; namespace Avalonia.Controls { From 77edfccd7139f2d7c94c11bad15d318879919e0a Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Tue, 7 Jul 2020 11:40:03 -0700 Subject: [PATCH 21/44] Fixes spelling in README.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6a04c7e31e..19a9a8420d 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ To see the status of some of our features, please see our [Roadmap](https://gith ## 🚀 Getting Started -The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starer guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project). +The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project). Avalonia is delivered via NuGet package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/ From be553738112175b1d291650a87fb32ac8fc7e2b5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 15:52:30 -0300 Subject: [PATCH 22/44] add some unit tests and tidy relative panel --- src/Avalonia.Controls/RelativePanel.cs | 33 +++++++++++++++++-- .../RelativePanelTests.cs | 32 ++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index cf7fa212fd..73f668bbe9 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -60,12 +60,26 @@ namespace Avalonia.Controls { child.Measure(availableSize); } + + Rect bounds = new Rect(); + foreach (var item in CalculateLocations(availableSize)) { if (item.Item2.Size.Width < item.Item1.DesiredSize.Width || item.Item2.Size.Height < item.Item1.DesiredSize.Height) item.Item1.Measure(item.Item2.Size); + + if(item.Item2.Right > bounds.Width) + { + bounds = bounds.WithWidth(item.Item2.Right); + } + + if(item.Item2.Bottom > bounds.Height) + { + bounds = bounds.WithHeight(item.Item2.Bottom); + } } - return base.MeasureOverride(availableSize); + + return bounds.Size; } /// @@ -79,9 +93,24 @@ namespace Avalonia.Controls /// The actual size used. protected override Size ArrangeOverride(Size finalSize) { + Rect bounds = new Rect(); + foreach (var item in CalculateLocations(finalSize)) + { item.Item1.Arrange(item.Item2); - return finalSize; + + if (item.Item2.Right > bounds.Width) + { + bounds = bounds.WithWidth(item.Item2.Right); + } + + if (item.Item2.Bottom > bounds.Height) + { + bounds = bounds.WithHeight(item.Item2.Bottom); + } + } + + return bounds.Size; } private IEnumerable> CalculateLocations(Size finalSize) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs new file mode 100644 index 0000000000..7a2036687e --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -0,0 +1,32 @@ +using Avalonia.Controls.Shapes; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class RelativePanelTests + { + [Fact] + public void Lays_Out_1_Child_Below_the_other() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetAlignLeftWithPanel(rect1 , true); + RelativePanel.SetBelow(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(20, 40), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); + } + } +} From 6e998b290657641a8f8605cdecbb66fb009eb3b8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 7 Jul 2020 22:06:07 +0300 Subject: [PATCH 23/44] Fixed ResolveByNameExtension --- .../ResolveByNameExtension.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs index 33263b6a27..2b5d1ced3a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Controls; +using Avalonia.Data.Core; namespace Avalonia.Markup.Xaml.MarkupExtensions { @@ -15,26 +16,20 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public object ProvideValue(IServiceProvider serviceProvider) { var namescope = serviceProvider.GetService(); - var provideValueTarget = serviceProvider.GetService(); - + var value = namescope.FindAsync(Name); if(value.IsCompleted) - { return value.GetResult(); - } - else - { - value.OnCompleted(() => - { - if(provideValueTarget is AvaloniaObject ao) - { - ao.SetValue(provideValueTarget.TargetProperty as AvaloniaProperty, value); - } - }); - return null; - } + var provideValueTarget = serviceProvider.GetService(); + var target = provideValueTarget.TargetObject; + var property = provideValueTarget.TargetProperty as IPropertyInfo; + + if (property != null) + value.OnCompleted(() => property.Set(target, value.GetResult())); + + return null; } } } From 835e87e8cdb46629666be214505d0cc0a61e588d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 16:27:50 -0300 Subject: [PATCH 24/44] add a failing relative panel test. --- .../RelativePanelTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 7a2036687e..1ab8fe2083 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -28,5 +28,31 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); } + + [Fact] + public void Lays_Out_2nd_Child_Aligned_With_Panel_sits_inside_1st() + { + var rect1 = new Rectangle { Height = 50, Width = 200 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + Children = + { + rect1, rect2 + }, + HorizontalAlignment = Layout.HorizontalAlignment.Left, + VerticalAlignment = Layout.VerticalAlignment.Top + }; + + RelativePanel.SetAlignLeftWithPanel(rect1, true); + RelativePanel.SetAlignBottomWithPanel(rect2, true); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(50, 200), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 50, 200), target.Children[0].Bounds); + Assert.Equal(new Rect(0, 30, 20, 20), target.Children[1].Bounds); + } } } From df1bfd60b5f5c15cc34db6c1cf1f3aa2fbf7ecfc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 16:42:17 -0300 Subject: [PATCH 25/44] skip test. --- tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 1ab8fe2083..feb30200af 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); } - [Fact] + [Fact (Skip = "TODO Implement auto sizing")] public void Lays_Out_2nd_Child_Aligned_With_Panel_sits_inside_1st() { var rect1 = new Rectangle { Height = 50, Width = 200 }; From 4b9cf819badf187bd4cde550e0783a5562528be3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 17:45:06 -0300 Subject: [PATCH 26/44] acceptable relativepanel implementation. --- .../RelativePanel.AttachedProperties.cs | 24 +- src/Avalonia.Controls/RelativePanel.cs | 658 ++++++++---------- .../RelativePanelTests.cs | 26 - 3 files changed, 283 insertions(+), 425 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index 1524626133..91a4f15039 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls static RelativePanel() { ClipToBoundsProperty.OverrideDefaultValue(true); - + AboveProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); AlignBottomWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); AlignBottomWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); @@ -61,7 +61,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AboveProperty = AvaloniaProperty.RegisterAttached("Above", typeof(RelativePanel)); @@ -126,7 +126,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignBottomWithProperty = AvaloniaProperty.RegisterAttached("AlignBottomWith", typeof(RelativePanel)); @@ -190,7 +190,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignHorizontalCenterWithProperty = AvaloniaProperty.RegisterAttached("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel)); @@ -255,7 +255,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignLeftWithProperty = AvaloniaProperty.RegisterAttached("AlignLeftWith"); @@ -320,7 +320,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignRightWithProperty = AvaloniaProperty.RegisterAttached("AlignRightWith"); @@ -381,7 +381,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignTopWithProperty = AvaloniaProperty.RegisterAttached("AlignTopWith"); @@ -434,7 +434,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's horizontal center with.) - + public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value) { obj.SetValue(AlignVerticalCenterWithProperty, value); @@ -465,7 +465,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to position this element below.) - + public static void SetBelow(AvaloniaObject obj, object value) { obj.SetValue(BelowProperty, value); @@ -474,7 +474,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty BelowProperty = AvaloniaProperty.RegisterAttached("Below"); @@ -505,7 +505,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty LeftOfProperty = AvaloniaProperty.RegisterAttached("LeftOf"); @@ -536,7 +536,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty RightOfProperty = AvaloniaProperty.RegisterAttached("RightOf"); } diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 73f668bbe9..d44d39ed90 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -1,470 +1,354 @@ -using System; +/* Ported from https://github.com/ghost1372/HandyControls/blob/develop/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs */ +using System; using System.Collections.Generic; using System.Linq; using Avalonia.Layout; namespace Avalonia.Controls { - /// - /// Defines an area within which you can position and align child objects in relation - /// to each other or the parent panel. - /// - /// - /// Default position - /// By default, any unconstrained element declared as a child of the RelativePanel is given the entire - /// available space and positioned at the(0, 0) coordinates(upper left corner) of the panel.So, if you - /// are positioning a second element relative to an unconstrained element, keep in mind that the second - /// element might get pushed out of the panel. - /// - ///Conflicting relationships - /// - /// If you set multiple relationships that target the same edge of an element, you might have conflicting - /// relationships in your layout as a result.When this happens, the relationships are applied in the - /// following order of priority: - /// • Panel alignment relationships (AlignTopWithPanel, AlignLeftWithPanel, …) are applied first. - /// • Sibling alignment relationships(AlignTopWith, AlignLeftWith, …) are applied second. - /// • Sibling positional relationships(Above, Below, RightOf, LeftOf) are applied last. - /// - /// - /// The panel-center alignment properties(AlignVerticalCenterWith, AlignHorizontalCenterWithPanel, ...) are - /// typically used independently of other constraints and are applied if there is no conflict. - /// - /// - /// The HorizontalAlignment and VerticalAlignment properties on UI elements are applied after relationship - /// properties are evaluated and applied. These properties control the placement of the element within the - /// available size for the element, if the desired size is smaller than the available size. - /// - /// public partial class RelativePanel : Panel { - // Dependency property for storing intermediate arrange state on the children - private static readonly StyledProperty ArrangeStateProperty = - AvaloniaProperty.Register("ArrangeState"); - - /// - /// When overridden in a derived class, measures the size in layout required for - /// child elements and determines a size for the System.Windows.FrameworkElement-derived - /// class. - /// - /// The available size that this element can give to child elements. Infinity can - /// be specified as a value to indicate that the element will size to whatever content - /// is available. - /// - /// - /// The size that this element determines it needs during layout, based on its calculations - /// of child element sizes. - /// + private readonly Graph _childGraph; + + public RelativePanel() => _childGraph = new Graph(); + protected override Size MeasureOverride(Size availableSize) { - foreach (var child in Children.OfType()) + foreach (var child in Children) { - child.Measure(availableSize); + child?.Measure(availableSize); } - Rect bounds = new Rect(); + return availableSize; + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + _childGraph.Reset(arrangeSize); + + foreach (var child in Children.OfType()) + { + if (child == null) + continue; + + var node = _childGraph.AddNode(child); + + node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); + node.AlignTopWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignTopWithProperty, child)); + node.AlignRightWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignRightWithProperty, child)); + node.AlignBottomWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignBottomWithProperty, child)); + + node.LeftOfNode = _childGraph.AddLink(node, GetDependencyElement(LeftOfProperty, child)); + node.AboveNode = _childGraph.AddLink(node, GetDependencyElement(AboveProperty, child)); + node.RightOfNode = _childGraph.AddLink(node, GetDependencyElement(RightOfProperty, child)); + node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child)); + + node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); + node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); + } - foreach (var item in CalculateLocations(availableSize)) + if (_childGraph.CheckCyclic()) { - if (item.Item2.Size.Width < item.Item1.DesiredSize.Width || item.Item2.Size.Height < item.Item1.DesiredSize.Height) - item.Item1.Measure(item.Item2.Size); + throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); + } + + var size = new Size(); - if(item.Item2.Right > bounds.Width) + foreach (var child in Children) + { + if (child.Bounds.Bottom > size.Height) { - bounds = bounds.WithWidth(item.Item2.Right); + size = size.WithHeight(child.Bounds.Bottom); } - if(item.Item2.Bottom > bounds.Height) + if (child.Bounds.Right > size.Width) { - bounds = bounds.WithHeight(item.Item2.Bottom); + size = size.WithWidth(child.Bounds.Right); } } - return bounds.Size; + if (VerticalAlignment == VerticalAlignment.Stretch) + { + size = size.WithHeight(arrangeSize.Height); + } + + if (HorizontalAlignment == HorizontalAlignment.Stretch) + { + size = size.WithWidth(arrangeSize.Width); + } + + return size; } - /// - /// When overridden in a derived class, positions child elements and determines a - /// size for a System.Windows.FrameworkElement derived class. - /// - /// - /// The final area within the parent that this element should use to arrange itself - /// and its children. - /// - /// The actual size used. - protected override Size ArrangeOverride(Size finalSize) + private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { - Rect bounds = new Rect(); + var dependency = child.GetValue(property); - foreach (var item in CalculateLocations(finalSize)) + if (dependency is Layoutable layoutable) { - item.Item1.Arrange(item.Item2); + if (Children.Contains((ILayoutable)layoutable)) + return layoutable; - if (item.Item2.Right > bounds.Width) - { - bounds = bounds.WithWidth(item.Item2.Right); - } + throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); + } - if (item.Item2.Bottom > bounds.Height) - { - bounds = bounds.WithHeight(item.Item2.Bottom); - } - } - - return bounds.Size; + return null; } - private IEnumerable> CalculateLocations(Size finalSize) + private class GraphNode { - //List of margins for each element between the element and panel (left, top, right, bottom) - List arranges = new List(Children.Count); - //First pass aligns all sides that aren't constrained by other elements - int arrangedCount = 0; - foreach (var child in Children.OfType()) + public Point Position { get; set; } + + public bool Arranged { get; set; } + + public Layoutable Element { get; } + + public HashSet OutgoingNodes { get; } + + public GraphNode AlignLeftWithNode { get; set; } + + public GraphNode AlignTopWithNode { get; set; } + + public GraphNode AlignRightWithNode { get; set; } + + public GraphNode AlignBottomWithNode { get; set; } + + public GraphNode LeftOfNode { get; set; } + + public GraphNode AboveNode { get; set; } + + public GraphNode RightOfNode { get; set; } + + public GraphNode BelowNode { get; set; } + + public GraphNode AlignHorizontalCenterWith { get; set; } + + public GraphNode AlignVerticalCenterWith { get; set; } + + public GraphNode(Layoutable element) + { + OutgoingNodes = new HashSet(); + Element = element; + } + } + + private class Graph + { + private readonly Dictionary _nodeDic; + + private Size _arrangeSize; + + public Graph() { - //NaN means the arrange value is not constrained yet for that side - double[] rect = new[] { double.NaN, double.NaN, double.NaN, double.NaN }; - arranges.Add(rect); - child.SetValue(ArrangeStateProperty, rect); + _nodeDic = new Dictionary(); + } - //Align with panels always wins, so do these first, or if no constraints are set at all set margin to 0 + public GraphNode AddLink(GraphNode from, Layoutable to) + { + if (to == null) + return null; - //Left side - if (GetAlignLeftWithPanel(child)) + GraphNode nodeTo; + if (_nodeDic.ContainsKey(to)) { - rect[0] = 0; + nodeTo = _nodeDic[to]; } - else if ( - child.GetValue(AlignLeftWithProperty) == null && - child.GetValue(RightOfProperty) == null && - child.GetValue(AlignHorizontalCenterWithProperty) == null && - !GetAlignHorizontalCenterWithPanel(child)) + else { - if (GetAlignRightWithPanel(child)) - rect[0] = finalSize.Width - child.DesiredSize.Width; - else if (child.GetValue(AlignRightWithProperty) == null && child.GetValue(AlignHorizontalCenterWithProperty) == null && child.GetValue(LeftOfProperty) == null) - rect[0] = 0; //default fallback to 0 + nodeTo = new GraphNode(to); + _nodeDic[to] = nodeTo; } - //Top side - if (GetAlignTopWithPanel(child)) + from.OutgoingNodes.Add(nodeTo); + return nodeTo; + } + + public GraphNode AddNode(Layoutable value) + { + if (!_nodeDic.ContainsKey(value)) { - rect[1] = 0; + var node = new GraphNode(value); + _nodeDic.Add(value, node); + return node; } - else if ( - child.GetValue(AlignTopWithProperty) == null && - child.GetValue(BelowProperty) == null && - child.GetValue(AlignVerticalCenterWithProperty) == null && - !GetAlignVerticalCenterWithPanel(child)) + + return _nodeDic[value]; + } + + public void Reset(Size arrangeSize) + { + _arrangeSize = arrangeSize; + _nodeDic.Clear(); + } + + public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null); + + private bool CheckCyclic(IEnumerable nodes, HashSet set) + { + if (set == null) { - if (GetAlignBottomWithPanel(child)) - rect[1] = finalSize.Height - child.DesiredSize.Height; - else if (child.GetValue(AlignBottomWithProperty) == null && child.GetValue(AlignVerticalCenterWithProperty) == null && child.GetValue(AboveProperty) == null) - rect[1] = 0; //default fallback to 0 + set = new HashSet(); } - //Right side - if (GetAlignRightWithPanel(child)) + foreach (var node in nodes) { - rect[2] = 0; + if (!node.Arranged && node.OutgoingNodes.Count == 0) + { + ArrangeChild(node, true); + continue; + } + + if (node.OutgoingNodes.All(item => item.Arranged)) + { + ArrangeChild(node); + continue; + } + + if (!set.Add(node.Element)) + return true; + + return CheckCyclic(node.OutgoingNodes, set); } - else if (!double.IsNaN(rect[0]) && - child.GetValue(AlignRightWithProperty) == null && - child.GetValue(LeftOfProperty) == null && - child.GetValue(AlignHorizontalCenterWithProperty) == null && - !GetAlignHorizontalCenterWithPanel(child)) + + return false; + } + + private void ArrangeChild(GraphNode node, bool ignoneSibling = false) + { + var child = node.Element; + var childSize = child.DesiredSize; + var childPos = new Point(); + + if (GetAlignHorizontalCenterWithPanel(child)) { - rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; + childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); } - //Bottom side - if (GetAlignBottomWithPanel(child)) - rect[3] = 0; - else if (!double.IsNaN(rect[1]) && - (child.GetValue(AlignBottomWithProperty) == null && - child.GetValue(AboveProperty) == null) && - child.GetValue(AlignVerticalCenterWithProperty) == null && - !GetAlignVerticalCenterWithPanel(child)) + if (GetAlignVerticalCenterWithPanel(child)) { - rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; + childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); } - if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && - !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) - arrangedCount++; - } - int i = 0; - //Run iterative layout passes - while (arrangedCount < Children.Count) - { - bool valueChanged = false; - i = 0; - foreach (var child in Children.OfType()) + var alignLeftWithPanel = GetAlignLeftWithPanel(child); + var alignTopWithPanel = GetAlignTopWithPanel(child); + var alignRightWithPanel = GetAlignRightWithPanel(child); + var alignBottomWithPanel = GetAlignBottomWithPanel(child); + + if (!ignoneSibling) { - double[] rect = arranges[i++]; + if (node.LeftOfNode != null) + { + childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width); + } - if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && - !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) - continue; //Control is fully arranged + if (node.AboveNode != null) + { + childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height); + } - //Calculate left side - if (double.IsNaN(rect[0])) + if (node.RightOfNode != null) { - var alignLeftWith = GetDependencyElement(RelativePanel.AlignLeftWithProperty, child); - if (alignLeftWith != null) - { - double[] r = (double[])alignLeftWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[0])) - { - rect[0] = r[0]; - valueChanged = true; - } - } - else - { - var rightOf = GetDependencyElement(RelativePanel.RightOfProperty, child); - if (rightOf != null) - { - double[] r = (double[])rightOf.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[2])) - { - rect[0] = finalSize.Width - r[2]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[2])) - { - rect[0] = finalSize.Width - rect[2] - child.DesiredSize.Width; - valueChanged = true; - } - else - { - rect[0] = 0; - } - } + childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width); } - //Calculate top side - if (double.IsNaN(rect[1])) + + if (node.BelowNode != null) { - var alignTopWith = GetDependencyElement(RelativePanel.AlignTopWithProperty, child); - if (alignTopWith != null) - { - double[] r = (double[])alignTopWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[1])) - { - rect[1] = r[1]; - valueChanged = true; - } - } - else - { - var below = GetDependencyElement(RelativePanel.BelowProperty, child); - if (below != null) - { - double[] r = (double[])below.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[3])) - { - rect[1] = finalSize.Height - r[3]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[3])) - { - rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; - valueChanged = true; - } - else - { - rect[1] = 0; - } - } + childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height); } - //Calculate right side - if (double.IsNaN(rect[2])) + + if (node.AlignHorizontalCenterWith != null) { - var alignRightWith = GetDependencyElement(RelativePanel.AlignRightWithProperty, child); - if (alignRightWith != null) - { - double[] r = (double[])alignRightWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[2])) - { - rect[2] = r[2]; - if (double.IsNaN(rect[0])) - { - if (child.GetValue(RelativePanel.AlignLeftWithProperty) == null) - { - rect[0] = rect[2] + child.DesiredSize.Width; - valueChanged = true; - } - } - } - } - else - { - var leftOf = GetDependencyElement(RelativePanel.LeftOfProperty, child); - if (leftOf != null) - { - double[] r = (double[])leftOf.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[0])) - { - rect[2] = finalSize.Width - r[0]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[0])) - { - rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; - valueChanged = true; - } - else - { - rect[2] = child.DesiredSize.Width; - } - } + childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X + + (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2); } - //Calculate bottom side - if (double.IsNaN(rect[3])) + + if (node.AlignVerticalCenterWith != null) { - var alignBottomWith = GetDependencyElement(RelativePanel.AlignBottomWithProperty, child); - if (alignBottomWith != null) - { - double[] r = (double[])alignBottomWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[3])) - { - rect[3] = r[3]; - valueChanged = true; - if (double.IsNaN(rect[1])) - { - if (child.GetValue(RelativePanel.AlignTopWithProperty) == null) - { - rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; - } - } - } - } - else - { - var above = GetDependencyElement(RelativePanel.AboveProperty, child); - if (above != null) - { - double[] r = (double[])above.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[1])) - { - rect[3] = finalSize.Height - r[1]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[1])) - { - rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; - valueChanged = true; - } - else - { - rect[3] = child.DesiredSize.Height; - } - } + childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y + + (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2); } - //Calculate horizontal alignment - if (double.IsNaN(rect[0]) && double.IsNaN(rect[2])) + + if (node.AlignLeftWithNode != null) { - var alignHorizontalCenterWith = GetDependencyElement(RelativePanel.AlignHorizontalCenterWithProperty, child); - if (alignHorizontalCenterWith != null) - { - double[] r = (double[])alignHorizontalCenterWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[0]) && !double.IsNaN(r[2])) - { - rect[0] = r[0] + (finalSize.Width - r[2] - r[0]) * .5 - child.DesiredSize.Width * .5; - rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; - valueChanged = true; - } - } - else - { - if (GetAlignHorizontalCenterWithPanel(child)) - { - var roomToSpare = finalSize.Width - child.DesiredSize.Width; - rect[0] = roomToSpare * .5; - rect[2] = roomToSpare * .5; - valueChanged = true; - } - } + childPos = childPos.WithX(node.AlignLeftWithNode.Position.X); } - //Calculate vertical alignment - if (double.IsNaN(rect[1]) && double.IsNaN(rect[3])) + if (node.AlignTopWithNode != null) { - var alignVerticalCenterWith = GetDependencyElement(RelativePanel.AlignVerticalCenterWithProperty, child); - if (alignVerticalCenterWith != null) - { - double[] r = (double[])alignVerticalCenterWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[1]) && !double.IsNaN(r[3])) - { - rect[1] = r[1] + (finalSize.Height - r[3] - r[1]) * .5 - child.DesiredSize.Height * .5; - rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; - valueChanged = true; - } - } - else - { - if (GetAlignVerticalCenterWithPanel(child)) - { - var roomToSpare = finalSize.Height - child.DesiredSize.Height; - rect[1] = roomToSpare * .5; - rect[3] = roomToSpare * .5; - valueChanged = true; - } - } + childPos = childPos.WithY(node.AlignTopWithNode.Position.Y); } + if (node.AlignRightWithNode != null) + { + childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width); + } - //if panel is now fully arranged, increase the counter - if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && - !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) - arrangedCount++; + if (node.AlignBottomWithNode != null) + { + childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height); + } } - if (!valueChanged) + + if (alignLeftWithPanel) { - //If a layout pass didn't increase number of arranged elements, - //there must be a circular dependency - throw new ArgumentException("RelativePanel error: Circular dependency detected. Layout could not complete"); + if (node.AlignRightWithNode != null) + { + childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2); + } + else + { + childPos = childPos.WithX(0); + } } - } - i = 0; - //Arrange iterations complete - Apply the results to the child elements - foreach (var child in Children.OfType()) - { - double[] rect = arranges[i++]; - //Measure child again with the new calculated available size - //this helps for instance textblocks to reflow the text wrapping - //We should probably have done this during the measure step but it would cause a more expensive - //measure+arrange layout cycle - //if(child is TextBlock) - // child.Measure(new Size(Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); - - //if(child is TextBlock tb) - //{ - // tb.ArrangeOverride(new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); - //} - //else - yield return new Tuple(child, new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); - } - } + if (alignTopWithPanel) + { + if (node.AlignBottomWithNode != null) + { + childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2); + } + else + { + childPos = childPos.WithY(0); + } + } - //Gets the element that's referred to in the alignment attached properties - private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) - { - var dependency = child.GetValue(property); + if (alignRightWithPanel) + { + if (alignLeftWithPanel) + { + childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); + } + else if (node.AlignLeftWithNode == null) + { + childPos = childPos.WithX(_arrangeSize.Width - childSize.Width); + } + else + { + childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2); + } + } - if (dependency is Layoutable layoutable) - { - if (Children.Contains((ILayoutable)layoutable)) - return layoutable; + if (alignBottomWithPanel) + { + if (alignTopWithPanel) + { + childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); + } + else if (node.AlignTopWithNode == null) + { + childPos = childPos.WithY(_arrangeSize.Height - childSize.Height); + } + else + { + childPos = childPos.WithY((_arrangeSize.Height + node.AlignLeftWithNode.Position.Y - childSize.Height) / 2); + } + } - throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); + child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height)); + node.Position = childPos; + node.Arranged = true; } - - return null; } } } diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index feb30200af..7a2036687e 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -28,31 +28,5 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); } - - [Fact (Skip = "TODO Implement auto sizing")] - public void Lays_Out_2nd_Child_Aligned_With_Panel_sits_inside_1st() - { - var rect1 = new Rectangle { Height = 50, Width = 200 }; - var rect2 = new Rectangle { Height = 20, Width = 20 }; - - var target = new RelativePanel - { - Children = - { - rect1, rect2 - }, - HorizontalAlignment = Layout.HorizontalAlignment.Left, - VerticalAlignment = Layout.VerticalAlignment.Top - }; - - RelativePanel.SetAlignLeftWithPanel(rect1, true); - RelativePanel.SetAlignBottomWithPanel(rect2, true); - target.Measure(new Size(400, 400)); - target.Arrange(new Rect(target.DesiredSize)); - - Assert.Equal(new Size(50, 200), target.Bounds.Size); - Assert.Equal(new Rect(0, 0, 50, 200), target.Children[0].Bounds); - Assert.Equal(new Rect(0, 30, 20, 20), target.Children[1].Bounds); - } } } From 23beddf0fdcf6496515da0ba6c0533becfaa3523 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 7 Jul 2020 17:47:44 -0300 Subject: [PATCH 27/44] restore mainwindow. --- samples/ControlCatalog/MainWindow.xaml | 90 +++++++++++++++++++---- samples/ControlCatalog/MainWindow.xaml.cs | 6 +- 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 03d2c48823..97bd88f5e4 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -13,20 +13,78 @@ TransparencyLevelHint="{Binding TransparencyLevel}" x:Name="MainWindow" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 45e59cc267..d97325ef8d 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -28,10 +28,10 @@ namespace ControlCatalog }; DataContext = new MainWindowViewModel(_notificationArea); - //_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu; + _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu; - //var mainMenu = this.FindControl("MainMenu"); - //mainMenu.AttachedToVisualTree += MenuAttached; + var mainMenu = this.FindControl("MainMenu"); + mainMenu.AttachedToVisualTree += MenuAttached; } public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit"; From 4e53adb9d463a5991d88b3a11111715ed14ecd56 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 8 Jul 2020 07:34:27 +0200 Subject: [PATCH 28/44] Fix MeasureText for empty glyph clusters --- .../Media/TextFormatting/TextFormatterImpl.cs | 7 ++++++- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 4 ++-- .../Rendering/SceneGraph/SceneBuilderTests.cs | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index fa4da1f50e..3ad23f3504 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -377,7 +377,7 @@ namespace Avalonia.Media.TextFormatting { var glyph = glyphRun.GlyphIndices[i]; - var advance = glyphTypeface.GetGlyphAdvance(glyph); + var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; if (currentWidth + advance > availableWidth) { @@ -411,6 +411,11 @@ namespace Avalonia.Media.TextFormatting return glyphRun.Characters.Length; } + if (glyphRun.GlyphClusters.IsEmpty) + { + return glyphCount; + } + var firstCluster = glyphRun.GlyphClusters[0]; var lastCluster = glyphRun.GlyphClusters[glyphCount]; diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index f89e8fc80f..ffe1175567 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -90,9 +90,9 @@ namespace Avalonia.Skia { count++; - buffer.Add('\u200D', cluster); - buffer.Add('\u200C', cluster); + + buffer.Add('\u200D', cluster); } else { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index e219682fa6..42e573c8a5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -34,6 +34,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Background = Brushes.Red, Child = textBlock = new TextBlock { + TextWrapping = TextWrapping.NoWrap, Text = "Hello World", } } From 40711f85970abe31edc66cb4bc5a8a01081d9c4a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 8 Jul 2020 08:19:55 +0200 Subject: [PATCH 29/44] Fix wrapping test --- .../Media/TextFormatting/TextFormatterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 8f47cb856e..4a88b259bc 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -273,7 +273,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var defaultProperties = new GenericTextRunProperties(Typeface.Default); - var paragraphProperties = new GenericTextParagraphProperties(defaultProperties); + var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap); var textSource = new SingleBufferTextSource(text, defaultProperties); From ee709082fb6d4305f742de1475bf3725ccc56c97 Mon Sep 17 00:00:00 2001 From: Splitwirez Date: Wed, 8 Jul 2020 09:23:55 -0400 Subject: [PATCH 30/44] Just more code style things --- src/Avalonia.Controls/ToggleSwitch.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index 74c2408449..c32f2d8102 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -10,11 +10,12 @@ namespace Avalonia.Controls /// public class ToggleSwitch : ToggleButton { - Panel _knobsPanel; - Panel _switchKnob; - bool _knobsPanelPressed = false; - Point _switchStartPoint = new Point(); - double _initLeft = -1; + private Panel _knobsPanel; + private Panel _switchKnob; + private bool _knobsPanelPressed = false; + private Point _switchStartPoint = new Point(); + private double _initLeft = -1; + private bool _isDragging = false; static ToggleSwitch() { @@ -207,7 +208,6 @@ namespace Avalonia.Controls _knobsPanelPressed = false; } - bool _isDragging = false; private void KnobsPanel_PointerMoved(object sender, Input.PointerEventArgs e) { if (_knobsPanelPressed) From e7b3994ce983f506701022232ecd756cfe84bce2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 10:53:04 -0300 Subject: [PATCH 31/44] remove old code. --- .../Avalonia.Markup.Xaml.csproj | 1 - .../Converters/NameReferenceConverter.cs | 82 ------------------- 2 files changed, 83 deletions(-) delete mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index fc0da902f6..3979312ce0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs deleted file mode 100644 index 885648ffde..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/NameReferenceConverter.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Media.Imaging; -using Avalonia.Platform; -using System; -using System.ComponentModel; -using System.Globalization; - -namespace Avalonia.Markup.Xaml.Converters -{ - public class NameReferenceConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - //var nameResolver = (IXamlNameResolver)context.GetService(typeof(IXamlNameResolver)); - //if (nameResolver == null) - //{ - // throw new InvalidOperationException(SR.Get(SRID.MissingNameResolver)); - //} - - //string name = value as string; - //if (String.IsNullOrEmpty(name)) - //{ - // throw new InvalidOperationException(SR.Get(SRID.MustHaveName)); - //} - //object obj = nameResolver.Resolve(name); - //if (obj == null) - //{ - // string[] names = new string[] { name }; - // obj = nameResolver.GetFixupToken(names, true); - //} - return null; //obj; - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - //if (context == null || (context.GetService(typeof(IXamlNameProvider)) as IXamlNameProvider) == null) - //{ - // return false; - //} - - //if (destinationType == typeof(string)) - //{ - // return true; - //} - - return base.CanConvertTo(context, destinationType); - - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - //if (context == null) - //{ - // throw new ArgumentNullException(nameof(context)); - //} - - //var nameProvider = (IXamlNameProvider)context.GetService(typeof(IXamlNameProvider)); - //if (nameProvider == null) - //{ - // throw new InvalidOperationException(SR.Get(SRID.MissingNameProvider)); - //} - - //return nameProvider.GetName(value); - return null; - } - } -} From ecdc9d01860a84cfd52d84b40606f1eb07ca5bdb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 11:22:01 -0300 Subject: [PATCH 32/44] add nullable and refactor. --- .../RelativePanel.AttachedProperties.cs | 9 ++- src/Avalonia.Controls/RelativePanel.cs | 37 ++++++------- .../ResolveByNameExtension.cs | 11 ++-- ...lIlResolveByNameMarkupExtensionReplacer.cs | 55 ++++++++----------- 4 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index 91a4f15039..f93de5ca15 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -1,14 +1,17 @@ using Avalonia.Layout; +#nullable enable + namespace Avalonia.Controls { public partial class RelativePanel { private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { - var elm = d as Layoutable; - if (elm.Parent is Layoutable) - ((Layoutable)elm.Parent).InvalidateArrange(); + if (d is Layoutable layoutable && layoutable.Parent is Layoutable layoutableParent) + { + layoutableParent.InvalidateArrange(); + } } static RelativePanel() diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index d44d39ed90..d7bc658dd1 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Layout; +#nullable enable + namespace Avalonia.Controls { public partial class RelativePanel : Panel @@ -80,7 +82,7 @@ namespace Avalonia.Controls return size; } - private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) + private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { var dependency = child.GetValue(property); @@ -89,7 +91,7 @@ namespace Avalonia.Controls if (Children.Contains((ILayoutable)layoutable)) return layoutable; - throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); + throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); } return null; @@ -105,25 +107,25 @@ namespace Avalonia.Controls public HashSet OutgoingNodes { get; } - public GraphNode AlignLeftWithNode { get; set; } + public GraphNode? AlignLeftWithNode { get; set; } - public GraphNode AlignTopWithNode { get; set; } + public GraphNode? AlignTopWithNode { get; set; } - public GraphNode AlignRightWithNode { get; set; } + public GraphNode? AlignRightWithNode { get; set; } - public GraphNode AlignBottomWithNode { get; set; } + public GraphNode? AlignBottomWithNode { get; set; } - public GraphNode LeftOfNode { get; set; } + public GraphNode? LeftOfNode { get; set; } - public GraphNode AboveNode { get; set; } + public GraphNode? AboveNode { get; set; } - public GraphNode RightOfNode { get; set; } + public GraphNode? RightOfNode { get; set; } - public GraphNode BelowNode { get; set; } + public GraphNode? BelowNode { get; set; } - public GraphNode AlignHorizontalCenterWith { get; set; } + public GraphNode? AlignHorizontalCenterWith { get; set; } - public GraphNode AlignVerticalCenterWith { get; set; } + public GraphNode? AlignVerticalCenterWith { get; set; } public GraphNode(Layoutable element) { @@ -143,7 +145,7 @@ namespace Avalonia.Controls _nodeDic = new Dictionary(); } - public GraphNode AddLink(GraphNode from, Layoutable to) + public GraphNode? AddLink(GraphNode from, Layoutable? to) { if (to == null) return null; @@ -183,12 +185,9 @@ namespace Avalonia.Controls public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null); - private bool CheckCyclic(IEnumerable nodes, HashSet set) + private bool CheckCyclic(IEnumerable nodes, HashSet? set) { - if (set == null) - { - set = new HashSet(); - } + set ??= new HashSet(); foreach (var node in nodes) { @@ -341,7 +340,7 @@ namespace Avalonia.Controls } else { - childPos = childPos.WithY((_arrangeSize.Height + node.AlignLeftWithNode.Position.Y - childSize.Height) / 2); + childPos = childPos.WithY((_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs index 2b5d1ced3a..8561aa898b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs @@ -2,6 +2,8 @@ using Avalonia.Controls; using Avalonia.Data.Core; +#nullable enable + namespace Avalonia.Markup.Xaml.MarkupExtensions { public class ResolveByNameExtension @@ -13,20 +15,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public string Name { get; } - public object ProvideValue(IServiceProvider serviceProvider) + public object? ProvideValue(IServiceProvider serviceProvider) { - var namescope = serviceProvider.GetService(); + var nameScope = serviceProvider.GetService(); - var value = namescope.FindAsync(Name); + var value = nameScope.FindAsync(Name); if(value.IsCompleted) return value.GetResult(); var provideValueTarget = serviceProvider.GetService(); var target = provideValueTarget.TargetObject; - var property = provideValueTarget.TargetProperty as IPropertyInfo; - if (property != null) + if (provideValueTarget.TargetProperty is IPropertyInfo property) value.OnCompleted(() => property.Set(target, value.GetResult())); return null; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs index f550f82972..1c5e4fd4ce 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs @@ -5,47 +5,40 @@ using XamlX.Ast; using XamlX.Transform; using XamlX.TypeSystem; +#nullable enable namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { class AvaloniaXamlIlResolveByNameMarkupExtensionReplacer : IXamlAstTransformer { public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) { - if (node is XamlAstXamlPropertyValueNode propertyValueNode) + if (!(node is XamlAstXamlPropertyValueNode propertyValueNode)) return node; + + IEnumerable attributes = propertyValueNode.Property.GetClrProperty().CustomAttributes; + + if (propertyValueNode.Property is XamlAstClrProperty referenceNode && + referenceNode.Getter != null) { + attributes = attributes.Concat(referenceNode.Getter.CustomAttributes); + } - IEnumerable attributes = propertyValueNode.Property.GetClrProperty().CustomAttributes; + if (attributes.All(attribute => attribute.Type.FullName != "Avalonia.Controls.ResolveByNameAttribute")) + return node; - if (propertyValueNode.Property is XamlAstClrProperty referenceNode && - referenceNode.Getter != null) - { - attributes = attributes.Concat(referenceNode.Getter.CustomAttributes); - } + if (propertyValueNode.Values.Count != 1 || !(propertyValueNode.Values.First() is XamlAstTextNode)) + return node; - foreach (var attribute in attributes) - { - if (attribute.Type.FullName == "Avalonia.Controls.ResolveByNameAttribute") - { - if (propertyValueNode.Values.Count == 1 && - propertyValueNode.Values.First() is XamlAstTextNode) - { - if (XamlTransformHelpers.TryConvertMarkupExtension(context, new XamlAstObjectNode( - propertyValueNode.Values[0], - new XamlAstClrTypeReference(propertyValueNode.Values[0], - context.GetAvaloniaTypes().ResolveByNameExtension, true)) - { - Arguments = new System.Collections.Generic.List - { - propertyValueNode.Values[0] - } - }, out var extensionNode)) - { - propertyValueNode.Values[0] = extensionNode; - } - } - break; - } - } + var newNode = new XamlAstObjectNode( + propertyValueNode.Values[0], + new XamlAstClrTypeReference(propertyValueNode.Values[0], + context.GetAvaloniaTypes().ResolveByNameExtension, true)) + { + Arguments = new List { propertyValueNode.Values[0] } + }; + + if (XamlTransformHelpers.TryConvertMarkupExtension(context, newNode, out var extensionNode)) + { + propertyValueNode.Values[0] = extensionNode; } return node; From b64ca7d5b7ed0af80828fd5fc1d6ba463fbfe6d1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 11:39:28 -0300 Subject: [PATCH 33/44] add more unit tests for relative panel --- .../RelativePanelTests.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 7a2036687e..4248e643eb 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -6,13 +6,15 @@ namespace Avalonia.Controls.UnitTests public class RelativePanelTests { [Fact] - public void Lays_Out_1_Child_Below_the_other() + public void Lays_Out_1_Child_Next_the_other() { var rect1 = new Rectangle { Height = 20, Width = 20 }; var rect2 = new Rectangle { Height = 20, Width = 20 }; var target = new RelativePanel { + VerticalAlignment = Layout.VerticalAlignment.Top, + HorizontalAlignment = Layout.HorizontalAlignment.Left, Children = { rect1, rect2 @@ -20,6 +22,31 @@ namespace Avalonia.Controls.UnitTests }; RelativePanel.SetAlignLeftWithPanel(rect1 , true); + RelativePanel.SetRightOf(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(40, 20), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(20, 0, 20, 20), target.Children[1].Bounds); + } + + public void Lays_Out_1_Child_Below_the_other() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Top, + HorizontalAlignment = Layout.HorizontalAlignment.Left, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetAlignLeftWithPanel(rect1, true); RelativePanel.SetBelow(rect2, rect1); target.Measure(new Size(400, 400)); target.Arrange(new Rect(target.DesiredSize)); From f842c3290c5c39c1624571eaa107db0d7620faf8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 12:47:18 -0300 Subject: [PATCH 34/44] fix xaml transformer. --- .../AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs index 1c5e4fd4ce..c0ac841b7f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs @@ -13,7 +13,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) { if (!(node is XamlAstXamlPropertyValueNode propertyValueNode)) return node; - + + if (!(propertyValueNode.Property is XamlAstClrProperty clrProperty)) return node; + IEnumerable attributes = propertyValueNode.Property.GetClrProperty().CustomAttributes; if (propertyValueNode.Property is XamlAstClrProperty referenceNode && From eb3c4cd008067710c2e25d63b1777bb8d1539985 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 13:28:59 -0300 Subject: [PATCH 35/44] add relative panel page to control catalog. --- samples/ControlCatalog/MainView.xaml | 1 + .../Pages/RelativePanelPage.axaml | 60 +++++++++++++++++++ .../Pages/RelativePanelPage.axaml.cs | 19 ++++++ 3 files changed, 80 insertions(+) create mode 100644 samples/ControlCatalog/Pages/RelativePanelPage.axaml create mode 100644 samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index d812818ed8..fa4fd7dd07 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -56,6 +56,7 @@ + diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml b/samples/ControlCatalog/Pages/RelativePanelPage.axaml new file mode 100644 index 0000000000..3657d52bd9 --- /dev/null +++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs new file mode 100644 index 0000000000..11d0a5152e --- /dev/null +++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class RelativePanelPage : UserControl + { + public RelativePanelPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} From 195da7581ece92467a8a3b739e8575f94368f9ce Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 13:34:38 -0300 Subject: [PATCH 36/44] fix nits. --- src/Avalonia.Controls/RelativePanel.cs | 2 +- .../XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index d7bc658dd1..033a5559f5 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -1,4 +1,4 @@ -/* Ported from https://github.com/ghost1372/HandyControls/blob/develop/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs */ +/// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index d9138753ab..b920698494 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -44,10 +44,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlTransformInstanceAttachedProperties(), new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers()); InsertAfter( - new AvaloniaXamlIlAvaloniaPropertyResolver(), - new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()); + new AvaloniaXamlIlAvaloniaPropertyResolver()); InsertBefore( + new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(), new AvaloniaXamlIlBindingPathParser(), new AvaloniaXamlIlSelectorTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), From e7e28553986804fd8c32412cc16c733a7d22f3ba Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 8 Jul 2020 13:36:31 -0300 Subject: [PATCH 37/44] nit --- .../XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index b920698494..abff763bb1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -46,15 +46,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertAfter( new AvaloniaXamlIlAvaloniaPropertyResolver()); - InsertBefore( - new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(), + InsertBefore( new AvaloniaXamlIlBindingPathParser(), new AvaloniaXamlIlSelectorTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlPropertyPathTransformer(), new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), - new AvaloniaXamlIlTransitionsTypeMetadataTransformer() + new AvaloniaXamlIlTransitionsTypeMetadataTransformer(), + new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer() ); // After everything else From d3304139bd091a33b57e4db75abdf60ce87af98e Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 9 Jul 2020 07:13:09 +0200 Subject: [PATCH 38/44] Use SkiaSharp V2 --- build/HarfBuzzSharp.props | 4 ++-- build/SkiaSharp.props | 4 ++-- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props index 873048ef21..88c4d36282 100644 --- a/build/HarfBuzzSharp.props +++ b/build/HarfBuzzSharp.props @@ -1,6 +1,6 @@  - - + + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 4def44cbd0..a8d9332c57 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - - + + diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index ade659f5eb..d1f8d6a779 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -35,6 +35,7 @@ namespace Avalonia.Skia IsAntialias = true, LcdRenderText = true, SubpixelText = true, + IsLinearText = true, Typeface = glyphTypeface.Typeface, TextSize = (float)fontSize, TextAlign = textAlignment.ToSKTextAlign() From 0555f218d320deb35f4b1baec24a701cf37c5d61 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 9 Jul 2020 19:26:04 +0200 Subject: [PATCH 39/44] Remove obsolete SkiaSharp members --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +++--- .../Gpu/OpenGl/GlRenderTarget.cs | 4 ++-- .../Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 11 +++++----- .../Avalonia.Skia/PlatformRenderInterface.cs | 20 +++++++++---------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a510763f64..d818e683c3 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -629,8 +629,8 @@ namespace Avalonia.Skia var tileTransform = tileBrush.TileMode != TileMode.None - ? SKMatrix.MakeTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y) - : SKMatrix.MakeIdentity(); + ? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y) + : SKMatrix.CreateIdentity(); SKShaderTileMode tileX = tileBrush.TileMode == TileMode.None @@ -655,7 +655,7 @@ namespace Avalonia.Skia SKMatrix.Concat( ref paintTransform, tileTransform, - SKMatrix.MakeScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y))); + SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y))); using (var shader = image.ToShader(tileX, tileY, paintTransform)) { diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 9b73174006..e0b7019672 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -82,10 +82,10 @@ namespace Avalonia.Skia var renderTarget = new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize, - new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat())); + new GRGlFramebufferInfo((uint)fb, SKColorType.Rgba8888.ToGlSizedFormat())); var surface = SKSurface.Create(_grContext, renderTarget, glSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft, - GRPixelConfig.Rgba8888.ToColorType()); + SKColorType.Rgba8888); success = true; return new GlGpuSession(_grContext, renderTarget, surface, glSession); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index de188f42bd..9278de2137 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -15,19 +15,18 @@ namespace Avalonia.Skia using (context.MakeCurrent()) { using (var iface = context.Version.Type == GlProfileType.OpenGL ? - GRGlInterface.AssembleGlInterface((_, proc) => context.GlInterface.GetProcAddress(proc)) : - GRGlInterface.AssembleGlesInterface((_, proc) => context.GlInterface.GetProcAddress(proc))) + GRGlInterface.CreateOpenGl(proc => context.GlInterface.GetProcAddress(proc)) : + GRGlInterface.CreateGles(proc => context.GlInterface.GetProcAddress(proc))) { - _grContext = GRContext.Create(GRBackend.OpenGL, iface); + _grContext = GRContext.CreateGl(iface); if (maxResourceBytes.HasValue) { - _grContext.GetResourceCacheLimits(out var maxResources, out _); - _grContext.SetResourceCacheLimits(maxResources, maxResourceBytes.Value); + _grContext.SetResourceCacheLimit(maxResourceBytes.Value); } } } } - + public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces) { foreach (var surface in surfaces) diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 0bc5dd56ac..66b7fdea13 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -157,12 +157,12 @@ namespace Avalonia.Skia return new WriteableBitmapImpl(size, dpi, format); } - private static readonly SKPaint s_paint = new SKPaint + private static readonly SKFont s_font = new SKFont { - TextEncoding = SKTextEncoding.GlyphId, - IsAntialias = true, - IsStroke = false, - SubpixelText = true + Subpixel = true, + Edging = SKFontEdging.Antialias, + Hinting = SKFontHinting.Full, + LinearMetrics = true }; private static readonly SKTextBlobBuilder s_textBlobBuilder = new SKTextBlobBuilder(); @@ -176,8 +176,8 @@ namespace Avalonia.Skia var typeface = glyphTypeface.Typeface; - s_paint.TextSize = (float)glyphRun.FontRenderingEmSize; - s_paint.Typeface = typeface; + s_font.Size = (float)glyphRun.FontRenderingEmSize; + s_font.Typeface = typeface; SKTextBlob textBlob; @@ -190,7 +190,7 @@ namespace Avalonia.Skia { if (glyphTypeface.IsFixedPitch) { - s_textBlobBuilder.AddRun(s_paint, 0, 0, glyphRun.GlyphIndices.Buffer.Span); + s_textBlobBuilder.AddRun(glyphRun.GlyphIndices.Buffer.Span, s_font); textBlob = s_textBlobBuilder.Build(); @@ -198,7 +198,7 @@ namespace Avalonia.Skia } else { - var buffer = s_textBlobBuilder.AllocateHorizontalRun(s_paint, count, 0); + var buffer = s_textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); var positions = buffer.GetPositionSpan(); @@ -223,7 +223,7 @@ namespace Avalonia.Skia } else { - var buffer = s_textBlobBuilder.AllocatePositionedRun(s_paint, count); + var buffer = s_textBlobBuilder.AllocatePositionedRun(s_font, count); var glyphPositions = buffer.GetPositionSpan(); From af8acfce0dbab0864affb5bc8ee4e45bc8c9f4a9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 9 Jul 2020 21:49:34 +0200 Subject: [PATCH 40/44] Prevent crashes caused by null locked framebuffer. --- src/Windows/Avalonia.Win32/FramebufferManager.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 87c5a1bb02..f4c3a80799 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -17,16 +17,18 @@ namespace Avalonia.Win32 public ILockedFramebuffer Lock() { - UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); - var width = rc.right - rc.left; - var height = rc.bottom - rc.top; - if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height) && width > 0 && height > 0) + UnmanagedMethods.GetClientRect(_hwnd, out var rc); + + var width = Math.Max(1, rc.right - rc.left); + var height = Math.Max(1, rc.bottom - rc.top); + + if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height)) { _fb?.Deallocate(); _fb = null; _fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height)); } + return _fb; } } From 1d94f80f7f4392479e1311992db610d89d79100d Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 9 Jul 2020 23:00:46 +0200 Subject: [PATCH 41/44] Dispose window framebuffers. --- src/Shared/PlatformSupport/StandardRuntimePlatform.cs | 10 +++++++--- src/Windows/Avalonia.Win32/FramebufferManager.cs | 8 +++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index 03832d3063..d574732e3d 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -86,11 +86,15 @@ namespace Avalonia.Shared.PlatformSupport #if DEBUG if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId) { - lock(_lock) + lock (_lock) + { if (!IsDisposed) + { Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: " - + Environment.StackTrace - + "\n\nBlob created by " + _backtrace); + + Environment.StackTrace + + "\n\nBlob created by " + _backtrace); + } + } } #endif DoDispose(); diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index f4c3a80799..6969a49dad 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -5,7 +5,7 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - class FramebufferManager : IFramebufferPlatformSurface + class FramebufferManager : IFramebufferPlatformSurface, IDisposable { private readonly IntPtr _hwnd; private WindowFramebuffer _fb; @@ -31,5 +31,11 @@ namespace Avalonia.Win32 return _fb; } + + public void Dispose() + { + _fb?.Deallocate(); + _fb = null; + } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6e9cb81b11..0ee1342d27 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -389,6 +389,8 @@ namespace Avalonia.Win32 UnregisterClass(_className, GetModuleHandle(null)); _className = null; } + + _framebuffer.Dispose(); } public void Invalidate(Rect rect) From 5d8f54ac42419ea23161a1a53a8948360acdaf6a Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 9 Jul 2020 23:12:05 +0200 Subject: [PATCH 42/44] Fix hardcoded thumb min length. --- src/Avalonia.Controls/Primitives/Track.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index c91adaa26e..29e7f28b44 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -353,6 +353,15 @@ namespace Avalonia.Controls.Primitives var trackLength = isVertical ? arrangeSize.Height : arrangeSize.Width; double thumbMinLength = 10; + StyledProperty minLengthProperty = isVertical ? MinHeightProperty : MinWidthProperty; + + var thumb = Thumb; + + if (thumb != null && thumb.IsSet(minLengthProperty)) + { + thumbMinLength = thumb.GetValue(minLengthProperty); + } + thumbLength = trackLength * viewportSize / extent; CoerceLength(ref thumbLength, trackLength); thumbLength = Math.Max(thumbMinLength, thumbLength); From 1ae75006911c94718653246c20a6f72760bc5945 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 10 Jul 2020 14:17:01 +0300 Subject: [PATCH 43/44] Fix X11 screens implementation for some old/weird setups --- src/Avalonia.X11/X11Screens.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index e247a4241a..af6d2674b6 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -48,9 +48,13 @@ namespace Avalonia.X11 var pwa = (IntPtr*)prop; var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32()); - - foreach (var s in screens) + + foreach (var s in screens) + { s.WorkingArea = s.Bounds.Intersect(wa); + if (s.WorkingArea.Width <= 0 || s.WorkingArea.Height <= 0) + s.WorkingArea = s.Bounds; + } XFree(prop); return screens; @@ -134,8 +138,14 @@ namespace Avalonia.X11 settings.GlobalScaleFactor) }); } - - Screens = new[] {new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)}; + else + { + Screens = new[] + { + new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, + settings.GlobalScaleFactor) + }; + } } public X11Screen[] Screens { get; } From fa72f9f209ea9cf2474e30a7c8f7943e63f403bd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 19:25:38 +0200 Subject: [PATCH 44/44] Make ncrunch work again. --- Avalonia.v3.ncrunchsolution | 1 + 1 file changed, 1 insertion(+) diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution index a2208a9a91..bef7e45524 100644 --- a/Avalonia.v3.ncrunchsolution +++ b/Avalonia.v3.ncrunchsolution @@ -3,6 +3,7 @@ tests\TestFiles\**.* src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll + src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll True .ncrunch