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 f6354bc612..17541866ec 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using Humanizer; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,6 +19,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components @@ -105,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (action.ActionMethod) { case PlatformActionMethod.Delete: - return deleteSelected(); + return DeleteSelected(); } return false; @@ -126,7 +128,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - private bool deleteSelected() + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + + public bool DeleteSelected() { List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); @@ -134,7 +139,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components 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) @@ -169,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return new MenuItem[] { - new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()), + new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => DeleteSelected()), new OsuMenuItem("Curve type") { Items = items diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index f851c7bfc9..9b758ec898 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -72,6 +73,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + public override bool HandleQuickDeletion() + { + var hoveredControlPoint = ControlPointVisualiser.Pieces.FirstOrDefault(p => p.IsHovered); + + if (hoveredControlPoint == null) + return false; + + hoveredControlPoint.IsSelected.Value = true; + ControlPointVisualiser.DeleteSelected(); + return true; + } + protected override void Update() { base.Update(); @@ -109,9 +122,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders rightClickPosition = e.MouseDownPosition; return false; // Allow right click to be handled by context menu - case MouseButton.Left when e.ControlPressed && IsSelected: - placementControlPointIndex = addControlPoint(e.MousePosition); - return true; // Stop input from being handled and modifying the selection + case MouseButton.Left: + if (e.ControlPressed && IsSelected) + { + placementControlPointIndex = addControlPoint(e.MousePosition); + return true; // Stop input from being handled and modifying the selection + } + + break; } return false; @@ -216,7 +234,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index f3816f6218..99cdca045b 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -143,5 +143,11 @@ namespace osu.Game.Rulesets.Edit public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; public virtual Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; + + /// + /// Handle to perform a partial deletion when the user requests a quick delete (Shift+Right Click). + /// + /// True if the deletion operation was handled by this blueprint. Returning false will delete the full blueprint. + public virtual bool HandleQuickDeletion() => false; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index fa98358dbe..e7da220946 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -116,7 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { - beginClickSelection(e); + if (!beginClickSelection(e)) return true; + prepareSelectionMovement(); return e.Button == MouseButton.Left; @@ -291,19 +292,24 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Attempts to select any hovered blueprints. /// /// The input event that triggered this selection. - private void beginClickSelection(MouseButtonEvent e) + /// Whether a selection was performed. + private bool beginClickSelection(MouseButtonEvent e) { Debug.Assert(!clickSelectionBegan); + bool selectedPerformed = true; + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (blueprint.IsHovered) { - SelectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + selectedPerformed &= SelectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); clickSelectionBegan = true; break; } } + + return selectedPerformed; } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 406ca07185..0bbbfaf5e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -219,18 +219,28 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The blueprint. /// The input state at the point of selection. - internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + /// Whether a selection was performed. + internal bool HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + { handleQuickDeletion(blueprint); - else if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) + return false; + } + + if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) blueprint.ToggleSelection(); else ensureSelected(blueprint); + + return true; } private void handleQuickDeletion(SelectionBlueprint blueprint) { + if (blueprint.HandleQuickDeletion()) + return; + if (!blueprint.IsSelected) EditorBeatmap.Remove(blueprint.HitObject); else