From aa67f87fe95af769c66e5329b30212d07b8e3ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 09:42:24 +0100 Subject: [PATCH 1/2] Add failing test coverage --- .../Editor/TestSceneOsuComposerSelection.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 345965b91268..5aa7d6865ffd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; @@ -261,6 +262,90 @@ public void TestControlClickDoesNotDiscardExistingSelectionEvenIfNothingHit() AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1)); } + [Test] + public void TestQuickDeleteOnUnselectedControlPointOnlyRemovesThatControlPoint() + { + var slider = new Slider + { + StartTime = 0, + Position = new Vector2(100, 100), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint { Type = PathType.LINEAR }, + new PathControlPoint(new Vector2(100, 0)), + new PathControlPoint(new Vector2(100)), + new PathControlPoint(new Vector2(0, 100)) + } + } + }; + AddStep("add slider", () => EditorBeatmap.Add(slider)); + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("select second node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddStep("also select third node", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(2)); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddStep("quick-delete fourth node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(3)); + InputManager.Click(MouseButton.Middle); + }); + AddUntilStep("slider not deleted", () => EditorBeatmap.HitObjects.OfType().Count(), () => Is.EqualTo(1)); + AddUntilStep("slider path has 3 nodes", () => EditorBeatmap.HitObjects.OfType().Single().Path.ControlPoints.Count, () => Is.EqualTo(3)); + } + + [Test] + public void TestQuickDeleteOnSelectedControlPointRemovesEntireSelection() + { + var slider = new Slider + { + StartTime = 0, + Position = new Vector2(100, 100), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint { Type = PathType.LINEAR }, + new PathControlPoint(new Vector2(100, 0)), + new PathControlPoint(new Vector2(100)), + new PathControlPoint(new Vector2(0, 100)) + } + } + }; + AddStep("add slider", () => EditorBeatmap.Add(slider)); + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("select second node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddStep("also select third node", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(2)); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddStep("quick-delete second node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1)); + InputManager.Click(MouseButton.Middle); + }); + AddUntilStep("slider not deleted", () => EditorBeatmap.HitObjects.OfType().Count(), () => Is.EqualTo(1)); + AddUntilStep("slider path has 2 nodes", () => EditorBeatmap.HitObjects.OfType().Single().Path.ControlPoints.Count, () => Is.EqualTo(2)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From 182f998f9b9069e52ab2b76e70bc47d4f4a0101c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 09:42:48 +0100 Subject: [PATCH 2/2] Fix quick-deleting unselected slider path control point also deleting all selected control points Closes https://github.com/ppy/osu/issues/31308. Logic matches corresponding quick-delete logic in https://github.com/ppy/osu/blob/130802e48048c134c6c8f19c77e3e032834acf72/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs#L307-L316. --- .../Components/PathControlPointVisualiser.cs | 23 ++++++++++++++----- .../Sliders/SliderSelectionBlueprint.cs | 7 ++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f11451630025..f98117c0faa9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -137,11 +137,27 @@ public void SetSelectionTo(PathControlPoint pathControlPoint) /// /// Delete all visually selected s. /// - /// + /// Whether any change actually took place. public bool DeleteSelected() { List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); + if (!Delete(toRemove)) + return false; + + // Since pieces are re-used, they will not point to the deleted control points while remaining selected + foreach (var piece in Pieces) + piece.IsSelected.Value = false; + + return true; + } + + /// + /// Delete the specified s. + /// + /// Whether any change actually took place. + public bool Delete(List toRemove) + { // Ensure that there are any points to be deleted if (toRemove.Count == 0) return false; @@ -149,11 +165,6 @@ public bool DeleteSelected() changeHandler?.BeginChange(); RemoveControlPointsRequested?.Invoke(toRemove); changeHandler?.EndChange(); - - // Since pieces are re-used, they will not point to the deleted control points while remaining selected - foreach (var piece in Pieces) - piece.IsSelected.Value = false; - return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 02f76b51b06d..3504954bec0f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -140,8 +140,11 @@ public override bool HandleQuickDeletion() if (hoveredControlPoint == null) return false; - hoveredControlPoint.IsSelected.Value = true; - ControlPointVisualiser?.DeleteSelected(); + if (hoveredControlPoint.IsSelected.Value) + ControlPointVisualiser?.DeleteSelected(); + else + ControlPointVisualiser?.Delete([hoveredControlPoint.ControlPoint]); + return true; }