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