diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml
index 12440b4106..945b9cb1b0 100644
--- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml
+++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml
@@ -221,6 +221,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs
index 9a0ef0b988..39e235cda3 100644
--- a/src/Perspex.Controls/GridSplitter.cs
+++ b/src/Perspex.Controls/GridSplitter.cs
@@ -2,35 +2,159 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Perspex.Collections;
using Perspex.Controls.Primitives;
using Perspex.Input;
-using Perspex.Rendering;
using Perspex.VisualTree;
namespace Perspex.Controls
{
+ ///
+ /// Represents the control that redistributes space between columns or rows of a Grid control.
+ ///
+ ///
+ /// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
+ ///
public class GridSplitter : Thumb
{
- private Grid _grid;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly PerspexProperty OrientationProperty =
+ PerspexProperty.Register(nameof(Orientation));
+
+ protected Grid _grid;
+
+ private DefinitionBase _prevDefinition;
+
+ private DefinitionBase _nextDefinition;
+
+ private bool _isResizingColumns;
+
+ private List _definitions;
///
- /// Initializes a new instance of the class.
+ /// Gets or sets the orientation of the GridsSlitter.
///
- public GridSplitter()
+ ///
+ /// if null, it's inferred from column/row definition (should be auto).
+ ///
+ public Orientation? Orientation {
+ get
+ {
+ return GetValue(OrientationProperty);
+ }
+ set
+ {
+ SetValue(OrientationProperty, value);
+ }
+ }
+
+ static GridSplitter()
{
- Cursor = new Cursor(StandardCursorType.SizeWestEast);
+ PseudoClass(OrientationProperty, o => o == Perspex.Controls.Orientation.Vertical, ":vertical");
+ PseudoClass(OrientationProperty, o => o == Perspex.Controls.Orientation.Horizontal, ":horizontal");
+ }
+
+ private void GetDeltaConstraints(out double min, out double max)
+ {
+ double prevDefinitionLen = GetActualLength(_prevDefinition);
+ double prevDefinitionMin = GetMinLength(_prevDefinition);
+ double prevDefinitionMax = GetMaxLength(_prevDefinition);
+
+ double nextDefinitionLen = GetActualLength(_nextDefinition);
+ double nextDefinitionMin = GetMinLength(_nextDefinition);
+ double 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);
}
protected override void OnDragDelta(VectorEventArgs e)
{
- int col = GetValue(Grid.ColumnProperty);
+ var delta = Orientation.Value == Perspex.Controls.Orientation.Vertical ? e.Vector.X : e.Vector.Y;
+ double max;
+ double min;
+ GetDeltaConstraints(out min, out max);
+ delta = Math.Min(Math.Max(delta, min), max);
+ foreach (var definition in _definitions)
+ {
+ if (definition == _prevDefinition)
+ {
+ SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta);
+ }
+ else if (definition == _nextDefinition)
+ {
+ SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta);
+ }
+ else if (IsStar(definition))
+ {
+ SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
+ }
+ }
+ }
- if (_grid != null && col > 0)
+ ///
+ /// If orientation is not set, method automatically calculates orientation based column/row auto size.
+ ///
+ private void AutoSetOrientation()
+ {
+ if (Orientation.HasValue)
{
- var size = _grid.ColumnDefinitions[col - 1].ActualWidth + e.Vector.X;
- _grid.ColumnDefinitions[col - 1].Width = new GridLength(
- Math.Max(0, size),
- GridUnitType.Pixel);
+ return;
+ }
+ var column = GetValue(Grid.ColumnProperty);
+ if (column < _grid.ColumnDefinitions.Count && _grid.ColumnDefinitions[column].Width.IsAuto)
+ {
+ Orientation = Perspex.Controls.Orientation.Vertical;
+ return;
+ }
+ var row = GetValue(Grid.RowProperty);
+ if (row < _grid.RowDefinitions.Count && _grid.RowDefinitions[row].Height.IsAuto)
+ {
+ Orientation = Perspex.Controls.Orientation.Horizontal;
+ return;
+ }
+ throw new InvalidOperationException("GridSpliter Should have Orientation, width or height set.");
+ }
+
+ private double GetActualLength(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
+ }
+
+ private double GetMinLength(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight;
+ }
+
+ private double GetMaxLength(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight;
+ }
+
+ private bool IsStar(DefinitionBase definition)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar;
+ }
+
+ private void SetLengthInStars(DefinitionBase definition, double value)
+ {
+ var columnDefinition = definition as ColumnDefinition;
+ if (columnDefinition != null)
+ {
+ columnDefinition.Width = new GridLength(value, GridUnitType.Star);
+ }
+ else
+ {
+ ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star);
}
}
@@ -38,6 +162,24 @@ namespace Perspex.Controls
{
base.OnAttachedToVisualTree(e);
_grid = this.GetVisualParent();
+ AutoSetOrientation();
+ if (Orientation.Value == Perspex.Controls.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];
+ }
+ else
+ {
+ Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
+ var row = GetValue(Grid.RowProperty);
+ _definitions = _grid.RowDefinitions.Cast().ToList();
+ _prevDefinition = _definitions[row - 1];
+ _nextDefinition = _definitions[row + 1];
+ }
}
}
}
+
diff --git a/src/Perspex.Themes.Default/DefaultTheme.paml b/src/Perspex.Themes.Default/DefaultTheme.paml
index e3bcf623f1..9d456d4af9 100644
--- a/src/Perspex.Themes.Default/DefaultTheme.paml
+++ b/src/Perspex.Themes.Default/DefaultTheme.paml
@@ -1,6 +1,5 @@
-
diff --git a/src/Perspex.Themes.Default/GridSplitter.paml b/src/Perspex.Themes.Default/GridSplitter.paml
index ee0757a3f5..6c5f936ec2 100644
--- a/src/Perspex.Themes.Default/GridSplitter.paml
+++ b/src/Perspex.Themes.Default/GridSplitter.paml
@@ -1,8 +1,51 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Perspex.Controls.UnitTests/GridSplitterTests.cs b/tests/Perspex.Controls.UnitTests/GridSplitterTests.cs
new file mode 100644
index 0000000000..2fd2c71034
--- /dev/null
+++ b/tests/Perspex.Controls.UnitTests/GridSplitterTests.cs
@@ -0,0 +1,106 @@
+using Moq;
+
+using Perspex.Controls.Primitives;
+using Perspex.Input;
+using Perspex.Platform;
+
+using Xunit;
+
+namespace Perspex.Controls.UnitTests
+{
+ public class GridSplitterTests
+ {
+ [Fact]
+ public void Vertical_Stays_Within_Constrains()
+ {
+ var cursorFactoryImpl = new Mock();
+ PerspexLocator.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 };
+
+ var columnDefenitions = 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 = columnDefenitions,
+ Children = new Controls()
+ {
+ control1, splitter, control2
+ }
+ };
+
+ 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));
+
+ splitter.RaiseEvent(new VectorEventArgs
+ {
+ RoutedEvent = Thumb.DragDeltaEvent,
+ Vector = new Vector(-100,0)
+ });
+ Assert.Equal(columnDefenitions[0].Width, new GridLength(80,GridUnitType.Star));
+ Assert.Equal(columnDefenitions[2].Width, new GridLength(120,GridUnitType.Star));
+ splitter.RaiseEvent(new VectorEventArgs
+ {
+ RoutedEvent = Thumb.DragDeltaEvent,
+ Vector = new Vector(100, 0)
+ });
+ Assert.Equal(columnDefenitions[0].Width, new GridLength(120, GridUnitType.Star));
+ Assert.Equal(columnDefenitions[2].Width, new GridLength(80, GridUnitType.Star));
+ }
+
+ [Fact]
+ public void Horizontal_Stays_Within_Constrains()
+ {
+ var cursorFactoryImpl = new Mock();
+ PerspexLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object);
+
+ var control1 = new Border { [Grid.RowProperty] = 0 };
+ var splitter = new GridSplitter { [Grid.RowProperty] = 1 };
+ var control2 = new Border { [Grid.RowProperty] = 2 };
+
+ var rowDefenitions = new RowDefinitions()
+ {
+ 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 = rowDefenitions,
+ 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)
+ });
+ Assert.Equal(rowDefenitions[0].Height, new GridLength(70, GridUnitType.Star));
+ Assert.Equal(rowDefenitions[2].Height, new GridLength(130, GridUnitType.Star));
+ splitter.RaiseEvent(new VectorEventArgs
+ {
+ RoutedEvent = Thumb.DragDeltaEvent,
+ Vector = new Vector(0, 100)
+ });
+ Assert.Equal(rowDefenitions[0].Height, new GridLength(110, GridUnitType.Star));
+ Assert.Equal(rowDefenitions[2].Height, new GridLength(90, GridUnitType.Star));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
index 2ec1569e11..5b33890f2a 100644
--- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
+++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
@@ -83,6 +83,7 @@
+