diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 6bfe7f892b..e7bcd2cadc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -1,7 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using Humanizer; using NUnit.Framework; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -47,6 +50,126 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddBlueprint(new TestSliderBlueprint(slider), drawableObject); }); + [Test] + public void TestSelection() + { + moveMouseToControlPoint(0); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + assertSelectionCount(1); + assertSelected(0); + + AddStep("click right mouse", () => InputManager.Click(MouseButton.Right)); + assertSelectionCount(1); + assertSelected(0); + + moveMouseToControlPoint(3); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + assertSelectionCount(1); + assertSelected(3); + + AddStep("press control", () => InputManager.PressKey(Key.ControlLeft)); + moveMouseToControlPoint(2); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + assertSelectionCount(2); + assertSelected(2); + assertSelected(3); + + moveMouseToControlPoint(0); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + assertSelectionCount(3); + assertSelected(0); + assertSelected(2); + assertSelected(3); + + AddStep("click right mouse", () => InputManager.Click(MouseButton.Right)); + assertSelectionCount(3); + assertSelected(0); + assertSelected(2); + assertSelected(3); + + AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + assertSelectionCount(1); + assertSelected(0); + + moveMouseToRelativePosition(new Vector2(350, 0)); + AddStep("ctrl+click to create new point", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressButton(MouseButton.Left); + }); + assertSelectionCount(1); + assertSelected(3); + + AddStep("release ctrl+click", () => + { + InputManager.ReleaseButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + assertSelectionCount(1); + assertSelected(3); + } + + [Test] + public void TestNewControlPointCreation() + { + moveMouseToRelativePosition(new Vector2(350, 0)); + AddStep("ctrl+click to create new point", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressButton(MouseButton.Left); + }); + AddAssert("slider has 6 control points", () => slider.Path.ControlPoints.Count == 6); + AddStep("release ctrl+click", () => + { + InputManager.ReleaseButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + // ensure that the next drag doesn't attempt to move the placement that just finished. + moveMouseToRelativePosition(new Vector2(0, 50)); + AddStep("press left mouse", () => InputManager.PressButton(MouseButton.Left)); + moveMouseToRelativePosition(new Vector2(0, 100)); + AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + assertControlPointPosition(3, new Vector2(350, 0)); + + moveMouseToRelativePosition(new Vector2(400, 75)); + AddStep("ctrl+click to create new point", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressButton(MouseButton.Left); + }); + AddAssert("slider has 7 control points", () => slider.Path.ControlPoints.Count == 7); + moveMouseToRelativePosition(new Vector2(350, 75)); + AddStep("release ctrl+click", () => + { + InputManager.ReleaseButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + assertControlPointPosition(5, new Vector2(350, 75)); + + // ensure that the next drag doesn't attempt to move the placement that just finished. + moveMouseToRelativePosition(new Vector2(0, 50)); + AddStep("press left mouse", () => InputManager.PressButton(MouseButton.Left)); + moveMouseToRelativePosition(new Vector2(0, 100)); + AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + assertControlPointPosition(5, new Vector2(350, 75)); + } + + private void assertSelectionCount(int count) => + AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == count); + + private void assertSelected(int index) => + AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected", + () => this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value); + + private void moveMouseToRelativePosition(Vector2 relativePosition) => + AddStep($"move mouse to {relativePosition}", () => + { + Vector2 position = slider.Position + relativePosition; + InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); + }); + [Test] public void TestDragControlPoint() { @@ -60,6 +183,83 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.PerfectCurve); } + [Test] + public void TestDragMultipleControlPoints() + { + moveMouseToControlPoint(2); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + AddStep("hold control", () => InputManager.PressKey(Key.LControl)); + + moveMouseToControlPoint(3); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + moveMouseToControlPoint(4); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + moveMouseToControlPoint(2); + AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); + + AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + + addMovementStep(new Vector2(450, 50)); + AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + + assertControlPointPosition(2, new Vector2(450, 50)); + assertControlPointType(2, PathType.PerfectCurve); + + assertControlPointPosition(3, new Vector2(550, 50)); + + assertControlPointPosition(4, new Vector2(550, 200)); + + AddStep("release control", () => InputManager.ReleaseKey(Key.LControl)); + } + + [Test] + public void TestDragMultipleControlPointsIncludingHead() + { + moveMouseToControlPoint(0); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + AddStep("hold control", () => InputManager.PressKey(Key.LControl)); + + moveMouseToControlPoint(3); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + moveMouseToControlPoint(4); + AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + moveMouseToControlPoint(3); + AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); + + AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + + addMovementStep(new Vector2(550, 50)); + AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + + // note: if the head is part of the selection being moved, the entire slider is moved. + // the unselected nodes will therefore change position relative to the slider head. + + AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50))); + + assertControlPointPosition(0, Vector2.Zero); + assertControlPointType(0, PathType.PerfectCurve); + + assertControlPointPosition(1, new Vector2(0, 100)); + + assertControlPointPosition(2, new Vector2(150, -50)); + + assertControlPointPosition(3, new Vector2(400, 0)); + + assertControlPointPosition(4, new Vector2(400, 150)); + + AddStep("release control", () => InputManager.ReleaseKey(Key.LControl)); + } + [Test] public void TestDragControlPointAlmostLinearlyExterior() { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index b3e0566a98..3709cd3a7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -16,11 +16,9 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -33,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointPiece : BlueprintPiece, IHasTooltip { public Action RequestSelection; + + public Action DragStarted; + public Action DragInProgress; + public Action DragEnded; + public List PointsInSegment; public readonly BindableBool IsSelected = new BindableBool(); @@ -42,12 +45,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; - [Resolved(CanBeNull = true)] - private IEditorChangeHandler changeHandler { get; set; } - - [Resolved(CanBeNull = true)] - private IPositionSnapProvider snapProvider { get; set; } - [Resolved] private OsuColour colours { get; set; } @@ -138,6 +135,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components updateMarkerDisplay(); } + // Used to pair up mouse down/drag events with their corresponding mouse up events, + // to avoid deselecting the piece by accident when the mouse up corresponding to the mouse down/drag fires. + private bool keepSelection; + protected override bool OnMouseDown(MouseDownEvent e) { if (RequestSelection == null) @@ -146,22 +147,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Button) { case MouseButton.Left: + // if control is pressed, do not do anything as the user may be adding to current selection + // or dragging all currently selected control points. + // if it isn't and the user's intent is to deselect, deselection will happen on mouse up. + if (e.ControlPressed && IsSelected.Value) + return true; + RequestSelection.Invoke(this, e); + keepSelection = true; + return true; case MouseButton.Right: if (!IsSelected.Value) RequestSelection.Invoke(this, e); + + keepSelection = true; return false; // Allow context menu to show } return false; } - protected override bool OnClick(ClickEvent e) => RequestSelection != null; + protected override void OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); - private Vector2 dragStartPosition; - private PathType? dragPathType; + // ctrl+click deselects this piece, but only if this event + // wasn't immediately preceded by a matching mouse down or drag. + if (IsSelected.Value && e.ControlPressed && !keepSelection) + IsSelected.Value = false; + + keepSelection = false; + } + + protected override bool OnClick(ClickEvent e) => RequestSelection != null; protected override bool OnDragStart(DragStartEvent e) { @@ -170,54 +190,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (e.Button == MouseButton.Left) { - dragStartPosition = ControlPoint.Position; - dragPathType = PointsInSegment[0].Type; - - changeHandler?.BeginChange(); + DragStarted?.Invoke(ControlPoint); + keepSelection = true; return true; } return false; } - protected override void OnDrag(DragEvent e) - { - Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray(); - var oldPosition = slider.Position; - double oldStartTime = slider.StartTime; + protected override void OnDrag(DragEvent e) => DragInProgress?.Invoke(e); - if (ControlPoint == slider.Path.ControlPoints[0]) - { - // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account - var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition); - - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position; - - slider.Position += movementDelta; - slider.StartTime = result?.Time ?? slider.StartTime; - - // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta - for (int i = 1; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position -= movementDelta; - } - else - ControlPoint.Position = dragStartPosition + (e.MousePosition - e.MouseDownPosition); - - if (!slider.Path.HasValidLength) - { - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position = oldControlPoints[i]; - - slider.Position = oldPosition; - slider.StartTime = oldStartTime; - return; - } - - // Maintain the path type in case it got defaulted to bezier at some point during the drag. - PointsInSegment[0].Type = dragPathType; - } - - protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); + protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke(); private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint); 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 1be9b5bf2e..065d4737a5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using Humanizer; using osu.Framework.Allocation; @@ -16,6 +17,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -40,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action> RemoveControlPointsRequested; + [Resolved(CanBeNull = true)] + private IPositionSnapProvider snapProvider { get; set; } + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; @@ -64,6 +69,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components controlPoints.BindTo(slider.Path.ControlPoints); } + /// + /// Selects the corresponding to the given , + /// and deselects all other s. + /// + public void SetSelectionTo(PathControlPoint pathControlPoint) + { + foreach (var p in Pieces) + p.IsSelected.Value = p.ControlPoint == pathControlPoint; + } + + /// + /// Delete all visually selected s. + /// + /// + public bool DeleteSelected() + { + List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); + + // Ensure that there are any points to be deleted + if (toRemove.Count == 0) + return false; + + 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; + } + private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) @@ -87,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Pieces.Add(new PathControlPointPiece(slider, point).With(d => { if (allowSelection) - d.RequestSelection = selectPiece; + d.RequestSelection = selectionRequested; + + d.DragStarted = dragStarted; + d.DragInProgress = dragInProgress; + d.DragEnded = dragEnded; })); Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); @@ -119,6 +161,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnClick(ClickEvent e) { + if (Pieces.Any(piece => piece.IsHovered)) + return false; + foreach (var piece in Pieces) { piece.IsSelected.Value = false; @@ -142,15 +187,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { } - private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) + private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) piece.IsSelected.Toggle(); else - { - foreach (var p in Pieces) - p.IsSelected.Value = p == piece; - } + SetSelectionTo(piece.ControlPoint); } /// @@ -184,25 +226,82 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } - public bool DeleteSelected() - { - List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); + #region Drag handling - // Ensure that there are any points to be deleted - if (toRemove.Count == 0) - return false; + private Vector2[] dragStartPositions; + private PathType?[] dragPathTypes; + private int draggedControlPointIndex; + private HashSet selectedControlPoints; + + private void dragStarted(PathControlPoint controlPoint) + { + dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray(); + dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray(); + draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint); + selectedControlPoints = new HashSet(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint)); + + Debug.Assert(draggedControlPointIndex >= 0); 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; } + private void dragInProgress(DragEvent e) + { + Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray(); + var oldPosition = slider.Position; + double oldStartTime = slider.StartTime; + + if (selectedControlPoints.Contains(slider.Path.ControlPoints[0])) + { + // Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account + Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); + var result = snapProvider?.SnapScreenSpacePositionToValidTime(newHeadPosition); + + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position; + + slider.Position += movementDelta; + slider.StartTime = result?.Time ?? slider.StartTime; + + for (int i = 1; i < slider.Path.ControlPoints.Count; i++) + { + var controlPoint = slider.Path.ControlPoints[i]; + // Since control points are relative to the position of the slider, all points that are _not_ selected + // need to be offset _back_ by the delta corresponding to the movement of the head point. + // All other selected control points (if any) will move together with the head point + // (and so they will not move at all, relative to each other). + if (!selectedControlPoints.Contains(controlPoint)) + controlPoint.Position -= movementDelta; + } + } + else + { + for (int i = 0; i < controlPoints.Count; ++i) + { + var controlPoint = controlPoints[i]; + if (selectedControlPoints.Contains(controlPoint)) + controlPoint.Position = dragStartPositions[i] + (e.MousePosition - e.MouseDownPosition); + } + } + + if (!slider.Path.HasValidLength) + { + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position = oldControlPoints[i]; + + slider.Position = oldPosition; + slider.StartTime = oldStartTime; + return; + } + + // Maintain the path types in case they got defaulted to bezier at some point during the drag. + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Type = dragPathTypes[i]; + } + + private void dragEnded() => changeHandler?.EndChange(); + + #endregion + public MenuItem[] ContextMenuItems { get diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 17a62fc61c..2aebe05c2f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -140,7 +139,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case MouseButton.Left: if (e.ControlPressed && IsSelected) { - placementControlPointIndex = addControlPoint(e.MousePosition); + changeHandler?.BeginChange(); + placementControlPoint = addControlPoint(e.MousePosition); + ControlPointVisualiser?.SetSelectionTo(placementControlPoint); return true; // Stop input from being handled and modifying the selection } @@ -150,31 +151,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return false; } - private int? placementControlPointIndex; + [CanBeNull] + private PathControlPoint placementControlPoint; - protected override bool OnDragStart(DragStartEvent e) - { - if (placementControlPointIndex != null) - { - changeHandler?.BeginChange(); - return true; - } - - return false; - } + protected override bool OnDragStart(DragStartEvent e) => placementControlPoint != null; protected override void OnDrag(DragEvent e) { - Debug.Assert(placementControlPointIndex != null); - - HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position = e.MousePosition - HitObject.Position; + if (placementControlPoint != null) + placementControlPoint.Position = e.MousePosition - HitObject.Position; } - protected override void OnDragEnd(DragEndEvent e) + protected override void OnMouseUp(MouseUpEvent e) { - if (placementControlPointIndex != null) + if (placementControlPoint != null) { - placementControlPointIndex = null; + placementControlPoint = null; changeHandler?.EndChange(); } } @@ -193,7 +185,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return false; } - private int addControlPoint(Vector2 position) + private PathControlPoint addControlPoint(Vector2 position) { position -= HitObject.Position; @@ -211,10 +203,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - // Move the control points from the insertion index onwards to make room for the insertion - controlPoints.Insert(insertionIndex, new PathControlPoint { Position = position }); + var pathControlPoint = new PathControlPoint { Position = position }; - return insertionIndex; + // Move the control points from the insertion index onwards to make room for the insertion + controlPoints.Insert(insertionIndex, pathControlPoint); + + return pathControlPoint; } private void removeControlPoints(List toRemove)