Browse Source

Merge branch 'gridsplitter' of https://github.com/susloparovdenis/Perspex into susloparovdenis-gridsplitter

pull/386/head
Steven Kirk 11 years ago
parent
commit
f92f9b27b1
  1. 13
      samples/XamlTestApplicationPcl/Views/MainWindow.paml
  2. 164
      src/Perspex.Controls/GridSplitter.cs
  3. 1
      src/Perspex.Themes.Default/DefaultTheme.paml
  4. 59
      src/Perspex.Themes.Default/GridSplitter.paml
  5. 106
      tests/Perspex.Controls.UnitTests/GridSplitterTests.cs
  6. 1
      tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

13
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@ -221,6 +221,19 @@
<Rectangle Fill="Yellow" Height="25" DockPanel.Dock="Bottom" />
<Rectangle Fill="Pink" />
</DockPanel>
<TextBlock Text="GridSplitter" FontWeight="Medium" FontSize="20" Foreground="#212121" />
<Grid Height="500" Width="500" ColumnDefinitions="*,Auto,*,Auto,*" RowDefinitions="*,Auto,*">
<Border Grid.Column="0" Grid.Row="0" Background="Red" />
<GridSplitter Grid.Column="0" Grid.Row="1" />
<Border Grid.Column="0" Grid.Row="2" Background="Yellow" />
<GridSplitter Grid.Column="1" Grid.RowSpan="3" />
<Border Grid.Column="2" Grid.RowSpan="3" Background="Green" />
<GridSplitter Grid.Column="3" Grid.RowSpan="3" />
<Border Grid.Column="4" Grid.Row="0" Background="Blue" />
<GridSplitter Grid.Column="4" Grid.Row="1" />
<Border Grid.Column="4" Grid.Row="3" Background="Pink" />
</Grid>
</StackPanel>
</ScrollViewer>
</TabItem>

164
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
{
/// <summary>
/// Represents the control that redistributes space between columns or rows of a Grid control.
/// </summary>
/// <remarks>
/// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
/// </remarks>
public class GridSplitter : Thumb
{
private Grid _grid;
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly PerspexProperty<Orientation?> OrientationProperty =
PerspexProperty.Register<GridSplitter, Orientation?>(nameof(Orientation));
protected Grid _grid;
private DefinitionBase _prevDefinition;
private DefinitionBase _nextDefinition;
private bool _isResizingColumns;
private List<DefinitionBase> _definitions;
/// <summary>
/// Initializes a new instance of the <see cref="GridSplitter"/> class.
/// Gets or sets the orientation of the GridsSlitter.
/// </summary>
public GridSplitter()
/// <remarks>
/// if null, it's inferred from column/row definition (should be auto).
/// </remarks>
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)
/// <summary>
/// If orientation is not set, method automatically calculates orientation based column/row auto size.
/// </summary>
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<Grid>();
AutoSetOrientation();
if (Orientation.Value == Perspex.Controls.Orientation.Vertical)
{
Cursor = new Cursor(StandardCursorType.SizeWestEast);
var col = GetValue(Grid.ColumnProperty);
_definitions = _grid.ColumnDefinitions.Cast<DefinitionBase>().ToList();
_prevDefinition = _definitions[col - 1];
_nextDefinition = _definitions[col + 1];
}
else
{
Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
var row = GetValue(Grid.RowProperty);
_definitions = _grid.RowDefinitions.Cast<DefinitionBase>().ToList();
_prevDefinition = _definitions[row - 1];
_nextDefinition = _definitions[row + 1];
}
}
}
}

1
src/Perspex.Themes.Default/DefaultTheme.paml

@ -1,6 +1,5 @@
<Styles xmlns="https://github.com/perspex">
<StyleInclude Source="resource://application/Perspex.Themes.Default/Perspex.Themes.Default.FocusAdorner.paml"/>
<StyleInclude Source="resource://application/Perspex.Themes.Default/Perspex.Themes.Default.Button.paml"/>
<StyleInclude Source="resource://application/Perspex.Themes.Default/Perspex.Themes.Default.Carousel.paml"/>
<StyleInclude Source="resource://application/Perspex.Themes.Default/Perspex.Themes.Default.CheckBox.paml"/>

59
src/Perspex.Themes.Default/GridSplitter.paml

@ -1,8 +1,51 @@
<Style xmlns="https://github.com/perspex" Selector="GridSplitter">
<Setter Property="Width" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter>
</Style>
<Styles xmlns="https://github.com/perspex">
<Style Selector="GridSplitter:vertical">
<Setter Property="Width" Value="6"/>
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Ellipse">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
<Setter Property="Fill" Value="#404040"/>
<Setter Property="Margin" Value="1"/>
</Style>
</StackPanel.Styles>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="GridSplitter:horizontal">
<Setter Property="Height" Value="6"/>
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Ellipse">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
<Setter Property="Fill" Value="#404040"/>
<Setter Property="Margin" Value="1"/>
</Style>
</StackPanel.Styles>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

106
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<IStandardCursorFactory>();
PerspexLocator.CurrentMutable.Bind<IStandardCursorFactory>().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<IStandardCursorFactory>();
PerspexLocator.CurrentMutable.Bind<IStandardCursorFactory>().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));
}
}
}

1
tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -83,6 +83,7 @@
<ItemGroup>
<Compile Include="ClassesTests.cs" />
<Compile Include="EnumerableExtensions.cs" />
<Compile Include="GridSplitterTests.cs" />
<Compile Include="HeaderedItemsControlTests .cs" />
<Compile Include="ControlTests_NameScope.cs" />
<Compile Include="Generators\ItemContainerGeneratorTests.cs" />

Loading…
Cancel
Save