diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs
index 7d040bc2523..2bd90a980fa 100644
--- a/src/Avalonia.Controls/DockPanel.cs
+++ b/src/Avalonia.Controls/DockPanel.cs
@@ -37,12 +37,29 @@ public class DockPanel : Panel
nameof(LastChildFill),
defaultValue: true);
+ ///
+ /// Identifies the HorizontalSpacing dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly StyledProperty HorizontalSpacingProperty =
+ AvaloniaProperty.Register(
+ nameof(HorizontalSpacing));
+
+ ///
+ /// Identifies the VerticalSpacing dependency property.
+ ///
+ /// The identifier for the dependency property.
+ public static readonly StyledProperty VerticalSpacingProperty =
+ AvaloniaProperty.Register(
+ nameof(VerticalSpacing));
+
///
/// Initializes static members of the class.
///
static DockPanel()
{
AffectsParentMeasure(DockProperty);
+ AffectsMeasure(LastChildFillProperty, HorizontalSpacingProperty, VerticalSpacingProperty);
}
///
@@ -75,39 +92,56 @@ public bool LastChildFill
set => SetValue(LastChildFillProperty, value);
}
+ ///
+ /// Gets or sets the horizontal distance between the child objects.
+ ///
+ public double HorizontalSpacing
+ {
+ get => GetValue(HorizontalSpacingProperty);
+ set => SetValue(HorizontalSpacingProperty, value);
+ }
+
+ ///
+ /// Gets or sets the vertical distance between the child objects.
+ ///
+ public double VerticalSpacing
+ {
+ get => GetValue(VerticalSpacingProperty);
+ set => SetValue(VerticalSpacingProperty, value);
+ }
+
+
///
/// Updates DesiredSize of the DockPanel. Called by parent Control. This is the first pass of layout.
///
///
/// Children are measured based on their sizing properties and .
- /// Each child is allowed to consume all of the space on the side on which it is docked; Left/Right docked
+ /// Each child is allowed to consume all the space on the side on which it is docked; Left/Right docked
/// children are granted all vertical space for their entire width, and Top/Bottom docked children are
/// granted all horizontal space for their entire height.
///
- /// Constraint size is an "upper limit" that the return value should not exceed.
+ /// Constraint size is an "upper limit" that the return value should not exceed.
/// The Panel's desired size.
- protected override Size MeasureOverride(Size constraint)
+ protected override Size MeasureOverride(Size availableSize)
{
- var children = Children;
+ var parentWidth = 0d;
+ var parentHeight = 0d;
+ var accumulatedWidth = 0d;
+ var accumulatedHeight = 0d;
- double parentWidth = 0; // Our current required width due to children thus far.
- double parentHeight = 0; // Our current required height due to children thus far.
- double accumulatedWidth = 0; // Total width consumed by children.
- double accumulatedHeight = 0; // Total height consumed by children.
+ var horizontalSpacing = false;
+ var verticalSpacing = false;
+ var childrenCount = LastChildFill ? Children.Count - 1 : Children.Count;
- for (int i = 0, count = children.Count; i < count; ++i)
+ for (var index = 0; index < childrenCount; ++index)
{
- var child = children[i];
- Size childConstraint; // Contains the suggested input constraint for this child.
- Size childDesiredSize; // Contains the return size from child measure.
-
- // Child constraint is the remaining size; this is total size minus size consumed by previous children.
- childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
- Math.Max(0.0, constraint.Height - accumulatedHeight));
+ var child = Children[index];
+ var childConstraint = new Size(
+ Math.Max(0, availableSize.Width - accumulatedWidth),
+ Math.Max(0, availableSize.Height - accumulatedHeight));
- // Measure child.
child.Measure(childConstraint);
- childDesiredSize = child.DesiredSize;
+ var childDesiredSize = child.DesiredSize;
// Now, we adjust:
// 1. Size consumed by children (accumulatedSize). This will be used when computing subsequent
@@ -119,88 +153,125 @@ protected override Size MeasureOverride(Size constraint)
// will deal with computing our minimum size (parentSize) due to that accumulation.
// Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does
// not accumulate: Width for Top/Bottom, Height for Left/Right.
- switch (GetDock(child))
+ switch (child.GetValue(DockProperty))
{
case Dock.Left:
case Dock.Right:
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
+ if (child.IsVisible)
+ {
+ accumulatedWidth += HorizontalSpacing;
+ horizontalSpacing = true;
+ }
accumulatedWidth += childDesiredSize.Width;
break;
case Dock.Top:
case Dock.Bottom:
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
+ if (child.IsVisible)
+ {
+ accumulatedHeight += VerticalSpacing;
+ verticalSpacing = true;
+ }
accumulatedHeight += childDesiredSize.Height;
break;
}
}
+ if (LastChildFill)
+ {
+ var child = Children[Children.Count - 1];
+ var childConstraint = new Size(
+ Math.Max(0, availableSize.Width - accumulatedWidth),
+ Math.Max(0, availableSize.Height - accumulatedHeight));
+
+ child.Measure(childConstraint);
+ var childDesiredSize = child.DesiredSize;
+ parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
+ parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
+ accumulatedHeight += childDesiredSize.Height;
+ accumulatedWidth += childDesiredSize.Width;
+ }
+ else
+ {
+ if (horizontalSpacing)
+ accumulatedWidth -= HorizontalSpacing;
+ if (verticalSpacing)
+ accumulatedHeight -= VerticalSpacing;
+ }
+
// Make sure the final accumulated size is reflected in parentSize.
parentWidth = Math.Max(parentWidth, accumulatedWidth);
parentHeight = Math.Max(parentHeight, accumulatedHeight);
-
- return (new Size(parentWidth, parentHeight));
+ return new Size(parentWidth, parentHeight);
}
///
/// DockPanel computes a position and final size for each of its children based upon their
/// enum and sizing properties.
///
- /// Size that DockPanel will assume to position children.
- protected override Size ArrangeOverride(Size arrangeSize)
+ /// Size that DockPanel will assume to position children.
+ protected override Size ArrangeOverride(Size finalSize)
{
- var children = Children;
- int totalChildrenCount = children.Count;
- int nonFillChildrenCount = totalChildrenCount - (LastChildFill ? 1 : 0);
+ if (Children.Count is 0)
+ return finalSize;
- double accumulatedLeft = 0;
- double accumulatedTop = 0;
- double accumulatedRight = 0;
- double accumulatedBottom = 0;
+ var currentBounds = new Rect(finalSize);
+ var childrenCount = LastChildFill ? Children.Count - 1 : Children.Count;
- for (int i = 0; i < totalChildrenCount; ++i)
+ for (var index = 0; index < childrenCount; ++index)
{
- var child = children[i];
+ var child = Children[index];
+ if (!child.IsVisible)
+ continue;
- Size childDesiredSize = child.DesiredSize;
- Rect rcChild = new Rect(
- accumulatedLeft,
- accumulatedTop,
- Math.Max(0.0, arrangeSize.Width - (accumulatedLeft + accumulatedRight)),
- Math.Max(0.0, arrangeSize.Height - (accumulatedTop + accumulatedBottom)));
-
- if (i < nonFillChildrenCount)
+ var dock = child.GetValue(DockProperty);
+ double width, height;
+ switch (dock)
{
- switch (GetDock(child))
- {
- case Dock.Left:
- accumulatedLeft += childDesiredSize.Width;
- rcChild = rcChild.WithWidth(childDesiredSize.Width);
- break;
-
- case Dock.Right:
- accumulatedRight += childDesiredSize.Width;
- rcChild = rcChild.WithX(Math.Max(0.0, arrangeSize.Width - accumulatedRight));
- rcChild = rcChild.WithWidth(childDesiredSize.Width);
- break;
-
- case Dock.Top:
- accumulatedTop += childDesiredSize.Height;
- rcChild = rcChild.WithHeight(childDesiredSize.Height);
- break;
-
- case Dock.Bottom:
- accumulatedBottom += childDesiredSize.Height;
- rcChild = rcChild.WithY(Math.Max(0.0, arrangeSize.Height - accumulatedBottom));
- rcChild = rcChild.WithHeight(childDesiredSize.Height);
- break;
- }
+ case Dock.Left:
+
+ width = Math.Min(child.DesiredSize.Width, currentBounds.Width);
+ child.Arrange(currentBounds.WithWidth(width));
+ width += HorizontalSpacing;
+ currentBounds = new Rect(currentBounds.X + width, currentBounds.Y, Math.Max(0, currentBounds.Width - width), currentBounds.Height);
+
+ break;
+ case Dock.Top:
+
+ height = Math.Min(child.DesiredSize.Height, currentBounds.Height);
+ child.Arrange(currentBounds.WithHeight(height));
+ height += VerticalSpacing;
+ currentBounds = new Rect(currentBounds.X, currentBounds.Y + height, currentBounds.Width, Math.Max(0, currentBounds.Height - height));
+
+ break;
+ case Dock.Right:
+
+ width = Math.Min(child.DesiredSize.Width, currentBounds.Width);
+ child.Arrange(new Rect(currentBounds.X + currentBounds.Width - width, currentBounds.Y, width, currentBounds.Height));
+ width += HorizontalSpacing;
+ currentBounds = currentBounds.WithWidth(Math.Max(0, currentBounds.Width - width));
+
+ break;
+ case Dock.Bottom:
+
+ height = Math.Min(child.DesiredSize.Height, currentBounds.Height);
+ child.Arrange(new Rect(currentBounds.X, currentBounds.Y + currentBounds.Height - height, currentBounds.Width, height));
+ height += VerticalSpacing;
+ currentBounds = currentBounds.WithHeight(Math.Max(0, currentBounds.Height - height));
+
+ break;
}
+ }
- child.Arrange(rcChild);
+ if (LastChildFill)
+ {
+ var child = Children[Children.Count - 1];
+ child.Arrange(new Rect(currentBounds.X, currentBounds.Y, currentBounds.Width, currentBounds.Height));
}
- return (arrangeSize);
+ return finalSize;
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
index 4da1ef5a100..d00ef70cde9 100644
--- a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
@@ -56,6 +56,34 @@ public void Should_Dock_Controls_Vertical_First()
Assert.Equal(new Rect(50, 50, 500, 300), target.Children[4].Bounds);
}
+ [Fact]
+ public void Should_Dock_Controls_With_Spacing()
+ {
+ var target = new DockPanel
+ {
+ HorizontalSpacing = 10,
+ VerticalSpacing = 10,
+ Children =
+ {
+ new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top },
+ new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom },
+ new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left },
+ new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right },
+ new Border { },
+ }
+ };
+
+ target.Measure(Size.Infinity);
+ target.Arrange(new Rect(target.DesiredSize));
+
+ Assert.Equal(new Rect(0, 0, 500, 520), target.Bounds);
+ Assert.Equal(new Rect(0, 0, 500, 50), target.Children[0].Bounds);
+ Assert.Equal(new Rect(0, 470, 500, 50), target.Children[1].Bounds);
+ Assert.Equal(new Rect(0, 60, 50, 400), target.Children[2].Bounds);
+ Assert.Equal(new Rect(450, 60, 50, 400), target.Children[3].Bounds);
+ Assert.Equal(new Rect(60, 60, 380, 400), target.Children[4].Bounds);
+ }
+
[Fact]
public void Changing_Child_Dock_Invalidates_Measure()
{