Browse Source
The old one was broken - replaced by the implementation from WinRTXamlToolkit which is both simpler and works correctly. Also added some tests. This closes #397, closes #348, closes #74.pull/403/head
4 changed files with 225 additions and 391 deletions
@ -1,445 +1,176 @@ |
|||
namespace Perspex.Controls |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using Layout; |
|||
|
|||
public class DockPanel : Panel |
|||
/// <summary>
|
|||
/// Defines the available docking modes for a control in a <see cref="DockPanel"/>.
|
|||
/// </summary>
|
|||
public enum Dock |
|||
{ |
|||
public static readonly PerspexProperty<Dock> DockProperty = PerspexProperty.RegisterAttached<DockPanel, Control, Dock>("Dock"); |
|||
Left = 0, |
|||
Bottom, |
|||
Right, |
|||
Top |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A panel which arranges its children at the top, bottom, left, right or center.
|
|||
/// </summary>
|
|||
public class DockPanel : Panel |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the Dock attached property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<Dock> DockProperty = |
|||
PerspexProperty.RegisterAttached<DockPanel, Control, Dock>("Dock"); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="LastChildFill"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<bool> LastChildFillProperty = |
|||
PerspexProperty.Register<DockPanel, bool>( |
|||
nameof(LastChildFillProperty), |
|||
defaultValue: true); |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="DockPanel"/> class.
|
|||
/// </summary>
|
|||
static DockPanel() |
|||
{ |
|||
AffectsArrange(DockProperty); |
|||
} |
|||
|
|||
// ReSharper disable once UnusedMember.Global
|
|||
public static Dock GetDock(PerspexObject perspexObject) |
|||
/// <summary>
|
|||
/// Gets the value of the Dock attached property on the specified control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control.</param>
|
|||
/// <returns>The Dock attached property.</returns>
|
|||
public static Dock GetDock(Control control) |
|||
{ |
|||
return perspexObject.GetValue(DockProperty); |
|||
return control.GetValue(DockProperty); |
|||
} |
|||
|
|||
// ReSharper disable once UnusedMember.Global
|
|||
public static void SetDock(PerspexObject element, Dock dock) |
|||
/// <summary>
|
|||
/// Sets the value of the Dock attached property on the specified control.
|
|||
/// </summary>
|
|||
/// <param name="control">The control.</param>
|
|||
/// <param name="value">The value of the Dock property.</param>
|
|||
public static void SetDock(Control control, Dock value) |
|||
{ |
|||
element.SetValue(DockProperty, dock); |
|||
control.SetValue(DockProperty, value); |
|||
} |
|||
|
|||
public static readonly PerspexProperty<bool> LastChildFillProperty = PerspexProperty.Register<DockPanel, bool>(nameof(LastChildFillProperty), defaultValue: true); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value which indicates whether the last child of the
|
|||
/// <see cref="DockPanel"/> fills the remaining space in the panel.
|
|||
/// </summary>
|
|||
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) |
|||
/// <inheritdoc/>
|
|||
protected override Size MeasureOverride(Size constraint) |
|||
{ |
|||
var requiredHorizontalLength = 0D; |
|||
var requiredVerticalLength = 0D; |
|||
double usedWidth = 0.0; |
|||
double usedHeight = 0.0; |
|||
double maximumWidth = 0.0; |
|||
double maximumHeight = 0.0; |
|||
|
|||
foreach (var control in children) |
|||
// Measure each of the Children
|
|||
foreach (Control element in Children) |
|||
{ |
|||
control.Measure(availableSize); |
|||
|
|||
var dock = control.GetValue(DockProperty); |
|||
if (IsHorizontal(dock)) |
|||
{ |
|||
requiredHorizontalLength += control.DesiredSize.Width; |
|||
} |
|||
else |
|||
// Get the child's desired size
|
|||
Size remainingSize = new Size( |
|||
Math.Max(0.0, constraint.Width - usedWidth), |
|||
Math.Max(0.0, constraint.Height - usedHeight)); |
|||
element.Measure(remainingSize); |
|||
Size desiredSize = element.DesiredSize; |
|||
|
|||
// Decrease the remaining space for the rest of the children
|
|||
switch (GetDock(element)) |
|||
{ |
|||
requiredVerticalLength += control.DesiredSize.Height; |
|||
case Dock.Left: |
|||
case Dock.Right: |
|||
maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height); |
|||
usedWidth += desiredSize.Width; |
|||
break; |
|||
case Dock.Top: |
|||
case Dock.Bottom: |
|||
maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width); |
|||
usedHeight += desiredSize.Height; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
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); |
|||
maximumWidth = Math.Max(maximumWidth, usedWidth); |
|||
maximumHeight = Math.Max(maximumHeight, usedHeight); |
|||
return new Size(maximumWidth, maximumHeight); |
|||
} |
|||
|
|||
private static void ArrangeToFill(ILayoutable layoutable, Size containerSize, Margin margin) |
|||
/// <inheritdoc/>
|
|||
protected override Size ArrangeOverride(Size arrangeSize) |
|||
{ |
|||
var containerRect = new Rect(new Point(0, 0), containerSize); |
|||
var marginsCutout = margin.AsThickness(); |
|||
var withoutMargins = containerRect.Deflate(marginsCutout); |
|||
|
|||
layoutable.Arrange(withoutMargins); |
|||
} |
|||
double left = 0.0; |
|||
double top = 0.0; |
|||
double right = 0.0; |
|||
double bottom = 0.0; |
|||
|
|||
private class DockingArranger |
|||
{ |
|||
public Margin UsedMargin { get; private set; } |
|||
// Arrange each of the Children
|
|||
var children = Children; |
|||
int dockedCount = children.Count - (LastChildFill ? 1 : 0); |
|||
int index = 0; |
|||
|
|||
public Size ArrangeAndGetUsedSize(Size availableSize, IEnumerable<IControl> children) |
|||
foreach (Control element in 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) |
|||
// Determine the remaining space left to arrange the element
|
|||
Rect remainingRect = new Rect( |
|||
left, |
|||
top, |
|||
Math.Max(0.0, arrangeSize.Width - left - right), |
|||
Math.Max(0.0, arrangeSize.Height - top - bottom)); |
|||
|
|||
// Trim the remaining Rect to the docked size of the element
|
|||
// (unless the element should fill the remaining space because
|
|||
// of LastChildFill)
|
|||
if (index < dockedCount) |
|||
{ |
|||
Rect dockedRect; |
|||
var dock = control.GetValue(DockProperty); |
|||
switch (dock) |
|||
Size desiredSize = element.DesiredSize; |
|||
switch (GetDock(element)) |
|||
{ |
|||
case Dock.Left: |
|||
dockedRect = leftArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); |
|||
left += desiredSize.Width; |
|||
remainingRect = remainingRect.WithWidth(desiredSize.Width); |
|||
break; |
|||
|
|||
case Dock.Top: |
|||
UsedMargin.Swap(); |
|||
dockedRect = topArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); |
|||
UsedMargin.Swap(); |
|||
top += desiredSize.Height; |
|||
remainingRect = remainingRect.WithHeight(desiredSize.Height); |
|||
break; |
|||
|
|||
case Dock.Right: |
|||
dockedRect = rightArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); |
|||
right += desiredSize.Width; |
|||
remainingRect = new Rect( |
|||
Math.Max(0.0, arrangeSize.Width - right), |
|||
remainingRect.Y, |
|||
desiredSize.Width, |
|||
remainingRect.Height); |
|||
break; |
|||
|
|||
case Dock.Bottom: |
|||
UsedMargin.Swap(); |
|||
dockedRect = bottomArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); |
|||
UsedMargin.Swap(); |
|||
bottom += desiredSize.Height; |
|||
remainingRect = new Rect( |
|||
remainingRect.X, |
|||
Math.Max(0.0, arrangeSize.Height - bottom), |
|||
remainingRect.Width, |
|||
desiredSize.Height); |
|||
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); |
|||
element.Arrange(remainingRect); |
|||
index++; |
|||
} |
|||
|
|||
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 |
|||
{ |
|||
public Alignments(Alignment horizontal, Alignment vertical) |
|||
{ |
|||
Horizontal = horizontal; |
|||
Vertical = vertical; |
|||
} |
|||
|
|||
public Alignment Horizontal { get; } |
|||
|
|||
public Alignment Vertical { get; } |
|||
} |
|||
|
|||
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); |
|||
return arrangeSize; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
public class DockPanelTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Dock_Controls_Horizontal_First() |
|||
{ |
|||
var target = new DockPanel |
|||
{ |
|||
Children = new Controls |
|||
{ |
|||
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, 500), target.Bounds); |
|||
Assert.Equal(new Rect(0, 0, 500, 50), target.Children[0].Bounds); |
|||
Assert.Equal(new Rect(0, 450, 500, 50), target.Children[1].Bounds); |
|||
Assert.Equal(new Rect(0, 50, 50, 400), target.Children[2].Bounds); |
|||
Assert.Equal(new Rect(450, 50, 50, 400), target.Children[3].Bounds); |
|||
Assert.Equal(new Rect(50, 50, 400, 400), target.Children[4].Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Dock_Controls_Vertical_First() |
|||
{ |
|||
var target = new DockPanel |
|||
{ |
|||
Children = new Controls |
|||
{ |
|||
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left }, |
|||
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, |
|||
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top }, |
|||
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, |
|||
new Border { }, |
|||
} |
|||
}; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(new Rect(0, 0, 600, 400), target.Bounds); |
|||
Assert.Equal(new Rect(0, 0, 50, 400), target.Children[0].Bounds); |
|||
Assert.Equal(new Rect(550, 0, 50, 400), target.Children[1].Bounds); |
|||
Assert.Equal(new Rect(50, 0, 500, 50), target.Children[2].Bounds); |
|||
Assert.Equal(new Rect(50, 350, 500, 50), target.Children[3].Bounds); |
|||
Assert.Equal(new Rect(50, 50, 500, 300), target.Children[4].Bounds); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue