22 changed files with 450 additions and 741 deletions
@ -1,44 +0,0 @@ |
|||||
namespace Perspex.Controls.UnitTests.DockPanelTests |
|
||||
{ |
|
||||
using Xunit; |
|
||||
|
|
||||
|
|
||||
public class AlignerTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void ToStartTest() |
|
||||
{ |
|
||||
Segment container = new Segment(2, 5); |
|
||||
|
|
||||
var aligned = container.AlignToStart(2); |
|
||||
Assert.Equal(new Segment(2, 4), aligned); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ToEndTest() |
|
||||
{ |
|
||||
Segment container = new Segment(2, 5); |
|
||||
|
|
||||
var aligned = container.AlignToEnd(2); |
|
||||
Assert.Equal(new Segment(3, 5), aligned); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ToMiddleTest() |
|
||||
{ |
|
||||
Segment container = new Segment(2, 5); |
|
||||
|
|
||||
var aligned = container.AlignToMiddle(2); |
|
||||
Assert.Equal(new Segment(2.5, 4.5), aligned); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ToMiddleTest2() |
|
||||
{ |
|
||||
Segment container = new Segment(0, 500); |
|
||||
|
|
||||
var aligned = container.AlignToMiddle(200); |
|
||||
Assert.Equal(new Segment(150, 350), aligned); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
namespace Perspex.Controls.UnitTests.DockPanelTests |
|
||||
{ |
|
||||
using System.Collections.Generic; |
|
||||
using Layout; |
|
||||
using Moq; |
|
||||
using Xunit; |
|
||||
|
|
||||
public class LeftDockerTests |
|
||||
{ |
|
||||
private readonly ILayoutable _layoutable; |
|
||||
|
|
||||
public LeftDockerTests() |
|
||||
{ |
|
||||
var layoutableMock = new Mock<ILayoutable>(); |
|
||||
layoutableMock.Setup(l => l.DesiredSize).Returns(new Size(40, 30)); |
|
||||
_layoutable = layoutableMock.Object; |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[MemberData("Source")] |
|
||||
public void Dock(Margins margins, Rect expectedRect) |
|
||||
{ |
|
||||
var sut = new LeftDocker(new Size(100, 50)); |
|
||||
var actualRect = sut.GetDockingRect(_layoutable.DesiredSize, margins, new Alignments(Alignment.Middle, Alignment.Stretch)); |
|
||||
|
|
||||
Assert.Equal(expectedRect, actualRect); |
|
||||
} |
|
||||
|
|
||||
// ReSharper disable once UnusedMember.Global
|
|
||||
public static IEnumerable<object[]> Source => new[] |
|
||||
{ |
|
||||
new object[] { new Margins(), new Rect(0, 0, 40, 50)}, |
|
||||
new object[] { new Margins { VerticalMargin = new Segment(15, 0) }, new Rect(0, 15, 40, 35)}, |
|
||||
new object[] { new Margins { VerticalMargin = new Segment(0, 15) }, new Rect(0, 0, 40, 35)}, |
|
||||
new object[] { new Margins { VerticalMargin = new Segment(20, 15) }, new Rect(0, 20, 40, 15)}, |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -1,40 +0,0 @@ |
|||||
namespace Perspex.Controls.UnitTests.DockPanelTests |
|
||||
{ |
|
||||
using Layout; |
|
||||
using Xunit; |
|
||||
|
|
||||
public class RectAlignerTests |
|
||||
{ |
|
||||
private readonly Rect _container = new Rect(0, 0, 40, 40); |
|
||||
private readonly Size _child = new Size(20, 20); |
|
||||
|
|
||||
[Theory] |
|
||||
[MemberData("TestData")] |
|
||||
public void LefTopTest(Alignment horz, Alignment vert, Rect expectedRect) |
|
||||
{ |
|
||||
var actualRect = _container.AlignChild(_child, horz, vert); |
|
||||
Assert.Equal(expectedRect, actualRect); |
|
||||
} |
|
||||
|
|
||||
// ReSharper disable once UnusedMember.Global
|
|
||||
public static object[] TestData => new object[] |
|
||||
{ |
|
||||
new object[] {Alignment.Start, Alignment.Start, new Rect(0, 0, 20, 20)}, |
|
||||
new object[] {Alignment.Middle, Alignment.Start, new Rect(10, 0, 20, 20)}, |
|
||||
new object[] {Alignment.End, Alignment.Start, new Rect(20, 0, 20, 20)}, |
|
||||
new object[] {Alignment.Stretch, Alignment.Start, new Rect(0, 0, 40, 20)}, |
|
||||
|
|
||||
new object[] {Alignment.Start, Alignment.Middle, new Rect(0, 10, 20, 20)}, |
|
||||
new object[] {Alignment.Middle, Alignment.Middle, new Rect(10, 10, 20, 20)}, |
|
||||
new object[] {Alignment.End, Alignment.Middle, new Rect(20, 10, 20, 20)}, |
|
||||
new object[] {Alignment.Stretch, Alignment.Middle, new Rect(0, 10, 40, 20)}, |
|
||||
|
|
||||
new object[] {Alignment.Start, VerticalAlignment.Bottom, new Rect(0, 20, 20, 20)}, |
|
||||
new object[] {Alignment.Middle, VerticalAlignment.Bottom, new Rect(10, 20, 20, 20)}, |
|
||||
new object[] {Alignment.End, VerticalAlignment.Bottom, new Rect(20, 20, 20, 20)}, |
|
||||
new object[] {Alignment.Stretch, VerticalAlignment.Bottom, new Rect(0, 20, 40, 20)}, |
|
||||
|
|
||||
new object[] {Alignment.Stretch, VerticalAlignment.Stretch, new Rect(0, 0, 40, 40)}, |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
namespace Perspex.Controls.UnitTests.DockPanelTests |
|
||||
{ |
|
||||
using System.Collections.Generic; |
|
||||
using Layout; |
|
||||
using Moq; |
|
||||
using Xunit; |
|
||||
|
|
||||
public class RightDockerTests |
|
||||
{ |
|
||||
private readonly ILayoutable _layoutable; |
|
||||
|
|
||||
public RightDockerTests() |
|
||||
{ |
|
||||
var layoutableMock = new Mock<ILayoutable>(); |
|
||||
layoutableMock.Setup(l => l.DesiredSize).Returns(new Size(40, 30)); |
|
||||
_layoutable = layoutableMock.Object; |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[MemberData("Source")] |
|
||||
public void Dock(Margins margins, Rect expectedRect) |
|
||||
{ |
|
||||
var sut = new RightDocker(new Size(100, 50)); |
|
||||
var actualRect = sut.GetDockingRect(_layoutable.DesiredSize, margins, new Alignments(Alignment.Middle, Alignment.Stretch)); |
|
||||
|
|
||||
Assert.Equal(expectedRect, actualRect); |
|
||||
} |
|
||||
|
|
||||
// ReSharper disable once UnusedMember.Global
|
|
||||
public static IEnumerable<object[]> Source => new[] |
|
||||
{ |
|
||||
new object[] { new Margins(), new Rect(60, 0, 40, 50)}, |
|
||||
new object[] { new Margins { VerticalMargin = new Segment(0, 15) }, new Rect(60, 0, 40, 35)}, |
|
||||
new object[] { new Margins { VerticalMargin = new Segment(15, 0) }, new Rect(60, 15, 40, 35)}, |
|
||||
new object[] { new Margins { VerticalMargin = new Segment(20, 15) }, new Rect(60, 20, 40, 15)}, |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,448 @@ |
|||||
|
namespace Perspex.Controls |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Linq; |
||||
|
using Layout; |
||||
|
|
||||
|
public class DockPanel : Panel |
||||
|
{ |
||||
|
public static readonly PerspexProperty<Dock> DockProperty = PerspexProperty.RegisterAttached<DockPanel, Control, Dock>("Dock"); |
||||
|
|
||||
|
static DockPanel() |
||||
|
{ |
||||
|
AffectsArrange(DockProperty); |
||||
|
} |
||||
|
|
||||
|
// ReSharper disable once UnusedMember.Global
|
||||
|
public static Dock GetDock(PerspexObject perspexObject) |
||||
|
{ |
||||
|
return perspexObject.GetValue(DockProperty); |
||||
|
} |
||||
|
|
||||
|
// ReSharper disable once UnusedMember.Global
|
||||
|
public static void SetDock(PerspexObject element, Dock dock) |
||||
|
{ |
||||
|
element.SetValue(DockProperty, dock); |
||||
|
} |
||||
|
|
||||
|
public static readonly PerspexProperty<bool> LastChildFillProperty = PerspexProperty.Register<DockPanel, bool>(nameof(LastChildFillProperty), defaultValue: true); |
||||
|
|
||||
|
public bool LastChildFill |
||||
|
{ |
||||
|
get { return GetValue(LastChildFillProperty); } |
||||
|
set { SetValue(LastChildFillProperty, value); } |
||||
|
} |
||||
|
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
if (!LastChildFill) |
||||
|
{ |
||||
|
return MeasureItemsThatWillBeDocked(availableSize, Children); |
||||
|
} |
||||
|
|
||||
|
var sizeRequiredByDockingItems = MeasureItemsThatWillBeDocked(availableSize, Children.WithoutLast()); |
||||
|
var elementThatWillFill = Children.Last(); |
||||
|
elementThatWillFill.Measure(availableSize - sizeRequiredByDockingItems); |
||||
|
var finalSize = sizeRequiredByDockingItems.Inflate(new Thickness(elementThatWillFill.DesiredSize.Width, elementThatWillFill.DesiredSize.Height)); |
||||
|
return finalSize; |
||||
|
} |
||||
|
|
||||
|
private static Size MeasureItemsThatWillBeDocked(Size availableSize, IEnumerable<IControl> children) |
||||
|
{ |
||||
|
var requiredHorizontalLength = 0D; |
||||
|
var requiredVerticalLength = 0D; |
||||
|
|
||||
|
foreach (var control in children) |
||||
|
{ |
||||
|
control.Measure(availableSize); |
||||
|
|
||||
|
var dock = control.GetValue(DockProperty); |
||||
|
if (IsHorizontal(dock)) |
||||
|
{ |
||||
|
requiredHorizontalLength += control.DesiredSize.Width; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
requiredVerticalLength += control.DesiredSize.Height; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new Size(requiredHorizontalLength, requiredVerticalLength); |
||||
|
} |
||||
|
|
||||
|
private static bool IsHorizontal(Dock dock) |
||||
|
{ |
||||
|
return dock == Dock.Left || dock == Dock.Right; |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
if (!LastChildFill) |
||||
|
{ |
||||
|
return ArrangeAllChildren(finalSize); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return ArrangeChildrenAndFillLastChild(finalSize); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Size ArrangeChildrenAndFillLastChild(Size finalSize) |
||||
|
{ |
||||
|
var docker = new DockingArranger(); |
||||
|
var requiredSize = docker.ArrangeAndGetUsedSize(finalSize, Children.WithoutLast()); |
||||
|
ArrangeToFill(Children.Last(), finalSize, docker.UsedMargin); |
||||
|
return requiredSize; |
||||
|
} |
||||
|
|
||||
|
private Size ArrangeAllChildren(Size finalSize) |
||||
|
{ |
||||
|
return new DockingArranger().ArrangeAndGetUsedSize(finalSize, Children); |
||||
|
} |
||||
|
|
||||
|
private static void ArrangeToFill(ILayoutable layoutable, Size containerSize, Margin margin) |
||||
|
{ |
||||
|
var containerRect = new Rect(new Point(0, 0), containerSize); |
||||
|
var marginsCutout = margin.AsThickness(); |
||||
|
var withoutMargins = containerRect.Deflate(marginsCutout); |
||||
|
|
||||
|
layoutable.Arrange(withoutMargins); |
||||
|
} |
||||
|
|
||||
|
private class DockingArranger |
||||
|
{ |
||||
|
public Margin UsedMargin { get; private set; } |
||||
|
|
||||
|
public Size ArrangeAndGetUsedSize(Size availableSize, IEnumerable<IControl> children) |
||||
|
{ |
||||
|
var leftArranger = new LeftDocker(availableSize); |
||||
|
var rightArranger = new RightDocker(availableSize); |
||||
|
var topArranger = new LeftDocker(availableSize.Swap()); |
||||
|
var bottomArranger = new RightDocker(availableSize.Swap()); |
||||
|
|
||||
|
UsedMargin = new Margin(); |
||||
|
|
||||
|
foreach (var control in children) |
||||
|
{ |
||||
|
Rect dockedRect; |
||||
|
var dock = control.GetValue(DockProperty); |
||||
|
switch (dock) |
||||
|
{ |
||||
|
case Dock.Left: |
||||
|
dockedRect = leftArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); |
||||
|
break; |
||||
|
|
||||
|
case Dock.Top: |
||||
|
UsedMargin.Swap(); |
||||
|
dockedRect = topArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); |
||||
|
UsedMargin.Swap(); |
||||
|
break; |
||||
|
|
||||
|
case Dock.Right: |
||||
|
dockedRect = rightArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); |
||||
|
break; |
||||
|
|
||||
|
case Dock.Bottom: |
||||
|
UsedMargin.Swap(); |
||||
|
dockedRect = bottomArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); |
||||
|
UsedMargin.Swap(); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new InvalidOperationException($"Invalid dock value {dock}"); |
||||
|
} |
||||
|
|
||||
|
control.Arrange(dockedRect); |
||||
|
} |
||||
|
|
||||
|
return availableSize; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class LeftDocker : Docker |
||||
|
{ |
||||
|
public LeftDocker(Size availableSize) : base(availableSize) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments) |
||||
|
{ |
||||
|
var marginsCutout = margin.AsThickness(); |
||||
|
var availableRect = OriginalRect.Deflate(marginsCutout); |
||||
|
var alignedRect = AlignToLeft(availableRect, childSize, alignments.Vertical); |
||||
|
|
||||
|
AccumulatedOffset += childSize.Width; |
||||
|
margin.Horizontal = margin.Horizontal.Offset(childSize.Width, 0); |
||||
|
|
||||
|
return alignedRect; |
||||
|
} |
||||
|
|
||||
|
private static Rect AlignToLeft(Rect availableRect, Size childSize, Alignment verticalAlignment) |
||||
|
{ |
||||
|
return availableRect.AlignChild(childSize, Alignment.Start, verticalAlignment); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class RightDocker : Docker |
||||
|
{ |
||||
|
public RightDocker(Size availableSize) : base(availableSize) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments) |
||||
|
{ |
||||
|
var marginsCutout = margin.AsThickness(); |
||||
|
var withoutMargins = OriginalRect.Deflate(marginsCutout); |
||||
|
var finalRect = withoutMargins.AlignChild(childSize, Alignment.End, alignments.Vertical); |
||||
|
|
||||
|
AccumulatedOffset += childSize.Width; |
||||
|
margin.Horizontal = margin.Horizontal.Offset(0, childSize.Width); |
||||
|
|
||||
|
return finalRect; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private abstract class Docker |
||||
|
{ |
||||
|
protected Docker(Size availableSize) |
||||
|
{ |
||||
|
OriginalRect = new Rect(new Point(0, 0), availableSize); |
||||
|
} |
||||
|
|
||||
|
protected double AccumulatedOffset { get; set; } |
||||
|
|
||||
|
protected Rect OriginalRect { get; } |
||||
|
|
||||
|
public abstract Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class Margin |
||||
|
{ |
||||
|
public Segment Horizontal { get; set; } |
||||
|
public Segment Vertical { get; set; } |
||||
|
} |
||||
|
|
||||
|
public enum Alignment |
||||
|
{ |
||||
|
Stretch, Start, Middle, End, |
||||
|
} |
||||
|
|
||||
|
public static class SegmentMixin |
||||
|
{ |
||||
|
public static Segment AlignToStart(this Segment container, double length) |
||||
|
{ |
||||
|
return new Segment(container.Start, container.Start + length); |
||||
|
} |
||||
|
|
||||
|
public static Segment AlignToEnd(this Segment container, double length) |
||||
|
{ |
||||
|
return new Segment(container.End - length, container.End); |
||||
|
} |
||||
|
|
||||
|
public static Segment AlignToMiddle(this Segment container, double length) |
||||
|
{ |
||||
|
var start = container.Start + (container.Length - length) / 2; |
||||
|
return new Segment(start, start + length); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public struct Alignments |
||||
|
{ |
||||
|
private readonly Alignment _horizontal; |
||||
|
private readonly Alignment _vertical; |
||||
|
|
||||
|
public Alignments(Alignment horizontal, Alignment vertical) |
||||
|
{ |
||||
|
_horizontal = horizontal; |
||||
|
_vertical = vertical; |
||||
|
} |
||||
|
|
||||
|
public Alignment Horizontal => _horizontal; |
||||
|
|
||||
|
public Alignment Vertical => _vertical; |
||||
|
} |
||||
|
|
||||
|
public static class CoordinateMixin |
||||
|
{ |
||||
|
private static Point Swap(this Point p) |
||||
|
{ |
||||
|
return new Point(p.Y, p.X); |
||||
|
} |
||||
|
|
||||
|
public static Size Swap(this Size s) |
||||
|
{ |
||||
|
return new Size(s.Height, s.Width); |
||||
|
} |
||||
|
|
||||
|
public static Rect Swap(this Rect r) |
||||
|
{ |
||||
|
return new Rect(r.Position.Swap(), r.Size.Swap()); |
||||
|
} |
||||
|
|
||||
|
public static Segment Offset(this Segment l, double startOffset, double endOffset) |
||||
|
{ |
||||
|
return new Segment(l.Start + startOffset, l.End + endOffset); |
||||
|
} |
||||
|
|
||||
|
public static void Swap(this Margin m) |
||||
|
{ |
||||
|
var v = m.Vertical; |
||||
|
m.Vertical = m.Horizontal; |
||||
|
m.Horizontal = v; |
||||
|
} |
||||
|
|
||||
|
public static Thickness AsThickness(this Margin margin) |
||||
|
{ |
||||
|
return new Thickness(margin.Horizontal.Start, margin.Vertical.Start, margin.Horizontal.End, margin.Vertical.End); |
||||
|
} |
||||
|
|
||||
|
private static Alignment AsAlignment(this HorizontalAlignment horz) |
||||
|
{ |
||||
|
switch (horz) |
||||
|
{ |
||||
|
case HorizontalAlignment.Stretch: |
||||
|
return Alignment.Stretch; |
||||
|
case HorizontalAlignment.Left: |
||||
|
return Alignment.Start; |
||||
|
case HorizontalAlignment.Center: |
||||
|
return Alignment.Middle; |
||||
|
case HorizontalAlignment.Right: |
||||
|
return Alignment.End; |
||||
|
default: |
||||
|
throw new ArgumentOutOfRangeException(nameof(horz), horz, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Alignment AsAlignment(this VerticalAlignment vert) |
||||
|
{ |
||||
|
switch (vert) |
||||
|
{ |
||||
|
case VerticalAlignment.Stretch: |
||||
|
return Alignment.Stretch; |
||||
|
case VerticalAlignment.Top: |
||||
|
return Alignment.Start; |
||||
|
case VerticalAlignment.Center: |
||||
|
return Alignment.Middle; |
||||
|
case VerticalAlignment.Bottom: |
||||
|
return Alignment.End; |
||||
|
default: |
||||
|
throw new ArgumentOutOfRangeException(nameof(vert), vert, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static Alignments GetAlignments(this ILayoutable layoutable) |
||||
|
{ |
||||
|
return new Alignments(layoutable.HorizontalAlignment.AsAlignment(), layoutable.VerticalAlignment.AsAlignment()); |
||||
|
} |
||||
|
|
||||
|
public static Alignments Swap(this Alignments alignments) |
||||
|
{ |
||||
|
return new Alignments(alignments.Vertical, alignments.Horizontal); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public enum Dock |
||||
|
{ |
||||
|
Left = 0, |
||||
|
Bottom, |
||||
|
Right, |
||||
|
Top |
||||
|
} |
||||
|
|
||||
|
public static class RectMixin |
||||
|
{ |
||||
|
public static Rect AlignChild(this Rect container, Size childSize, Alignment horizontalAlignment, Alignment verticalAlignment) |
||||
|
{ |
||||
|
var horzSegment = container.GetHorizontalCoordinates(); |
||||
|
var vertSegment = container.GetVerticalCoordinates(); |
||||
|
|
||||
|
var horzResult = GetAlignedSegment(childSize.Width, horizontalAlignment, horzSegment); |
||||
|
var vertResult = GetAlignedSegment(childSize.Height, verticalAlignment, vertSegment); |
||||
|
|
||||
|
return FromSegments(horzResult, vertResult); |
||||
|
} |
||||
|
|
||||
|
private static Rect FromSegments(Segment horzSegment, Segment vertSegment) |
||||
|
{ |
||||
|
return new Rect(horzSegment.Start, vertSegment.Start, horzSegment.Length, vertSegment.Length); |
||||
|
} |
||||
|
|
||||
|
private static Segment GetAlignedSegment(double width, Alignment alignment, Segment horzSegment) |
||||
|
{ |
||||
|
switch (alignment) |
||||
|
{ |
||||
|
case Alignment.Start: |
||||
|
return horzSegment.AlignToStart(width); |
||||
|
|
||||
|
case Alignment.Middle: |
||||
|
return horzSegment.AlignToMiddle(width); |
||||
|
|
||||
|
case Alignment.End: |
||||
|
return horzSegment.AlignToEnd(width); |
||||
|
|
||||
|
default: |
||||
|
return new Segment(horzSegment.Start, horzSegment.End); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Segment GetHorizontalCoordinates(this Rect rect) |
||||
|
{ |
||||
|
return new Segment(rect.X, rect.Right); |
||||
|
} |
||||
|
|
||||
|
private static Segment GetVerticalCoordinates(this Rect rect) |
||||
|
{ |
||||
|
return new Segment(rect.Y, rect.Bottom); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public struct Segment |
||||
|
{ |
||||
|
public Segment(double start, double end) |
||||
|
{ |
||||
|
Start = start; |
||||
|
End = end; |
||||
|
} |
||||
|
|
||||
|
public double Start { get; } |
||||
|
public double End { get; } |
||||
|
|
||||
|
public double Length => End - Start; |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"Start: {Start}, End: {End}"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static class EnumerableMixin |
||||
|
{ |
||||
|
private static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right) |
||||
|
{ |
||||
|
int i = 0; |
||||
|
var buffer = new Queue<T>(right + 1); |
||||
|
|
||||
|
foreach (T x in source) |
||||
|
{ |
||||
|
if (i >= left) // Read past left many elements at the start
|
||||
|
{ |
||||
|
buffer.Enqueue(x); |
||||
|
if (buffer.Count > right) // Build a buffer to drop right many elements at the end
|
||||
|
yield return buffer.Dequeue(); |
||||
|
} |
||||
|
else i++; |
||||
|
} |
||||
|
} |
||||
|
public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1) |
||||
|
{ |
||||
|
return source.Shrink(0, n); |
||||
|
} |
||||
|
public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1) |
||||
|
{ |
||||
|
return source.Shrink(n, 0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,21 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public static class LinearMarginMixin |
|
||||
{ |
|
||||
public static Segment AlignToStart(this Segment container, double length) |
|
||||
{ |
|
||||
return new Segment(container.Start, container.Start + length); |
|
||||
} |
|
||||
|
|
||||
public static Segment AlignToEnd(this Segment container, double length) |
|
||||
{ |
|
||||
return new Segment(container.End - length, container.End); |
|
||||
} |
|
||||
|
|
||||
public static Segment AlignToMiddle(this Segment container, double length) |
|
||||
{ |
|
||||
var start = container.Start + (container.Length - length) / 2; |
|
||||
return new Segment(start, start + length); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public enum Alignment |
|
||||
{ |
|
||||
Stretch, |
|
||||
Start, |
|
||||
Middle, |
|
||||
End, |
|
||||
} |
|
||||
} |
|
||||
@ -1,24 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public struct Alignments |
|
||||
{ |
|
||||
private readonly Alignment _horizontal; |
|
||||
private readonly Alignment _vertical; |
|
||||
|
|
||||
public Alignments(Alignment horizontal, Alignment vertical) |
|
||||
{ |
|
||||
_horizontal = horizontal; |
|
||||
_vertical = vertical; |
|
||||
} |
|
||||
|
|
||||
public Alignment Horizontal |
|
||||
{ |
|
||||
get { return _horizontal; } |
|
||||
} |
|
||||
|
|
||||
public Alignment Vertical |
|
||||
{ |
|
||||
get { return _vertical; } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,98 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
using System; |
|
||||
using Layout; |
|
||||
|
|
||||
public static class CoordinateMixin |
|
||||
{ |
|
||||
private static Point Swap(this Point p) |
|
||||
{ |
|
||||
return new Point(p.Y, p.X); |
|
||||
} |
|
||||
|
|
||||
public static Size Swap(this Size s) |
|
||||
{ |
|
||||
return new Size(s.Height, s.Width); |
|
||||
} |
|
||||
|
|
||||
public static Rect Swap(this Rect r) |
|
||||
{ |
|
||||
return new Rect(r.Position.Swap(), r.Size.Swap()); |
|
||||
} |
|
||||
|
|
||||
public static Segment Offset(this Segment l, double startOffset, double endOffset) |
|
||||
{ |
|
||||
return new Segment(l.Start + startOffset, l.End + endOffset); |
|
||||
} |
|
||||
|
|
||||
public static void Swap(this Margins m) |
|
||||
{ |
|
||||
var v = m.VerticalMargin; |
|
||||
m.VerticalMargin = m.HorizontalMargin; |
|
||||
m.HorizontalMargin = v; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public static Thickness AsThickness(this Margins margins) |
|
||||
{ |
|
||||
return new Thickness(margins.HorizontalMargin.Start, margins.VerticalMargin.Start, margins.HorizontalMargin.End, margins.VerticalMargin.End); |
|
||||
} |
|
||||
|
|
||||
private static Alignment AsAlignment(this HorizontalAlignment horz) |
|
||||
{ |
|
||||
switch (horz) |
|
||||
{ |
|
||||
case HorizontalAlignment.Stretch: |
|
||||
return Alignment.Stretch; |
|
||||
case HorizontalAlignment.Left: |
|
||||
return Alignment.Start; |
|
||||
case HorizontalAlignment.Center: |
|
||||
return Alignment.Middle; |
|
||||
case HorizontalAlignment.Right: |
|
||||
return Alignment.End; |
|
||||
default: |
|
||||
throw new ArgumentOutOfRangeException(nameof(horz), horz, null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static Alignment AsAlignment(this VerticalAlignment vert) |
|
||||
{ |
|
||||
switch (vert) |
|
||||
{ |
|
||||
case VerticalAlignment.Stretch: |
|
||||
return Alignment.Stretch; |
|
||||
case VerticalAlignment.Top: |
|
||||
return Alignment.Start; |
|
||||
case VerticalAlignment.Center: |
|
||||
return Alignment.Middle; |
|
||||
case VerticalAlignment.Bottom: |
|
||||
return Alignment.End; |
|
||||
default: |
|
||||
throw new ArgumentOutOfRangeException(nameof(vert), vert, null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static Alignments GetAlignments(this ILayoutable layoutable) |
|
||||
{ |
|
||||
return new Alignments(layoutable.HorizontalAlignment.AsAlignment(), layoutable.VerticalAlignment.AsAlignment()); |
|
||||
} |
|
||||
|
|
||||
public static Alignments Swap(this Alignments alignments) |
|
||||
{ |
|
||||
return new Alignments(alignments.Vertical, alignments.Horizontal); |
|
||||
} |
|
||||
|
|
||||
public static LayoutSizes GetLayoutSizes(this ILayoutable layoutable) |
|
||||
{ |
|
||||
return new LayoutSizes( |
|
||||
new Size(layoutable.Width, layoutable.Height), |
|
||||
new Size(layoutable.MaxWidth, layoutable.MaxHeight), |
|
||||
new Size(layoutable.MinWidth, layoutable.MinHeight)); |
|
||||
} |
|
||||
|
|
||||
public static LayoutSizes Swap(this LayoutSizes l) |
|
||||
{ |
|
||||
return new LayoutSizes(l.Size.Swap(), l.MaxSize.Swap(), l.MinSize.Swap()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public enum Dock |
|
||||
{ |
|
||||
Left = 0, |
|
||||
Bottom, |
|
||||
Right, |
|
||||
Top |
|
||||
} |
|
||||
} |
|
||||
@ -1,129 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Linq; |
|
||||
using Layout; |
|
||||
|
|
||||
// ReSharper disable once UnusedMember.Global
|
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
|
||||
public class DockPanel : Panel |
|
||||
{ |
|
||||
public static readonly PerspexProperty<Dock> DockProperty = PerspexProperty.RegisterAttached<DockPanel, Control, Dock>("Dock"); |
|
||||
|
|
||||
static DockPanel() |
|
||||
{ |
|
||||
AffectsArrange(DockProperty); |
|
||||
} |
|
||||
|
|
||||
public static Dock GetDock(PerspexObject element) |
|
||||
{ |
|
||||
return element.GetValue(DockProperty); |
|
||||
} |
|
||||
|
|
||||
public static void SetDock(PerspexObject element, Dock dock) |
|
||||
{ |
|
||||
element.SetValue(DockProperty, dock); |
|
||||
} |
|
||||
|
|
||||
public static readonly PerspexProperty<bool> LastChildFillProperty = PerspexProperty.Register<DockPanel, bool>(nameof(DataContext), defaultValue: true); |
|
||||
|
|
||||
public bool LastChildFill |
|
||||
{ |
|
||||
get { return GetValue(LastChildFillProperty); } |
|
||||
set { SetValue(LastChildFillProperty, value); } |
|
||||
} |
|
||||
|
|
||||
protected override Size MeasureOverride(Size availableSize) |
|
||||
{ |
|
||||
if (!LastChildFill) |
|
||||
{ |
|
||||
return MeasureItemsThatWillBeDocked(availableSize, Children); |
|
||||
} |
|
||||
|
|
||||
var sizeRequiredByDockingItems = MeasureItemsThatWillBeDocked(availableSize, Children.WithoutLast()); |
|
||||
var elementThatWillFill = Children.Last(); |
|
||||
elementThatWillFill.Measure(availableSize - sizeRequiredByDockingItems); |
|
||||
var finalSize = sizeRequiredByDockingItems.Inflate(new Thickness(elementThatWillFill.DesiredSize.Width, elementThatWillFill.DesiredSize.Height)); |
|
||||
return finalSize; |
|
||||
} |
|
||||
|
|
||||
private static Size MeasureItemsThatWillBeDocked(Size availableSize, IEnumerable<IControl> children) |
|
||||
{ |
|
||||
var requiredHorizontalLength = 0D; |
|
||||
var requiredVerticalLength = 0D; |
|
||||
|
|
||||
foreach (var control in children) |
|
||||
{ |
|
||||
control.Measure(availableSize); |
|
||||
|
|
||||
var dock = control.GetValue(DockProperty); |
|
||||
if (IsHorizontal(dock)) |
|
||||
{ |
|
||||
requiredHorizontalLength += control.DesiredSize.Width; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
requiredVerticalLength += control.DesiredSize.Height; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return new Size(requiredHorizontalLength, requiredVerticalLength); |
|
||||
} |
|
||||
|
|
||||
private static bool IsHorizontal(Dock dock) |
|
||||
{ |
|
||||
return dock == Dock.Left || dock == Dock.Right; |
|
||||
} |
|
||||
|
|
||||
protected override Size ArrangeOverride(Size finalSize) |
|
||||
{ |
|
||||
var docker = new DockingArranger(); |
|
||||
|
|
||||
if (!LastChildFill) |
|
||||
{ |
|
||||
return docker.ArrangeChildren(finalSize, Children); |
|
||||
} |
|
||||
|
|
||||
var requiredSize = docker.ArrangeChildren(finalSize, Children.WithoutLast()); |
|
||||
|
|
||||
ArrangeToFill(finalSize, docker.Margins, Children.Last()); |
|
||||
|
|
||||
return requiredSize; |
|
||||
} |
|
||||
|
|
||||
private static void ArrangeToFill(Size availableSize, Margins margins, ILayoutable layoutable) |
|
||||
{ |
|
||||
var containerRect = new Rect(new Point(0,0), availableSize); |
|
||||
var marginsCutout = margins.AsThickness(); |
|
||||
var withoutMargins = containerRect.Deflate(marginsCutout); |
|
||||
|
|
||||
var finalSize = GetConstrainedSize(layoutable, withoutMargins); |
|
||||
|
|
||||
var finalRect = withoutMargins.AlignChild(finalSize, Alignment.Middle, Alignment.Middle); |
|
||||
|
|
||||
layoutable.Arrange(finalRect); |
|
||||
} |
|
||||
|
|
||||
private static Size GetConstrainedSize(ILayoutable layoutable, Rect withoutMargins) |
|
||||
{ |
|
||||
var width = GetWidth(layoutable.GetLayoutSizes(), withoutMargins); |
|
||||
var height = GetWidth(layoutable.GetLayoutSizes().Swap(), withoutMargins.Swap()); |
|
||||
var finalSize = new Size(width, height); |
|
||||
return finalSize; |
|
||||
} |
|
||||
|
|
||||
private static double GetWidth(LayoutSizes layoutSizes, Rect withoutMargins) |
|
||||
{ |
|
||||
return layoutSizes.IsWidthSpecified |
|
||||
? layoutSizes.Size.Width |
|
||||
: GetConstrainedDimension(withoutMargins.Width, layoutSizes.MaxSize.Width, layoutSizes.MinSize.Width); |
|
||||
} |
|
||||
|
|
||||
private static double GetConstrainedDimension(double toConstrain, double maximum, double minimum) |
|
||||
{ |
|
||||
return Math.Max(Math.Min(toConstrain, maximum), minimum); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,33 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public class Docker |
|
||||
{ |
|
||||
private Size _availableSize; |
|
||||
private double _accumulatedOffset; |
|
||||
private Rect _originalRect; |
|
||||
|
|
||||
protected Docker(Size availableSize) |
|
||||
{ |
|
||||
AvailableSize = availableSize; |
|
||||
OriginalRect = new Rect(new Point(0, 0), AvailableSize); |
|
||||
} |
|
||||
|
|
||||
protected Size AvailableSize |
|
||||
{ |
|
||||
get { return _availableSize; } |
|
||||
set { _availableSize = value; } |
|
||||
} |
|
||||
|
|
||||
protected double AccumulatedOffset |
|
||||
{ |
|
||||
get { return _accumulatedOffset; } |
|
||||
set { _accumulatedOffset = value; } |
|
||||
} |
|
||||
|
|
||||
protected Rect OriginalRect |
|
||||
{ |
|
||||
get { return _originalRect; } |
|
||||
set { _originalRect = value; } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,54 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using Perspex; |
|
||||
using Perspex.Controls; |
|
||||
|
|
||||
internal class DockingArranger |
|
||||
{ |
|
||||
public Margins Margins { get; private set; } |
|
||||
|
|
||||
public Size ArrangeChildren(Size finalSize, IEnumerable<IControl> controls) |
|
||||
{ |
|
||||
var leftArranger = new LeftDocker(finalSize); |
|
||||
var rightArranger = new RightDocker(finalSize); |
|
||||
var topArranger = new LeftDocker(finalSize.Swap()); |
|
||||
var bottomArranger = new RightDocker(finalSize.Swap()); |
|
||||
|
|
||||
Margins = new Margins(); |
|
||||
|
|
||||
foreach (var control in controls) |
|
||||
{ |
|
||||
Rect dockedRect; |
|
||||
var dock = control.GetValue(DockPanel.DockProperty); |
|
||||
switch (dock) |
|
||||
{ |
|
||||
case Dock.Left: |
|
||||
dockedRect = leftArranger.GetDockingRect(control.DesiredSize, Margins, control.GetAlignments()); |
|
||||
break; |
|
||||
|
|
||||
case Dock.Top: |
|
||||
Margins.Swap(); |
|
||||
dockedRect = topArranger.GetDockingRect(control.DesiredSize.Swap(), Margins, control.GetAlignments().Swap()).Swap(); |
|
||||
Margins.Swap(); |
|
||||
break; |
|
||||
|
|
||||
case Dock.Right: |
|
||||
dockedRect = rightArranger.GetDockingRect(control.DesiredSize, Margins, control.GetAlignments()); |
|
||||
break; |
|
||||
|
|
||||
case Dock.Bottom: |
|
||||
Margins.Swap(); |
|
||||
dockedRect = bottomArranger.GetDockingRect(control.DesiredSize.Swap(), Margins, control.GetAlignments().Swap()).Swap(); |
|
||||
Margins.Swap(); |
|
||||
break; |
|
||||
|
|
||||
default: |
|
||||
throw new InvalidOperationException($"Invalid dock value {dock}"); |
|
||||
} |
|
||||
|
|
||||
control.Arrange(dockedRect); |
|
||||
} |
|
||||
|
|
||||
return finalSize; |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
public static class EnumerableMixin |
|
||||
{ |
|
||||
public static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right) |
|
||||
{ |
|
||||
int i = 0; |
|
||||
var buffer = new Queue<T>(right + 1); |
|
||||
|
|
||||
foreach (T x in source) |
|
||||
{ |
|
||||
if (i >= left) // Read past left many elements at the start
|
|
||||
{ |
|
||||
buffer.Enqueue(x); |
|
||||
if (buffer.Count > right) // Build a buffer to drop right many elements at the end
|
|
||||
yield return buffer.Dequeue(); |
|
||||
} |
|
||||
else i++; |
|
||||
} |
|
||||
} |
|
||||
public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1) |
|
||||
{ |
|
||||
return source.Shrink(0, n); |
|
||||
} |
|
||||
public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1) |
|
||||
{ |
|
||||
return source.Shrink(n, 0); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,27 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
using System; |
|
||||
|
|
||||
public struct LayoutSizes |
|
||||
{ |
|
||||
private readonly Size _size; |
|
||||
private readonly Size _maxSize; |
|
||||
private readonly Size _minSize; |
|
||||
|
|
||||
public LayoutSizes(Size size, Size maxSize, Size minSize) |
|
||||
{ |
|
||||
_size = size; |
|
||||
_maxSize = maxSize; |
|
||||
_minSize = minSize; |
|
||||
} |
|
||||
|
|
||||
public Size MinSize => _minSize; |
|
||||
|
|
||||
public Size MaxSize => _maxSize; |
|
||||
|
|
||||
public Size Size => _size; |
|
||||
|
|
||||
public bool IsWidthSpecified => !double.IsNaN(_size.Width); |
|
||||
public bool IsHeightSpecified => !double.IsNaN(_size.Height); |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public class LeftDocker : Docker |
|
||||
{ |
|
||||
public LeftDocker(Size availableSize) : base(availableSize) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public Rect GetDockingRect(Size sizeToDock, Margins margins, Alignments alignments) |
|
||||
{ |
|
||||
var marginsCutout = margins.AsThickness(); |
|
||||
var withoutMargins = OriginalRect.Deflate(marginsCutout); |
|
||||
var finalRect = withoutMargins.AlignChild(sizeToDock, Alignment.Start, alignments.Vertical); |
|
||||
|
|
||||
AccumulatedOffset += sizeToDock.Width; |
|
||||
margins.HorizontalMargin = margins.HorizontalMargin.Offset(sizeToDock.Width, 0); |
|
||||
|
|
||||
return finalRect; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,51 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
using Layout; |
|
||||
|
|
||||
public static class RectMixin |
|
||||
{ |
|
||||
public static Rect AlignChild(this Rect container, Size childSize, Alignment horizontalAlignment, Alignment verticalAlignment) |
|
||||
{ |
|
||||
var horzSegment = container.GetHorizontalCoordinates(); |
|
||||
var vertSegment = container.GetVerticalCoordinates(); |
|
||||
|
|
||||
var horzResult = GetAlignedSegment(childSize.Width, horizontalAlignment, horzSegment); |
|
||||
var vertResult = GetAlignedSegment(childSize.Height, verticalAlignment, vertSegment); |
|
||||
|
|
||||
return FromSegments(horzResult, vertResult); |
|
||||
} |
|
||||
|
|
||||
public static Rect FromSegments(Segment horzSegment, Segment vertSegment) |
|
||||
{ |
|
||||
return new Rect(horzSegment.Start, vertSegment.Start, horzSegment.Length, vertSegment.Length); |
|
||||
} |
|
||||
|
|
||||
private static Segment GetAlignedSegment(double width, Alignment alignment, Segment horzSegment) |
|
||||
{ |
|
||||
switch (alignment) |
|
||||
{ |
|
||||
case Alignment.Start: |
|
||||
return horzSegment.AlignToStart(width); |
|
||||
|
|
||||
case Alignment.Middle: |
|
||||
return horzSegment.AlignToMiddle(width); |
|
||||
|
|
||||
case Alignment.End: |
|
||||
return horzSegment.AlignToEnd(width); |
|
||||
|
|
||||
default: |
|
||||
return new Segment(horzSegment.Start, horzSegment.End); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static Segment GetHorizontalCoordinates(this Rect rect) |
|
||||
{ |
|
||||
return new Segment(rect.X, rect.Right); |
|
||||
} |
|
||||
|
|
||||
public static Segment GetVerticalCoordinates(this Rect rect) |
|
||||
{ |
|
||||
return new Segment(rect.Y, rect.Bottom); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,23 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
using Layout; |
|
||||
|
|
||||
public class RightDocker : Docker |
|
||||
{ |
|
||||
public RightDocker(Size availableSize) : base(availableSize) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public Rect GetDockingRect(Size sizeToDock, Margins margins, Alignments alignments) |
|
||||
{ |
|
||||
var marginsCutout = margins.AsThickness(); |
|
||||
var withoutMargins = OriginalRect.Deflate(marginsCutout); |
|
||||
var finalRect = withoutMargins.AlignChild(sizeToDock, Alignment.End, alignments.Vertical); |
|
||||
|
|
||||
AccumulatedOffset += sizeToDock.Width; |
|
||||
margins.HorizontalMargin = margins.HorizontalMargin.Offset(0, sizeToDock.Width); |
|
||||
|
|
||||
return finalRect; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public struct Segment |
|
||||
{ |
|
||||
public Segment(double start, double end) |
|
||||
{ |
|
||||
Start = start; |
|
||||
End = end; |
|
||||
} |
|
||||
|
|
||||
public double Start { get; set; } |
|
||||
public double End { get; set; } |
|
||||
|
|
||||
public double Length => End - Start; |
|
||||
|
|
||||
public override string ToString() |
|
||||
{ |
|
||||
return $"Start: {Start}, End: {End}"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,8 +0,0 @@ |
|||||
namespace Perspex.Controls |
|
||||
{ |
|
||||
public class Margins |
|
||||
{ |
|
||||
public Segment HorizontalMargin { get; set; } |
|
||||
public Segment VerticalMargin { get; set; } |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue