diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 05cb9dfb28..10d6b55285 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -186,7 +187,7 @@ namespace Avalonia.Controls internal static bool IsUserMinSizePropertyValueValid(object value) { double v = (double)value; - return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + return (!MathUtilities.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); } /// @@ -215,7 +216,7 @@ namespace Avalonia.Controls internal static bool IsUserMaxSizePropertyValueValid(object value) { double v = (double)value; - return (!DoubleUtil.IsNaN(v) && v >= 0.0d); + return (!MathUtilities.IsNaN(v) && v >= 0.0d); } /// @@ -847,7 +848,7 @@ namespace Avalonia.Controls sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); } - bool sharedMinSizeChanged = !DoubleUtil.AreClose(_minSize, sharedMinSize); + bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); // compare accumulated min size with min sizes of the individual definitions for (int i = 0, count = _registry.Count; i < count; ++i) @@ -857,7 +858,7 @@ namespace Avalonia.Controls if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) { // if definition's min size is different, then need to re-measure - if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.MinSize)) + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateMeasure(); @@ -870,7 +871,7 @@ namespace Avalonia.Controls // if measure is valid then also need to check arrange. // Note: definitionBase.SizeCache is volatile but at this point // it contains up-to-date final size - if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.SizeCache)) + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateArrange(); diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 96543fda81..30ca751e57 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -16,6 +16,7 @@ using Avalonia.Controls; using Avalonia.Media; using Avalonia; using System.Collections; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -153,7 +154,7 @@ namespace Avalonia.Controls { child.Measure(constraint); gridDesiredSize = new Size( - Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } @@ -199,156 +200,6 @@ namespace Avalonia.Controls Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); - // Grid classifies cells into four groups depending on - // the column / row type a cell belongs to (number corresponds to - // group number): - // - // Px Auto Star - // +--------+--------+--------+ - // | | | | - // Px | 1 | 1 | 3 | - // | | | | - // +--------+--------+--------+ - // | | | | - // Auto | 1 | 1 | 3 | - // | | | | - // +--------+--------+--------+ - // | | | | - // Star | 4 | 2 | 4 | - // | | | | - // +--------+--------+--------+ - // - // The group number indicates the order in which cells are measured. - // Certain order is necessary to be able to dynamically resolve star - // columns / rows sizes which are used as input for measuring of - // the cells belonging to them. - // - // However, there are cases when topology of a grid causes cyclical - // size dependences. For example: - // - // - // column width="Auto" column width="*" - // +----------------------+----------------------+ - // | | | - // | | | - // | | | - // | | | - // row height="Auto" | | cell 1 2 | - // | | | - // | | | - // | | | - // | | | - // +----------------------+----------------------+ - // | | | - // | | | - // | | | - // | | | - // row height="*" | cell 2 1 | | - // | | | - // | | | - // | | | - // | | | - // +----------------------+----------------------+ - // - // In order to accurately calculate constraint width for "cell 1 2" - // (which is the remaining of grid's available width and calculated - // value of Auto column), "cell 2 1" needs to be calculated first, - // as it contributes to the Auto column's calculated value. - // At the same time in order to accurately calculate constraint - // height for "cell 2 1", "cell 1 2" needs to be calcualted first, - // as it contributes to Auto row height, which is used in the - // computation of Star row resolved height. - // - // to "break" this cyclical dependency we are making (arbitrary) - // decision to treat cells like "cell 2 1" as if they appear in Auto - // rows. And then recalculate them one more time when star row - // heights are resolved. - // - // (Or more strictly) the code below implement the following logic: - // - // +---------+ - // | enter | - // +---------+ - // | - // V - // +----------------+ - // | Measure Group1 | - // +----------------+ - // | - // V - // / - \ - // / \ - // Y / Can \ N - // +--------| Resolve |-----------+ - // | \ StarsV? / | - // | \ / | - // | \ - / | - // V V - // +----------------+ / - \ - // | Resolve StarsV | / \ - // +----------------+ Y / Can \ N - // | +----| Resolve |------+ - // V | \ StarsU? / | - // +----------------+ | \ / | - // | Measure Group2 | | \ - / | - // +----------------+ | V - // | | +-----------------+ - // V | | Measure Group2' | - // +----------------+ | +-----------------+ - // | Resolve StarsU | | | - // +----------------+ V V - // | +----------------+ +----------------+ - // V | Resolve StarsU | | Resolve StarsU | - // +----------------+ +----------------+ +----------------+ - // | Measure Group3 | | | - // +----------------+ V V - // | +----------------+ +----------------+ - // | | Measure Group3 | | Measure Group3 | - // | +----------------+ +----------------+ - // | | | - // | V V - // | +----------------+ +----------------+ - // | | Resolve StarsV | | Resolve StarsV | - // | +----------------+ +----------------+ - // | | | - // | | V - // | | +------------------+ - // | | | Measure Group2'' | - // | | +------------------+ - // | | | - // +----------------------+-------------------------+ - // | - // V - // +----------------+ - // | Measure Group4 | - // +----------------+ - // | - // V - // +--------+ - // | exit | - // +--------+ - // - // where: - // * all [Measure GroupN] - regular Children measure process - - // each cell is measured given contraint size as an input - // and each cell's desired size is accumulated on the - // corresponding column / row; - // * [Measure Group2'] - is when each cell is measured with - // infinit height as a constraint and a cell's desired - // height is ignored; - // * [Measure Groups''] - is when each cell is measured (second - // time during single Grid.MeasureOverride) regularly but its - // returned width is ignored; - // - // This algorithm is believed to be as close to ideal as possible. - // It has the following drawbacks: - // * cells belonging to Group2 can be called to measure twice; - // * iff during second measure a cell belonging to Group2 returns - // desired width greater than desired width returned the first - // time, such a cell is going to be clipped, even though it - // appears in Auto column. - // - MeasureCellsGroup(extData.CellGroup1, constraint, false, false); { @@ -478,7 +329,7 @@ namespace Avalonia.Controls } // update render bound on grid lines renderer visual - GridLinesRenderer gridLinesRenderer = EnsureGridLinesRenderer(); + var gridLinesRenderer = EnsureGridLinesRenderer(); if (gridLinesRenderer != null) { gridLinesRenderer.UpdateRenderBounds(arrangeSize); @@ -624,23 +475,23 @@ namespace Avalonia.Controls // // read and cache child positioning properties // - + // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(Grid.ColumnProperty (child), DefinitionsU.Length - 1); + cell.ColumnIndex = Math.Min(child.GetValue(Grid.ColumnProperty), DefinitionsU.Length - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); + cell.RowIndex = Math.Min(child.GetValue(Grid.RowProperty), DefinitionsV.Length - 1); // read span properties // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(child.GetValue(Grid.ColumnSpanProperty), DefinitionsU.Length - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + cell.RowSpan = Math.Min(child.GetValue(Grid.RowSpanProperty), DefinitionsV.Length - cell.RowIndex); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); @@ -1063,7 +914,7 @@ namespace Avalonia.Controls } var child = Children[cell]; - + if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); @@ -1334,7 +1185,7 @@ namespace Avalonia.Controls // discrepancy (defined below). This avoids discontinuities - small // change in available space resulting in large change to one def's allocation. // 3. Correct handling of large *-values, including Infinity. - + private void ResolveStar( DefinitionBase[] definitions, double availableSize) @@ -1655,7 +1506,7 @@ namespace Avalonia.Controls DefinitionBase[] definitions, double finalSize, bool columns) - { + { int defCount = definitions.Length; int[] definitionIndices = DefinitionIndices; int minCount = 0, maxCount = 0; @@ -2219,12 +2070,12 @@ namespace Avalonia.Controls if (ShowGridLines && (_gridLinesRenderer == null)) { _gridLinesRenderer = new GridLinesRenderer(); - this.AddVisualChild(_gridLinesRenderer); + this.VisualChildren.Add(_gridLinesRenderer); } if ((!ShowGridLines) && (_gridLinesRenderer != null)) { - this.RemoveVisualChild(_gridLinesRenderer); + this.VisualChildren.Remove(_gridLinesRenderer); _gridLinesRenderer = null; } @@ -2287,7 +2138,7 @@ namespace Avalonia.Controls if (child != null) { - Grid grid = VisualTreeHelper.GetParent(child) as Grid; + Grid grid = child.GetVisualParent() as Grid; if (grid != null && grid.ExtData != null && grid.ListenToNotifications) @@ -2514,7 +2365,7 @@ namespace Avalonia.Controls /// true if d == 0. private static bool _IsZero(double d) { - return (Math.Abs(d) < c_epsilon); + return (Math.Abs(d) < double.Epsilon); } /// @@ -2525,7 +2376,7 @@ namespace Avalonia.Controls /// true if d1 == d2 private static bool _AreClose(double d1, double d2) { - return (Math.Abs(d1 - d2) < c_epsilon); + return (Math.Abs(d1 - d2) < double.Epsilon); } /// @@ -2554,15 +2405,6 @@ namespace Avalonia.Controls } } - #endregion Private Properties - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only private Flags _flags; // grid validity / property caches dirtiness flags private GridLinesRenderer _gridLinesRenderer; @@ -2573,15 +2415,6 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. double[] _roundingErrors; - #endregion Private Fields - - //------------------------------------------------------ - // - // Static Fields - // - //------------------------------------------------------ - - #region Static Fields private const double c_epsilon = 1e-5; // used in fp calculations private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop @@ -2594,16 +2427,6 @@ namespace Avalonia.Controls private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); - #endregion Static Fields - - //------------------------------------------------------ - // - // Private Structures / Classes - // - //------------------------------------------------------ - - #region Private Structures Classes - /// /// Extended data instantiated on demand, when grid handles non-trivial case. /// @@ -2656,140 +2479,6 @@ namespace Avalonia.Controls ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride } - #endregion Private Structures Classes - - //------------------------------------------------------ - // - // Properties - // - //------------------------------------------------------ - - #region Properties - - /// - /// ShowGridLines property. This property is used mostly - /// for simplification of visual debuggig. When it is set - /// to true grid lines are drawn to visualize location - /// of grid lines. - /// - public static readonly DependencyProperty ShowGridLinesProperty = - DependencyProperty.Register( - "ShowGridLines", - typeof(bool), - typeof(Grid), - new FrameworkPropertyMetadata( - false, - new PropertyChangedCallback(OnShowGridLinesPropertyChanged))); - - /// - /// Column property. This is an attached property. - /// Grid defines Column property, so that it can be set - /// on any element treated as a cell. Column property - /// specifies child's position with respect to columns. - /// - /// - /// Columns are 0 - based. In order to appear in first column, element - /// should have Column property set to 0. - /// Default value for the property is 0. - /// - [CommonDependencyProperty] - public static readonly DependencyProperty ColumnProperty = - DependencyProperty.RegisterAttached( - "Column", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 0, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueNotNegative)); - - /// - /// Row property. This is an attached property. - /// Grid defines Row, so that it can be set - /// on any element treated as a cell. Row property - /// specifies child's position with respect to rows. - /// - /// Rows are 0 - based. In order to appear in first row, element - /// should have Row property set to 0. - /// Default value for the property is 0. - /// - /// - [CommonDependencyProperty] - public static readonly DependencyProperty RowProperty = - DependencyProperty.RegisterAttached( - "Row", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 0, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueNotNegative)); - - /// - /// ColumnSpan property. This is an attached property. - /// Grid defines ColumnSpan, so that it can be set - /// on any element treated as a cell. ColumnSpan property - /// specifies child's width with respect to columns. - /// Example, ColumnSpan == 2 means that child will span across two columns. - /// - /// - /// Default value for the property is 1. - /// - [CommonDependencyProperty] - public static readonly DependencyProperty ColumnSpanProperty = - DependencyProperty.RegisterAttached( - "ColumnSpan", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 1, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueGreaterThanZero)); - - /// - /// RowSpan property. This is an attached property. - /// Grid defines RowSpan, so that it can be set - /// on any element treated as a cell. RowSpan property - /// specifies child's height with respect to row grid lines. - /// Example, RowSpan == 3 means that child will span across three rows. - /// - /// - /// Default value for the property is 1. - /// - [CommonDependencyProperty] - public static readonly DependencyProperty RowSpanProperty = - DependencyProperty.RegisterAttached( - "RowSpan", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 1, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueGreaterThanZero)); - - - /// - /// IsSharedSizeScope property marks scoping element for shared size. - /// - public static readonly DependencyProperty IsSharedSizeScopeProperty = - DependencyProperty.RegisterAttached( - "IsSharedSizeScope", - typeof(bool), - typeof(Grid), - new FrameworkPropertyMetadata( - false, - new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged))); - - #endregion Properties - - //------------------------------------------------------ - // - // Internal Structures / Classes - // - //------------------------------------------------------ - - #region Internal Structures Classes - /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// @@ -2802,16 +2491,6 @@ namespace Avalonia.Controls Star = 0x04, } - #endregion Internal Structures Classes - - //------------------------------------------------------ - // - // Private Structures / Classes - // - //------------------------------------------------------ - - #region Private Structures Classes - /// /// CellCache stored calculated values of /// 1. attached cell positioning properties; @@ -3331,7 +3010,7 @@ namespace Avalonia.Controls return result; } } - + /// /// Helper to render grid lines. /// @@ -3342,53 +3021,52 @@ namespace Avalonia.Controls /// static GridLinesRenderer() { - s_oddDashPen = new Pen(Brushes.Blue, c_penWidth); - DoubleCollection oddDashArray = new DoubleCollection(); + var oddDashArray = new List(); oddDashArray.Add(c_dashLength); oddDashArray.Add(c_dashLength); - s_oddDashPen.DashStyle = new DashStyle(oddDashArray, 0); - s_oddDashPen.DashCap = PenLineCap.Flat; - s_oddDashPen.Freeze(); + var ds1 = new DashStyle(oddDashArray, 0); + s_oddDashPen = new Pen(Brushes.Blue, + c_penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds1); - s_evenDashPen = new Pen(Brushes.Yellow, c_penWidth); - DoubleCollection evenDashArray = new DoubleCollection(); + var evenDashArray = new List(); evenDashArray.Add(c_dashLength); evenDashArray.Add(c_dashLength); - s_evenDashPen.DashStyle = new DashStyle(evenDashArray, c_dashLength); - s_evenDashPen.DashCap = PenLineCap.Flat; - s_evenDashPen.Freeze(); + var ds2 = new DashStyle(evenDashArray, 0); + s_evenDashPen = new Pen(Brushes.Yellow, + c_penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds2); } /// /// UpdateRenderBounds. /// - /// Size of render bounds - internal void UpdateRenderBounds(Size boundsSize) + public override void Render(DrawingContext drawingContext) { - using (DrawingContext drawingContext = RenderOpen()) + var grid = this.GetVisualParent() as Grid; + + if (grid == null + || grid.ShowGridLines == false) { - Grid grid = VisualTreeHelper.GetParent(this) as Grid; - if (grid == null - || grid.ShowGridLines == false) - { - return; - } + return; + } - for (int i = 1; i < grid.DefinitionsU.Length; ++i) - { - DrawGridLine( - drawingContext, - grid.DefinitionsU[i].FinalOffset, 0.0, - grid.DefinitionsU[i].FinalOffset, boundsSize.Height); - } + for (int i = 1; i < grid.DefinitionsU.Length; ++i) + { + DrawGridLine( + drawingContext, + grid.DefinitionsU[i].FinalOffset, 0.0, + grid.DefinitionsU[i].FinalOffset, lastArrangeSize.Height); + } - for (int i = 1; i < grid.DefinitionsV.Length; ++i) - { - DrawGridLine( - drawingContext, - 0.0, grid.DefinitionsV[i].FinalOffset, - boundsSize.Width, grid.DefinitionsV[i].FinalOffset); - } + for (int i = 1; i < grid.DefinitionsV.Length; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.DefinitionsV[i].FinalOffset, + lastArrangeSize.Width, grid.DefinitionsV[i].FinalOffset); } } @@ -3402,12 +3080,18 @@ namespace Avalonia.Controls double endX, double endY) { - Point start = new Point(startX, startY); - Point end = new Point(endX, endY); + var start = new Point(startX, startY); + var end = new Point(endX, endY); drawingContext.DrawLine(s_oddDashPen, start, end); drawingContext.DrawLine(s_evenDashPen, start, end); } + internal void UpdateRenderBounds(Size arrangeSize) + { + lastArrangeSize = arrangeSize; + this.InvalidateVisual(); + } + private static Size lastArrangeSize; private const double c_dashLength = 4.0; // private const double c_penWidth = 1.0; // private static readonly Pen s_oddDashPen; // first pen to draw dash