23 changed files with 752 additions and 0 deletions
@ -0,0 +1,44 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
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)}, |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
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)}, |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
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,21 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Perspex.Controls |
|||
{ |
|||
public enum Alignment |
|||
{ |
|||
Stretch, |
|||
Start, |
|||
Middle, |
|||
End, |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
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; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
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()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Perspex.Controls |
|||
{ |
|||
public enum Dock |
|||
{ |
|||
Left = 0, |
|||
Bottom, |
|||
Right, |
|||
Top |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
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; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
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}"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Perspex.Controls |
|||
{ |
|||
public class Margins |
|||
{ |
|||
public Segment HorizontalMargin { get; set; } |
|||
public Segment VerticalMargin { get; set; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue