diff --git a/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj b/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj
index b6ea28ee7..cfc38e291 100644
--- a/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj
+++ b/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj
@@ -3,8 +3,10 @@
net10.0enablelatest
- 3.2.0-optiq06
+ 3.2.0-optiq07
+ #3.2.0-optiq07
+ Tapping a point now only selects the top-most
#3.2.0-optiq06
Improved GetDescendant and ActionSheetDialog has selected item index
#3.2.0-optiq05
diff --git a/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj b/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj
index 50d6ce7f8..0412c4fc3 100644
--- a/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj
+++ b/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj
@@ -3,8 +3,10 @@
net10.0enablelatest
- 3.2.0-optiq06
+ 3.2.0-optiq07
+ #3.2.0-optiq07
+ Tapping a point now only selects the top-most
#3.2.0-optiq06
Improved GetDescendant and ActionSheetDialog has selected item index
#3.2.0-optiq05
diff --git a/Svg.Editor.Core.Tests/SelectionToolTests.cs b/Svg.Editor.Core.Tests/SelectionToolTests.cs
index 8dec5e837..4595e3d34 100644
--- a/Svg.Editor.Core.Tests/SelectionToolTests.cs
+++ b/Svg.Editor.Core.Tests/SelectionToolTests.cs
@@ -74,6 +74,95 @@ public async Task IfUserTapsCanvas_AndTapPositionDoesNotIntersectWithSelectedEle
Assert.AreEqual(0, Canvas.SelectedElements.Count);
}
+ [Test]
+ public async Task IfUserTapsOverlappingElements_OnlySelectsTopMostElement()
+ {
+ // Arrange
+ await Canvas.EnsureInitialized();
+ var txtTool = Canvas.Tools.OfType().Single();
+ Canvas.ActiveTool = txtTool;
+ Canvas.ScreenWidth = 800;
+ Canvas.ScreenHeight = 500;
+
+ // "bottom" is declared first, so "top" (declared after, hence higher z-order) fully occludes it
+ var bottom = new SvgRectangle
+ {
+ X = new SvgUnit(SvgUnitType.Pixel, 200),
+ Y = new SvgUnit(SvgUnitType.Pixel, 200),
+ Width = new SvgUnit(SvgUnitType.Pixel, 100),
+ Height = new SvgUnit(SvgUnitType.Pixel, 100),
+ Fill = new SvgColourServer(Color.Create(255, 0, 0))
+ };
+ var top = new SvgRectangle
+ {
+ X = new SvgUnit(SvgUnitType.Pixel, 200),
+ Y = new SvgUnit(SvgUnitType.Pixel, 200),
+ Width = new SvgUnit(SvgUnitType.Pixel, 100),
+ Height = new SvgUnit(SvgUnitType.Pixel, 100),
+ Fill = new SvgColourServer(Color.Create(0, 0, 255))
+ };
+ Canvas.Document.Children.Add(bottom);
+ Canvas.Document.Children.Add(top);
+
+ // Preassert
+ Assert.AreEqual(0, Canvas.SelectedElements.Count);
+
+ // Act - tap somewhere in the middle of both fully-overlapping rectangles
+ var pt1 = PointF.Create(250, 250);
+ await Canvas.OnEvent(new PointerEvent(EventType.PointerDown, pt1, pt1, pt1, 1));
+ await Canvas.OnEvent(new PointerEvent(EventType.PointerUp, pt1, pt1, pt1, 1));
+ ((TestScheduler)SchedulerProvider.BackgroundScheduler).AdvanceBy(TimeSpan.FromSeconds(1).Ticks);
+
+ // Assert - only the top-most element is selected, even though both are hit
+ Assert.AreEqual(1, Canvas.SelectedElements.Count);
+ Assert.AreSame(top, Canvas.SelectedElements.Single());
+ }
+
+ [Test]
+ public async Task IfUserDrawsSelectionRectangle_AndOverlappingElementsAreContained_AllOverlappingElementsAreSelected()
+ {
+ // Arrange
+ await Canvas.EnsureInitialized();
+ var txtTool = Canvas.Tools.OfType().Single();
+ Canvas.ActiveTool = txtTool;
+ Canvas.ScreenWidth = 800;
+ Canvas.ScreenHeight = 500;
+
+ var bottom = new SvgRectangle
+ {
+ X = new SvgUnit(SvgUnitType.Pixel, 200),
+ Y = new SvgUnit(SvgUnitType.Pixel, 200),
+ Width = new SvgUnit(SvgUnitType.Pixel, 100),
+ Height = new SvgUnit(SvgUnitType.Pixel, 100),
+ Fill = new SvgColourServer(Color.Create(255, 0, 0))
+ };
+ var top = new SvgRectangle
+ {
+ X = new SvgUnit(SvgUnitType.Pixel, 200),
+ Y = new SvgUnit(SvgUnitType.Pixel, 200),
+ Width = new SvgUnit(SvgUnitType.Pixel, 100),
+ Height = new SvgUnit(SvgUnitType.Pixel, 100),
+ Fill = new SvgColourServer(Color.Create(0, 0, 255))
+ };
+ Canvas.Document.Children.Add(bottom);
+ Canvas.Document.Children.Add(top);
+
+ // Preassert
+ Assert.AreEqual(0, Canvas.SelectedElements.Count);
+
+ // Act - drag a selection area that encloses both fully-overlapping rectangles
+ var start = PointF.Create(190, 190);
+ var end = PointF.Create(310, 310);
+ await Canvas.OnEvent(new PointerEvent(EventType.PointerDown, start, start, start, 1));
+ await Canvas.OnEvent(new MoveEvent(start, start, end, end - start, 1));
+ await Canvas.OnEvent(new PointerEvent(EventType.PointerUp, start, end, end, 1));
+
+ // Assert - unlike a tap, an area selection selects every element it contains
+ Assert.AreEqual(2, Canvas.SelectedElements.Count);
+ CollectionAssert.Contains(Canvas.SelectedElements, bottom);
+ CollectionAssert.Contains(Canvas.SelectedElements, top);
+ }
+
[Test]
public async Task IfUserDrawsSelectionRectangle_AndElementsAreContained_ElementsAreSelected()
{
diff --git a/Svg.Editor.Core/Svg.Editor.Core.csproj b/Svg.Editor.Core/Svg.Editor.Core.csproj
index 7ab0cd779..9427c3daa 100644
--- a/Svg.Editor.Core/Svg.Editor.Core.csproj
+++ b/Svg.Editor.Core/Svg.Editor.Core.csproj
@@ -4,9 +4,11 @@
Svg.Editoren-USnet10.0
- 3.2.0-optiq06
+ 3.2.0-optiq07latest
+ #3.2.0-optiq07
+ Tapping a point now only selects the top-most
#3.2.0-optiq06
Improved GetDescendant and ActionSheetDialog has selected item index
#3.2.0-optiq05
diff --git a/Svg.Editor.Core/Tools/SelectionTool.cs b/Svg.Editor.Core/Tools/SelectionTool.cs
index 0ceae6668..5245c3dda 100644
--- a/Svg.Editor.Core/Tools/SelectionTool.cs
+++ b/Svg.Editor.Core/Tools/SelectionTool.cs
@@ -260,10 +260,13 @@ private void SelectElementsUnderArea(RectangleF selectionRectangle, ISvgDrawingC
SelectElementsUnder(selectionRectangle, ws, selectionType, maxItems, 1);
}
- private void SelectElementsUnderPoint(RectangleF selectionRectangle, ISvgDrawingCanvas ws, SelectionType selectionType, int maxItems = int.MaxValue)
+ private void SelectElementsUnderPoint(RectangleF selectionRectangle, ISvgDrawingCanvas ws, SelectionType selectionType, int maxItems = 1)
{
// when selecting by tapping a point, we do want an element to be selected, if the point intersects with ANY child element of it
// therefore we need "recursionLevel=max"!
+ // a tap is a single point, so it should only ever select the top-most (highest z-order) element under
+ // it, not every overlapping element - that's what dragging out a selection area is for (see SelectElementsUnderArea).
+ // HitTest already returns matches in top-to-bottom z-order, so taking just the first one is correct.
SelectElementsUnder(selectionRectangle, ws, selectionType, maxItems, int.MaxValue);
}
diff --git a/Svg.Tests.Win/SvgHitTests.cs b/Svg.Tests.Win/SvgHitTests.cs
index 86436681d..bfacd411a 100644
--- a/Svg.Tests.Win/SvgHitTests.cs
+++ b/Svg.Tests.Win/SvgHitTests.cs
@@ -390,5 +390,28 @@ public void Contains_Group_HasTransformations_IsHit(float x, float y, float wh,
}
}
}
+
+ [Test]
+ public void Intersect_TwoLayeredRectangles_OnlySelectsTopLayerRectangle()
+ {
+ // Arrange
+ // "top" is declared last, so it is rendered on top of (and occludes) "bottom"
+ var rawSvg = @"";
+ var svg = SvgDocument.FromSvg(rawSvg);
+ var rect = RectangleF.Create(150f, 150f, 10, 10);
+
+ // Act
+ var result = svg.HitTest(rect, SelectionType.Intersect, HitTestResultMode.ReturnAllMatchingDescendants)
+ .ToArray();
+
+ // Assert
+ // both rectangles are hit, but the top-most (highest z-order) one must be reported first,
+ // so that a caller who only cares about a single selection (e.g. result.First()) selects "top"
+ result.Count().ShouldBe(2);
+ result[0].ID.ShouldBe("top");
+ }
}
}
diff --git a/Svg/Svg.csproj b/Svg/Svg.csproj
index 0bd909b58..6cd3c3d8d 100644
--- a/Svg/Svg.csproj
+++ b/Svg/Svg.csproj
@@ -3,10 +3,12 @@
netstandard2.0;net48;net10.0PackageReference
- 3.2.0-optiq06
+ 3.2.0-optiq07gentledpp,zeprOpti-Q GmbH
+ #3.2.0-optiq07
+ Tapping a point now only selects the top-most
#3.2.0-optiq06
Improved GetDescendant and ActionSheetDialog has selected item index
#3.2.0-optiq05