From 2e813c867d85344e6f03d5b8a224bacd22a443bf Mon Sep 17 00:00:00 2001 From: susloparov Date: Fri, 11 Nov 2016 23:16:06 +0700 Subject: [PATCH 1/3] Removed Orientation Property from GridSplitter It's possible to unambiguously detect orientation based on Column/RowDefenitions or other elements. --- src/Avalonia.Controls/GridSplitter.cs | 93 ++++++++++--------- .../GridSplitterTests.cs | 4 - 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 4b8ef06400..982cb7cbb6 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.VisualTree; @@ -18,45 +19,15 @@ namespace Avalonia.Controls /// public class GridSplitter : Thumb { - /// - /// Defines the property. - /// - public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation)); + private List _definitions; protected Grid _grid; - private DefinitionBase _prevDefinition; - private DefinitionBase _nextDefinition; - private List _definitions; - - /// - /// Gets or sets the orientation of the GridsSlitter. - /// - /// - /// if null, it's inferred from column/row definition (should be auto). - /// - public Orientation Orientation { - get - { - return GetValue(OrientationProperty); - } - set - { - SetValue(OrientationProperty, value); - } - } + private Orientation _orientation; - /// - /// Initializes static members of the class. - /// - static GridSplitter() - { - PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical"); - PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal"); - } + private DefinitionBase _prevDefinition; private void GetDeltaConstraints(out double min, out double max) { @@ -74,7 +45,7 @@ namespace Avalonia.Controls protected override void OnDragDelta(VectorEventArgs e) { - var delta = Orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; + var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; double max; double min; GetDeltaConstraints(out min, out max); @@ -137,24 +108,58 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTree(e); _grid = this.GetVisualParent(); - - if (Orientation == Orientation.Vertical) + + _orientation = DetectOrientation(); + + int pos; + if (_orientation == Orientation.Vertical) { Cursor = new Cursor(StandardCursorType.SizeWestEast); - var col = GetValue(Grid.ColumnProperty); + pos = GetValue(Grid.ColumnProperty); _definitions = _grid.ColumnDefinitions.Cast().ToList(); - _prevDefinition = _definitions[col - 1]; - _nextDefinition = _definitions[col + 1]; + PseudoClasses.Add(":vertical"); } else { Cursor = new Cursor(StandardCursorType.SizeNorthSouth); - var row = GetValue(Grid.RowProperty); + pos = GetValue(Grid.RowProperty); _definitions = _grid.RowDefinitions.Cast().ToList(); - _prevDefinition = _definitions[row - 1]; - _nextDefinition = _definitions[row + 1]; + PseudoClasses.Add(":horizontal"); } + _prevDefinition = _definitions[pos - 1]; + _nextDefinition = _definitions[pos + 1]; } - } -} + private Orientation DetectOrientation() + { + if(!_grid.ColumnDefinitions.Any()) + return Orientation.Horizontal; + if (!_grid.RowDefinitions.Any()) + return Orientation.Vertical; + + var col = GetValue(Grid.ColumnProperty); + var row = GetValue(Grid.RowProperty); + var width = _grid.ColumnDefinitions[col].Width; + var height = _grid.RowDefinitions[row].Height; + if (!width.IsAuto && !height.IsAuto) + { + throw new InvalidOperationException("Whether RowDefenition or ColumnDefenition matched with the GridSplitter should have Auto size"); + } + if (width.IsAuto && !height.IsAuto) + { + return Orientation.Vertical; + } + if (!width.IsAuto && height.IsAuto) + { + return Orientation.Horizontal; + } + if (_grid.Children.OfType() // Decision based on other controls in the same column + .Where(c => Grid.GetColumn(c) == col) + .Any(c => c.GetType() != typeof(GridSplitter))) + { + return Orientation.Horizontal; + } + return Orientation.Vertical; + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs index 90b9913141..ea18dfc95f 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs @@ -19,7 +19,6 @@ namespace Avalonia.Controls.UnitTests var control1 = new Border { [Grid.ColumnProperty] = 0 }; var splitter = new GridSplitter { - Orientation = Orientation.Vertical, [Grid.ColumnProperty] = 1, }; var control2 = new Border { [Grid.ColumnProperty] = 2 }; @@ -41,7 +40,6 @@ namespace Avalonia.Controls.UnitTests }; var root = new TestRoot { Child = grid }; - Assert.Equal(splitter.Orientation, Orientation.Vertical); root.Measure(new Size(200, 100)); root.Arrange(new Rect(0, 0, 200, 100)); @@ -71,7 +69,6 @@ namespace Avalonia.Controls.UnitTests var control1 = new Border { [Grid.RowProperty] = 0 }; var splitter = new GridSplitter { - Orientation = Orientation.Horizontal, [Grid.RowProperty] = 1, }; var control2 = new Border { [Grid.RowProperty] = 2 }; @@ -93,7 +90,6 @@ namespace Avalonia.Controls.UnitTests }; var root = new TestRoot { Child = grid }; - Assert.Equal(splitter.Orientation, Orientation.Horizontal); root.Measure(new Size(100, 200)); root.Arrange(new Rect(0, 0, 100, 200)); From d03662a373f0e25efcd71eac84e507f0df0503c7 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 18 Nov 2016 07:52:15 +0700 Subject: [PATCH 2/3] Not throw exception in last or first position. --- src/Avalonia.Controls/GridSplitter.cs | 57 +++++++++++++++------------ 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 982cb7cbb6..8112b2babd 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; - using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.VisualTree; @@ -12,16 +11,16 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { /// - /// Represents the control that redistributes space between columns or rows of a Grid control. + /// Represents the control that redistributes space between columns or rows of a Grid control. /// /// - /// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext. + /// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext. /// public class GridSplitter : Thumb { private List _definitions; - protected Grid _grid; + private Grid _grid; private DefinitionBase _nextDefinition; @@ -31,13 +30,13 @@ namespace Avalonia.Controls private void GetDeltaConstraints(out double min, out double max) { - double prevDefinitionLen = GetActualLength(_prevDefinition); - double prevDefinitionMin = GetMinLength(_prevDefinition); - double prevDefinitionMax = GetMaxLength(_prevDefinition); + var prevDefinitionLen = GetActualLength(_prevDefinition); + var prevDefinitionMin = GetMinLength(_prevDefinition); + var prevDefinitionMax = GetMaxLength(_prevDefinition); - double nextDefinitionLen = GetActualLength(_nextDefinition); - double nextDefinitionMin = GetMinLength(_nextDefinition); - double nextDefinitionMax = GetMaxLength(_nextDefinition); + var nextDefinitionLen = GetActualLength(_nextDefinition); + var nextDefinitionMin = GetMinLength(_nextDefinition); + var nextDefinitionMax = GetMaxLength(_nextDefinition); // Determine the minimum and maximum the columns can be resized min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); @@ -69,26 +68,32 @@ namespace Avalonia.Controls private double GetActualLength(DefinitionBase definition) { + if (definition == null) + return 0; var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; + return columnDefinition?.ActualWidth ?? ((RowDefinition) definition).ActualHeight; } private double GetMinLength(DefinitionBase definition) { + if (definition == null) + return 0; var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; + return columnDefinition?.MinWidth ?? ((RowDefinition) definition).MinHeight; } private double GetMaxLength(DefinitionBase definition) { + if (definition == null) + return 0; var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; + return columnDefinition?.MaxWidth ?? ((RowDefinition) definition).MaxHeight; } private bool IsStar(DefinitionBase definition) { var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; + return columnDefinition?.Width.IsStar ?? ((RowDefinition) definition).Height.IsStar; } private void SetLengthInStars(DefinitionBase definition, double value) @@ -100,7 +105,7 @@ namespace Avalonia.Controls } else { - ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); + ((RowDefinition) definition).Height = new GridLength(value, GridUnitType.Star); } } @@ -111,28 +116,32 @@ namespace Avalonia.Controls _orientation = DetectOrientation(); - int pos; + int defenitionIndex; //row or col if (_orientation == Orientation.Vertical) { Cursor = new Cursor(StandardCursorType.SizeWestEast); - pos = GetValue(Grid.ColumnProperty); _definitions = _grid.ColumnDefinitions.Cast().ToList(); + defenitionIndex = GetValue(Grid.ColumnProperty); PseudoClasses.Add(":vertical"); } else { Cursor = new Cursor(StandardCursorType.SizeNorthSouth); - pos = GetValue(Grid.RowProperty); + defenitionIndex = GetValue(Grid.RowProperty); _definitions = _grid.RowDefinitions.Cast().ToList(); PseudoClasses.Add(":horizontal"); } - _prevDefinition = _definitions[pos - 1]; - _nextDefinition = _definitions[pos + 1]; + + if (defenitionIndex > 0) + _prevDefinition = _definitions[defenitionIndex - 1]; + + if (defenitionIndex < _definitions.Count - 1) + _nextDefinition = _definitions[defenitionIndex + 1]; } private Orientation DetectOrientation() { - if(!_grid.ColumnDefinitions.Any()) + if (!_grid.ColumnDefinitions.Any()) return Orientation.Horizontal; if (!_grid.RowDefinitions.Any()) return Orientation.Vertical; @@ -141,10 +150,6 @@ namespace Avalonia.Controls var row = GetValue(Grid.RowProperty); var width = _grid.ColumnDefinitions[col].Width; var height = _grid.RowDefinitions[row].Height; - if (!width.IsAuto && !height.IsAuto) - { - throw new InvalidOperationException("Whether RowDefenition or ColumnDefenition matched with the GridSplitter should have Auto size"); - } if (width.IsAuto && !height.IsAuto) { return Orientation.Vertical; @@ -155,7 +160,7 @@ namespace Avalonia.Controls } if (_grid.Children.OfType() // Decision based on other controls in the same column .Where(c => Grid.GetColumn(c) == col) - .Any(c => c.GetType() != typeof(GridSplitter))) + .Any(c => c.GetType() != typeof (GridSplitter))) { return Orientation.Horizontal; } From 8704235b667c623e84d686d9e24da313a1fa5acd Mon Sep 17 00:00:00 2001 From: susloparov Date: Mon, 21 Nov 2016 09:05:46 +0700 Subject: [PATCH 3/3] Added test for GridSplitter --- .../GridSplitterTests.cs | 220 ++++++++++++------ 1 file changed, 155 insertions(+), 65 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs index ea18dfc95f..5d622a5fc1 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs @@ -1,112 +1,202 @@ -using Moq; - using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Platform; using Avalonia.UnitTests; + +using Moq; + using Xunit; namespace Avalonia.Controls.UnitTests { public class GridSplitterTests { - [Fact] - public void Vertical_Stays_Within_Constraints() + public GridSplitterTests() { var cursorFactoryImpl = new Mock(); AvaloniaLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object); + } - var control1 = new Border { [Grid.ColumnProperty] = 0 }; - var splitter = new GridSplitter - { - [Grid.ColumnProperty] = 1, - }; - var control2 = new Border { [Grid.ColumnProperty] = 2 }; + [Fact] + public void Detects_Horizontal_Orientation() + { + var grid = new Grid() + { + RowDefinitions = new RowDefinitions("*,Auto,*"), + ColumnDefinitions = new ColumnDefinitions("*,*"), + Children = new Controls() + { + new Border { [Grid.RowProperty] = 0 }, + new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" }, + new Border { [Grid.RowProperty] = 2 } + } + }; - var columnDefinitions = new ColumnDefinitions() - { - new ColumnDefinition(1, GridUnitType.Star) {MinWidth = 10, MaxWidth = 190}, - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(1, GridUnitType.Star) {MinWidth = 80, MaxWidth = 120}, - }; + var root = new TestRoot { Child = grid }; + root.Measure(new Size(100, 300)); + root.Arrange(new Rect(0, 0, 100, 300)); + Assert.Contains(grid.FindControl("splitter").Classes, ":horizontal".Equals); + } + [Fact] + public void Detects_Vertical_Orientation() + { var grid = new Grid() - { - ColumnDefinitions = columnDefinitions, - Children = new Controls() - { - control1, splitter, control2 - } - }; + { + ColumnDefinitions = new ColumnDefinitions("*,Auto,*"), + RowDefinitions = new RowDefinitions("*,*"), + Children = new Controls() + { + new Border { [Grid.ColumnProperty] = 0 }, + new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, + new Border { [Grid.ColumnProperty] = 2 }, + } + }; var root = new TestRoot { Child = grid }; + root.Measure(new Size(100, 300)); + root.Arrange(new Rect(0, 0, 100, 300)); + Assert.Contains(grid.FindControl("splitter").Classes, ":vertical".Equals); + } - root.Measure(new Size(200, 100)); - root.Arrange(new Rect(0, 0, 200, 100)); + [Fact] + public void Detects_With_Both_Auto() + { + var grid = new Grid() + { + ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"), + RowDefinitions = new RowDefinitions("Auto,Auto"), + Children = new Controls() + { + new Border { [Grid.ColumnProperty] = 0 }, + new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, + new Border { [Grid.ColumnProperty] = 2 }, + } + }; - splitter.RaiseEvent(new VectorEventArgs - { - RoutedEvent = Thumb.DragDeltaEvent, - Vector = new Vector(-100,0) - }); - Assert.Equal(columnDefinitions[0].Width, new GridLength(80,GridUnitType.Star)); - Assert.Equal(columnDefinitions[2].Width, new GridLength(120,GridUnitType.Star)); - splitter.RaiseEvent(new VectorEventArgs - { - RoutedEvent = Thumb.DragDeltaEvent, - Vector = new Vector(100, 0) - }); - Assert.Equal(columnDefinitions[0].Width, new GridLength(120, GridUnitType.Star)); - Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star)); + var root = new TestRoot { Child = grid }; + root.Measure(new Size(100, 300)); + root.Arrange(new Rect(0, 0, 100, 300)); + Assert.Contains(grid.FindControl("splitter").Classes, ":vertical".Equals); } [Fact] public void Horizontal_Stays_Within_Constraints() { - var cursorFactoryImpl = new Mock(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object); - var control1 = new Border { [Grid.RowProperty] = 0 }; var splitter = new GridSplitter - { - [Grid.RowProperty] = 1, - }; + { + [Grid.RowProperty] = 1, + }; var control2 = new Border { [Grid.RowProperty] = 2 }; var rowDefinitions = new RowDefinitions() - { - new RowDefinition(1, GridUnitType.Star) {MinHeight = 70, MaxHeight = 110}, - new RowDefinition(GridLength.Auto), - new RowDefinition(1, GridUnitType.Star) { MinHeight = 10, MaxHeight = 140}, - }; + { + new RowDefinition(1, GridUnitType.Star) { MinHeight = 70, MaxHeight = 110 }, + new RowDefinition(GridLength.Auto), + new RowDefinition(1, GridUnitType.Star) { MinHeight = 10, MaxHeight = 140 }, + }; var grid = new Grid() - { - RowDefinitions = rowDefinitions, - Children = new Controls() - { - control1, splitter, control2 - } - }; + { + RowDefinitions = rowDefinitions, + Children = new Controls() + { + control1, splitter, control2 + } + }; var root = new TestRoot { Child = grid }; root.Measure(new Size(100, 200)); root.Arrange(new Rect(0, 0, 100, 200)); splitter.RaiseEvent(new VectorEventArgs - { - RoutedEvent = Thumb.DragDeltaEvent, - Vector = new Vector(0, -100) - }); + { + RoutedEvent = Thumb.DragDeltaEvent, + Vector = new Vector(0, -100) + }); Assert.Equal(rowDefinitions[0].Height, new GridLength(70, GridUnitType.Star)); Assert.Equal(rowDefinitions[2].Height, new GridLength(130, GridUnitType.Star)); splitter.RaiseEvent(new VectorEventArgs - { - RoutedEvent = Thumb.DragDeltaEvent, - Vector = new Vector(0, 100) - }); + { + RoutedEvent = Thumb.DragDeltaEvent, + Vector = new Vector(0, 100) + }); Assert.Equal(rowDefinitions[0].Height, new GridLength(110, GridUnitType.Star)); Assert.Equal(rowDefinitions[2].Height, new GridLength(90, GridUnitType.Star)); } + + [Fact] + public void In_First_Position_Doesnt_Throw_Exception() + { + var grid = new Grid() + { + ColumnDefinitions = new ColumnDefinitions("Auto,*,*"), + RowDefinitions = new RowDefinitions("*,*"), + Children = new Controls() + { + new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" }, + new Border { [Grid.ColumnProperty] = 1 }, + new Border { [Grid.ColumnProperty] = 2 }, + } + }; + + var root = new TestRoot { Child = grid }; + root.Measure(new Size(100, 300)); + root.Arrange(new Rect(0, 0, 100, 300)); + var splitter = grid.FindControl("splitter"); + splitter.RaiseEvent(new VectorEventArgs + { + RoutedEvent = Thumb.DragDeltaEvent, + Vector = new Vector(100, 1000) + }); + } + + [Fact] + public void Vertical_Stays_Within_Constraints() + { + var control1 = new Border { [Grid.ColumnProperty] = 0 }; + var splitter = new GridSplitter + { + [Grid.ColumnProperty] = 1, + }; + var control2 = new Border { [Grid.ColumnProperty] = 2 }; + + var columnDefinitions = new ColumnDefinitions() + { + new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 10, MaxWidth = 190 }, + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 80, MaxWidth = 120 }, + }; + + var grid = new Grid() + { + ColumnDefinitions = columnDefinitions, + Children = new Controls() + { + control1, splitter, control2 + } + }; + + var root = new TestRoot { Child = grid }; + + root.Measure(new Size(200, 100)); + root.Arrange(new Rect(0, 0, 200, 100)); + + splitter.RaiseEvent(new VectorEventArgs + { + RoutedEvent = Thumb.DragDeltaEvent, + Vector = new Vector(-100, 0) + }); + Assert.Equal(columnDefinitions[0].Width, new GridLength(80, GridUnitType.Star)); + Assert.Equal(columnDefinitions[2].Width, new GridLength(120, GridUnitType.Star)); + splitter.RaiseEvent(new VectorEventArgs + { + RoutedEvent = Thumb.DragDeltaEvent, + Vector = new Vector(100, 0) + }); + Assert.Equal(columnDefinitions[0].Width, new GridLength(120, GridUnitType.Star)); + Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star)); + } } } \ No newline at end of file