From d94bbfc2c3772d57039603d0a074c939431828a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Nieto=20S=C3=A1nchez?= Date: Thu, 17 Sep 2015 19:12:57 +0200 Subject: [PATCH] DockPanel --- Perspex.sln.DotSettings | 1 + .../DockPanelTests/AlignerTests.cs | 44 ++++++ .../DockPanelTests/LeftDockerTests.cs | 38 ++++++ .../DockPanelTests/RectAlignerTests.cs | 40 ++++++ .../DockPanelTests/RightDockerTests.cs | 38 ++++++ src/Perspex.Controls/DockPanel/Aligner.cs | 21 +++ src/Perspex.Controls/DockPanel/Alignment.cs | 10 ++ src/Perspex.Controls/DockPanel/Alignments.cs | 24 ++++ .../DockPanel/CoordinateMixin.cs | 98 +++++++++++++ src/Perspex.Controls/DockPanel/Dock.cs | 10 ++ src/Perspex.Controls/DockPanel/DockPanel.cs | 129 ++++++++++++++++++ src/Perspex.Controls/DockPanel/Docker.cs | 33 +++++ .../DockPanel/DockingArranger.cs | 54 ++++++++ .../DockPanel/EnumerableMixin.cs | 32 +++++ src/Perspex.Controls/DockPanel/LayoutSizes.cs | 27 ++++ src/Perspex.Controls/DockPanel/LeftDocker.cs | 21 +++ src/Perspex.Controls/DockPanel/RectMixin.cs | 51 +++++++ src/Perspex.Controls/DockPanel/RightDocker.cs | 23 ++++ src/Perspex.Controls/DockPanel/Segment.cs | 21 +++ src/Perspex.Controls/Margins.cs | 8 ++ src/Perspex.Controls/Perspex.Controls.csproj | 15 ++ src/Perspex.SceneGraph/Size.cs | 10 ++ .../Perspex.Controls.UnitTests.csproj | 4 + 23 files changed, 752 insertions(+) create mode 100644 Tests/Perspex.Controls.UnitTests/DockPanelTests/AlignerTests.cs create mode 100644 Tests/Perspex.Controls.UnitTests/DockPanelTests/LeftDockerTests.cs create mode 100644 Tests/Perspex.Controls.UnitTests/DockPanelTests/RectAlignerTests.cs create mode 100644 Tests/Perspex.Controls.UnitTests/DockPanelTests/RightDockerTests.cs create mode 100644 src/Perspex.Controls/DockPanel/Aligner.cs create mode 100644 src/Perspex.Controls/DockPanel/Alignment.cs create mode 100644 src/Perspex.Controls/DockPanel/Alignments.cs create mode 100644 src/Perspex.Controls/DockPanel/CoordinateMixin.cs create mode 100644 src/Perspex.Controls/DockPanel/Dock.cs create mode 100644 src/Perspex.Controls/DockPanel/DockPanel.cs create mode 100644 src/Perspex.Controls/DockPanel/Docker.cs create mode 100644 src/Perspex.Controls/DockPanel/DockingArranger.cs create mode 100644 src/Perspex.Controls/DockPanel/EnumerableMixin.cs create mode 100644 src/Perspex.Controls/DockPanel/LayoutSizes.cs create mode 100644 src/Perspex.Controls/DockPanel/LeftDocker.cs create mode 100644 src/Perspex.Controls/DockPanel/RectMixin.cs create mode 100644 src/Perspex.Controls/DockPanel/RightDocker.cs create mode 100644 src/Perspex.Controls/DockPanel/Segment.cs create mode 100644 src/Perspex.Controls/Margins.cs diff --git a/Perspex.sln.DotSettings b/Perspex.sln.DotSettings index f0a2c81713..bf98899847 100644 --- a/Perspex.sln.DotSettings +++ b/Perspex.sln.DotSettings @@ -1,5 +1,6 @@  ExplicitlyExcluded + ExplicitlyExcluded HINT <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> diff --git a/Tests/Perspex.Controls.UnitTests/DockPanelTests/AlignerTests.cs b/Tests/Perspex.Controls.UnitTests/DockPanelTests/AlignerTests.cs new file mode 100644 index 0000000000..4e9c087381 --- /dev/null +++ b/Tests/Perspex.Controls.UnitTests/DockPanelTests/AlignerTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/Tests/Perspex.Controls.UnitTests/DockPanelTests/LeftDockerTests.cs b/Tests/Perspex.Controls.UnitTests/DockPanelTests/LeftDockerTests.cs new file mode 100644 index 0000000000..38e12490eb --- /dev/null +++ b/Tests/Perspex.Controls.UnitTests/DockPanelTests/LeftDockerTests.cs @@ -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(); + 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 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)}, + }; + } +} \ No newline at end of file diff --git a/Tests/Perspex.Controls.UnitTests/DockPanelTests/RectAlignerTests.cs b/Tests/Perspex.Controls.UnitTests/DockPanelTests/RectAlignerTests.cs new file mode 100644 index 0000000000..33e18b1c8b --- /dev/null +++ b/Tests/Perspex.Controls.UnitTests/DockPanelTests/RectAlignerTests.cs @@ -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)}, + }; + } +} \ No newline at end of file diff --git a/Tests/Perspex.Controls.UnitTests/DockPanelTests/RightDockerTests.cs b/Tests/Perspex.Controls.UnitTests/DockPanelTests/RightDockerTests.cs new file mode 100644 index 0000000000..b08676dd71 --- /dev/null +++ b/Tests/Perspex.Controls.UnitTests/DockPanelTests/RightDockerTests.cs @@ -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(); + 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 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)}, + }; + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/Aligner.cs b/src/Perspex.Controls/DockPanel/Aligner.cs new file mode 100644 index 0000000000..821d9000f3 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/Aligner.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/Alignment.cs b/src/Perspex.Controls/DockPanel/Alignment.cs new file mode 100644 index 0000000000..d710a6bd60 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/Alignment.cs @@ -0,0 +1,10 @@ +namespace Perspex.Controls +{ + public enum Alignment + { + Stretch, + Start, + Middle, + End, + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/Alignments.cs b/src/Perspex.Controls/DockPanel/Alignments.cs new file mode 100644 index 0000000000..4eefde9719 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/Alignments.cs @@ -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; } + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/CoordinateMixin.cs b/src/Perspex.Controls/DockPanel/CoordinateMixin.cs new file mode 100644 index 0000000000..829b2d3de2 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/CoordinateMixin.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/Dock.cs b/src/Perspex.Controls/DockPanel/Dock.cs new file mode 100644 index 0000000000..69a2e0fe66 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/Dock.cs @@ -0,0 +1,10 @@ +namespace Perspex.Controls +{ + public enum Dock + { + Left = 0, + Bottom, + Right, + Top + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/DockPanel.cs b/src/Perspex.Controls/DockPanel/DockPanel.cs new file mode 100644 index 0000000000..e71ac4ee9e --- /dev/null +++ b/src/Perspex.Controls/DockPanel/DockPanel.cs @@ -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 DockProperty = PerspexProperty.RegisterAttached("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 LastChildFillProperty = PerspexProperty.Register(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 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); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/Docker.cs b/src/Perspex.Controls/DockPanel/Docker.cs new file mode 100644 index 0000000000..04fea052b2 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/Docker.cs @@ -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; } + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/DockingArranger.cs b/src/Perspex.Controls/DockPanel/DockingArranger.cs new file mode 100644 index 0000000000..2ce9c429ae --- /dev/null +++ b/src/Perspex.Controls/DockPanel/DockingArranger.cs @@ -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 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; + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/EnumerableMixin.cs b/src/Perspex.Controls/DockPanel/EnumerableMixin.cs new file mode 100644 index 0000000000..d1543e4fac --- /dev/null +++ b/src/Perspex.Controls/DockPanel/EnumerableMixin.cs @@ -0,0 +1,32 @@ +namespace Perspex.Controls +{ + using System.Collections.Generic; + + public static class EnumerableMixin + { + public static IEnumerable Shrink(this IEnumerable source, int left, int right) + { + int i = 0; + var buffer = new Queue(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 WithoutLast(this IEnumerable source, int n = 1) + { + return source.Shrink(0, n); + } + public static IEnumerable WithoutFirst(this IEnumerable source, int n = 1) + { + return source.Shrink(n, 0); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/LayoutSizes.cs b/src/Perspex.Controls/DockPanel/LayoutSizes.cs new file mode 100644 index 0000000000..62862ac958 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/LayoutSizes.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/LeftDocker.cs b/src/Perspex.Controls/DockPanel/LeftDocker.cs new file mode 100644 index 0000000000..063774f752 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/LeftDocker.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/RectMixin.cs b/src/Perspex.Controls/DockPanel/RectMixin.cs new file mode 100644 index 0000000000..a0bbe52045 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/RectMixin.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/RightDocker.cs b/src/Perspex.Controls/DockPanel/RightDocker.cs new file mode 100644 index 0000000000..5c8abe0e0f --- /dev/null +++ b/src/Perspex.Controls/DockPanel/RightDocker.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/DockPanel/Segment.cs b/src/Perspex.Controls/DockPanel/Segment.cs new file mode 100644 index 0000000000..56afae9bb5 --- /dev/null +++ b/src/Perspex.Controls/DockPanel/Segment.cs @@ -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}"; + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/Margins.cs b/src/Perspex.Controls/Margins.cs new file mode 100644 index 0000000000..12075f61f0 --- /dev/null +++ b/src/Perspex.Controls/Margins.cs @@ -0,0 +1,8 @@ +namespace Perspex.Controls +{ + public class Margins + { + public Segment HorizontalMargin { get; set; } + public Segment VerticalMargin { get; set; } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index dc7cbc571a..fe00a5d7dd 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -41,6 +41,17 @@ Properties\SharedAssemblyInfo.cs + + + + + + + + + + + @@ -48,6 +59,9 @@ + + + @@ -65,6 +79,7 @@ + diff --git a/src/Perspex.SceneGraph/Size.cs b/src/Perspex.SceneGraph/Size.cs index e7d210f1ef..7c67fbd774 100644 --- a/src/Perspex.SceneGraph/Size.cs +++ b/src/Perspex.SceneGraph/Size.cs @@ -96,6 +96,16 @@ namespace Perspex return new Size(size._width / scale.X, size._height / scale.Y); } + public static Size operator +(Size size, Size toAdd) + { + return new Size(size._width + toAdd._width, size._height + toAdd._height); + } + + public static Size operator -(Size size, Size toSubstract) + { + return new Size(size._width + toSubstract._width, size._height + toSubstract._height); + } + /// /// Constrains the size. /// diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 9287985d70..ed7a4d41ff 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -91,9 +91,13 @@ + + + +