diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 4b8ef06400..8112b2babd 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -11,62 +11,32 @@ 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 { - /// - /// Defines the property. - /// - public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation)); - - protected Grid _grid; + private List _definitions; - private DefinitionBase _prevDefinition; + private Grid _grid; 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) { - 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); @@ -74,7 +44,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); @@ -98,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) @@ -129,7 +105,7 @@ namespace Avalonia.Controls } else { - ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); + ((RowDefinition) definition).Height = new GridLength(value, GridUnitType.Star); } } @@ -137,24 +113,58 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTree(e); _grid = this.GetVisualParent(); - - if (Orientation == Orientation.Vertical) + + _orientation = DetectOrientation(); + + int defenitionIndex; //row or col + if (_orientation == Orientation.Vertical) { Cursor = new Cursor(StandardCursorType.SizeWestEast); - var col = GetValue(Grid.ColumnProperty); _definitions = _grid.ColumnDefinitions.Cast().ToList(); - _prevDefinition = _definitions[col - 1]; - _nextDefinition = _definitions[col + 1]; + defenitionIndex = GetValue(Grid.ColumnProperty); + PseudoClasses.Add(":vertical"); } else { Cursor = new Cursor(StandardCursorType.SizeNorthSouth); - var row = GetValue(Grid.RowProperty); + defenitionIndex = GetValue(Grid.RowProperty); _definitions = _grid.RowDefinitions.Cast().ToList(); - _prevDefinition = _definitions[row - 1]; - _nextDefinition = _definitions[row + 1]; + PseudoClasses.Add(":horizontal"); } + + if (defenitionIndex > 0) + _prevDefinition = _definitions[defenitionIndex - 1]; + + if (defenitionIndex < _definitions.Count - 1) + _nextDefinition = _definitions[defenitionIndex + 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) + { + 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..5d622a5fc1 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs @@ -1,116 +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 - { - Orientation = Orientation.Vertical, - [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 }; - Assert.Equal(splitter.Orientation, Orientation.Vertical); + 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 - { - Orientation = Orientation.Horizontal, - [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 }; - Assert.Equal(splitter.Orientation, Orientation.Horizontal); 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