From 0422dc71cce430a908e92a628aab973b7e5076d6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 10 Oct 2024 14:59:46 +0200 Subject: [PATCH] Implement variant type generic proxies without heap allocations --- .../Components/PathControlPointVisualiser.cs | 32 ++--- .../Sliders/SliderSelectionBlueprint.cs | 44 +++---- .../Edit/Commands/AddControlPointCommand.cs | 29 ----- .../Edit/Commands/OsuHitObjectCommandProxy.cs | 24 ++-- .../Commands/PathControlPointCommandProxy.cs | 34 ------ .../Commands/PathControlPointsCommandProxy.cs | 113 ----------------- .../Commands/RemoveControlPointCommand.cs | 32 ----- .../Commands/SetExpectedDistanceCommand.cs | 25 ---- .../Edit/Commands/SliderCommandProxy.cs | 19 +-- .../Edit/Commands/SliderPathCommandProxy.cs | 31 ----- .../Rulesets/Objects/SliderPathExtensions.cs | 13 ++ .../Screens/Edit/Commands/CommandProxy.cs | 115 +++++++++++++++++- .../Edit/Commands/HitObjectCommandProxy.cs | 23 ++-- .../Screens/Edit/Commands/InsertCommand.cs | 27 ++++ .../Commands/PathControlPointCommandProxy.cs | 20 +++ .../Screens/Edit/Commands/RemoveCommand.cs | 30 +++++ .../SetSliderVelocityMultiplierCommand.cs | 30 +++++ .../Edit/Commands/SliderPathCommandProxy.cs | 21 ++++ .../Commands/UpdateControlPointCommand.cs | 3 +- 19 files changed, 309 insertions(+), 356 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/Commands/AddControlPointCommand.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointCommandProxy.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointsCommandProxy.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Commands/RemoveControlPointCommand.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Commands/SetExpectedDistanceCommand.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Commands/SliderPathCommandProxy.cs create mode 100644 osu.Game/Screens/Edit/Commands/InsertCommand.cs create mode 100644 osu.Game/Screens/Edit/Commands/PathControlPointCommandProxy.cs create mode 100644 osu.Game/Screens/Edit/Commands/RemoveCommand.cs create mode 100644 osu.Game/Screens/Edit/Commands/SetSliderVelocityMultiplierCommand.cs create mode 100644 osu.Game/Screens/Edit/Commands/SliderPathCommandProxy.cs rename {osu.Game.Rulesets.Osu => osu.Game/Screens}/Edit/Commands/UpdateControlPointCommand.cs (93%) 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 107aa8b086..624fd28c85 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Commands; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Commands; using osuTK; using osuTK.Input; @@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly T hitObject; private readonly bool allowSelection; - private SliderPathCommandProxy pathProxy = null!; + private CommandProxy proxy; private InputManager inputManager; @@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components controlPoints.CollectionChanged += onControlPointsChanged; controlPoints.BindTo(hitObject.Path.ControlPoints); - pathProxy = new SliderPathCommandProxy(commandHandler, hitObject.Path); + proxy = new CommandProxy(commandHandler, hitObject); } // Generally all the control points are within the visible area all the time. @@ -410,8 +411,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public void DragInProgress(DragEvent e) { - var proxy = new OsuHitObjectCommandProxy(commandHandler, hitObject); - var controlPointsProxy = pathProxy.ControlPoints; + var controlPointsProxy = proxy.Path().ControlPoints(); Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray(); Vector2 oldPosition = hitObject.Position; double oldStartTime = hitObject.StartTime; @@ -424,8 +424,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position; - proxy.Position += movementDelta; - proxy.StartTime = result?.Time ?? hitObject.StartTime; + proxy.SetPosition(hitObject.Position + movementDelta); + proxy.SetStartTime(result?.Time ?? hitObject.StartTime); for (int i = 1; i < controlPointsProxy.Count; i++) { @@ -434,8 +434,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // 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(controlPointProxy.ControlPoint)) - controlPointProxy.Position -= movementDelta; + if (!selectedControlPoints.Contains(controlPointProxy.Target)) + controlPointProxy.SetPosition(controlPointProxy.Position() - movementDelta); } } else @@ -447,29 +447,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components for (int i = 0; i < controlPointsProxy.Count; ++i) { var controlPointProxy = controlPointsProxy[i]; - if (selectedControlPoints.Contains(controlPointProxy.ControlPoint)) - controlPointProxy.Position = dragStartPositions[i] + movementDelta; + if (selectedControlPoints.Contains(controlPointProxy.Target)) + controlPointProxy.SetPosition(dragStartPositions[i] + movementDelta); } } // Snap the path to the current beat divisor before checking length validity. - hitObject.SnapTo(distanceSnapProvider, commandHandler); + proxy.SnapTo(distanceSnapProvider); if (!hitObject.Path.HasValidLength) { for (int i = 0; i < controlPointsProxy.Count; i++) - controlPointsProxy[i].Position = oldControlPoints[i]; + controlPointsProxy[i].SetPosition(oldControlPoints[i]); - proxy.Position = oldPosition; - proxy.StartTime = oldStartTime; + proxy.SetPosition(oldPosition); + proxy.SetStartTime(oldStartTime); // Snap the path length again to undo the invalid length. - hitObject.SnapTo(distanceSnapProvider, commandHandler); + proxy.SnapTo(distanceSnapProvider); return; } // Maintain the path types in case they got defaulted to bezier at some point during the drag. for (int i = 0; i < controlPointsProxy.Count; i++) - controlPointsProxy[i].Type = dragPathTypes[i]; + controlPointsProxy[i].SetType(dragPathTypes[i]); EnsureValidPathTypes(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a4e7bfcccc..72c3e9a965 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject; - protected SliderCommandProxy Proxy = null!; + protected CommandProxy Proxy { get; private set; } protected SliderBodyPiece BodyPiece { get; private set; } = null!; protected SliderCircleOverlay HeadOverlay { get; private set; } = null!; @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.LoadComplete(); - Proxy = new SliderCommandProxy(commandHandler, HitObject); + Proxy = new CommandProxy(commandHandler, HitObject); controlPoints.BindTo(HitObject.Path.ControlPoints); controlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate(); @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void endAdjustLength() { - trimExcessControlPoints(Proxy.Path); + trimExcessControlPoints(Proxy.Path()); commandHandler?.Commit(); isAdjustingLength = false; } @@ -281,8 +281,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier)) return; - Proxy.SliderVelocityMultiplier = proposedVelocity; - Proxy.Path.ExpectedDistance = proposedDistance; + Proxy.SetSliderVelocityMultiplier(proposedVelocity); + Proxy.Path().SetExpectedDistance(proposedDistance); editorBeatmap?.Update(HitObject); } @@ -290,22 +290,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider. /// /// The slider path to trim control points of. - private void trimExcessControlPoints(SliderPathCommandProxy sliderPath) + private void trimExcessControlPoints(CommandProxy sliderPath) { - if (!sliderPath.ExpectedDistance.HasValue) + if (!sliderPath.ExpectedDistance().HasValue) return; double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); int segmentIndex = 0; - for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++) + for (int i = 1; i < sliderPath.ControlPoints().Count - 1; i++) { - if (!sliderPath.ControlPoints[i].Type.HasValue) continue; + if (!sliderPath.ControlPoints()[i].Type().HasValue) continue; if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3)) { - sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1); - sliderPath.ControlPoints[^1].Type = null; + sliderPath.ControlPoints().RemoveRange(i + 1, sliderPath.ControlPoints().Count - i - 1); + sliderPath.ControlPoints()[^1].SetType(null); break; } @@ -439,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var pathControlPoint = new PathControlPoint { Position = position }; - Proxy.Path.ControlPoints.Insert(insertionIndex, pathControlPoint); + Proxy.Path().ControlPoints().Insert(insertionIndex, pathControlPoint); ControlPointVisualiser?.EnsureValidPathTypes(); @@ -459,9 +459,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // The first control point in the slider must have a type, so take it from the previous "first" one // Todo: Should be handled within SliderPath itself if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type == null) - new PathControlPointCommandProxy(commandHandler, c).Type = controlPoints[0].Type; + new CommandProxy(commandHandler, c).SetType(controlPoints[0].Type); - Proxy.Path.ControlPoints.Remove(c); + Proxy.Path().ControlPoints().Remove(c); } ControlPointVisualiser?.EnsureValidPathTypes(); @@ -499,7 +499,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders editorBeatmap.SelectedHitObjects.Clear(); - var controlPointsProxy = new PathControlPointsCommandProxy(commandHandler, controlPoints); + var controlPointsProxy = new ListCommandProxy, PathControlPoint, CommandProxy>(commandHandler, controlPoints); foreach (var splitPoint in controlPointsToSplitAt) { @@ -527,19 +527,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Path = new SliderPath(splitControlPoints.Select(o => new PathControlPoint(o.Position - splitControlPoints[0].Position, o == splitControlPoints[^1] ? null : o.Type)).ToArray()) }; - Proxy.StartTime += split_gap; + Proxy.SetStartTime(Proxy.StartTime() + split_gap); // Increase the start time of the slider before adding the new slider so the new slider is immediately inserted at the correct index and internal state remains valid. commandHandler.SafeSubmit(new AddHitObjectCommand(editorBeatmap, newSlider)); - Proxy.NewCombo = false; - Proxy.Path.ExpectedDistance -= newSlider.Path.CalculatedDistance; - Proxy.StartTime += newSlider.SpanDuration; + Proxy.SetNewCombo(false); + Proxy.Path().SetExpectedDistance(Proxy.Path().ExpectedDistance() - newSlider.Path.CalculatedDistance); + Proxy.SetStartTime(Proxy.StartTime() + newSlider.SpanDuration); // In case the remainder of the slider has no length left over, give it length anyways so we don't get a 0 length slider. if (HitObject.Path.ExpectedDistance.Value <= Precision.DOUBLE_EPSILON) { - Proxy.Path.ExpectedDistance = null; + Proxy.Path().SetExpectedDistance(null); } } @@ -547,9 +547,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // As a final step, we must reset its control points to have an origin of (0,0). Vector2 first = controlPoints[0].Position; foreach (var c in controlPointsProxy) - c.Position -= first; + c.SetPosition(c.Position() - first); - Proxy.Position += first; + Proxy.SetPosition(Proxy.Position() + first); } private void convertToStream() diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/AddControlPointCommand.cs b/osu.Game.Rulesets.Osu/Edit/Commands/AddControlPointCommand.cs deleted file mode 100644 index 0146674f96..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Commands/AddControlPointCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Commands; - -namespace osu.Game.Rulesets.Osu.Edit.Commands -{ - public class AddControlPointCommand : IEditorCommand - { - public readonly BindableList ControlPoints; - - public readonly int InsertionIndex; - - public readonly PathControlPoint ControlPoint; - - public AddControlPointCommand(BindableList controlPoints, int insertionIndex, PathControlPoint controlPoint) - { - ControlPoints = controlPoints; - InsertionIndex = insertionIndex; - ControlPoint = controlPoint; - } - - public void Apply() => ControlPoints.Insert(InsertionIndex, ControlPoint); - - public IEditorCommand CreateUndo() => new RemoveControlPointCommand(ControlPoints, InsertionIndex); - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/OsuHitObjectCommandProxy.cs b/osu.Game.Rulesets.Osu/Edit/Commands/OsuHitObjectCommandProxy.cs index c1e1796b0f..c4847a3e78 100644 --- a/osu.Game.Rulesets.Osu/Edit/Commands/OsuHitObjectCommandProxy.cs +++ b/osu.Game.Rulesets.Osu/Edit/Commands/OsuHitObjectCommandProxy.cs @@ -2,31 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Commands; using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Commands { - public class OsuHitObjectCommandProxy : HitObjectCommandProxy + public static class OsuHitObjectCommandProxy { - public OsuHitObjectCommandProxy(EditorCommandHandler? commandHandler, OsuHitObject hitObject) - : base(commandHandler, hitObject) - { - } + public static Vector2 Position(this CommandProxy proxy) where T : OsuHitObject => proxy.Target.Position; - protected new OsuHitObject HitObject => (OsuHitObject)base.HitObject; + public static void SetPosition(this CommandProxy proxy, Vector2 value) where T : OsuHitObject => + proxy.Submit(new MoveCommand(proxy.Target, value)); - public Vector2 Position - { - get => HitObject.Position; - set => Submit(new MoveCommand(HitObject, value)); - } + public static bool NewCombo(this CommandProxy proxy) where T : OsuHitObject => proxy.Target.NewCombo; - public bool NewCombo - { - get => HitObject.NewCombo; - set => Submit(new SetNewComboCommand(HitObject, value)); - } + public static void SetNewCombo(this CommandProxy proxy, bool value) where T : OsuHitObject => + proxy.Submit(new SetNewComboCommand(proxy.Target, value)); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointCommandProxy.cs b/osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointCommandProxy.cs deleted file mode 100644 index d18c250c46..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointCommandProxy.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Commands; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit.Commands -{ - public class PathControlPointCommandProxy : CommandProxy - { - public PathControlPointCommandProxy(EditorCommandHandler? commandHandler, PathControlPoint controlPoint) - : base(commandHandler) - { - ControlPoint = controlPoint; - } - - public readonly PathControlPoint ControlPoint; - - public Vector2 Position - { - get => ControlPoint.Position; - set => Submit(new UpdateControlPointCommand(ControlPoint) { Position = value }); - } - - public PathType? Type - { - get => ControlPoint.Type; - set => Submit(new UpdateControlPointCommand(ControlPoint) { Type = value }); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointsCommandProxy.cs b/osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointsCommandProxy.cs deleted file mode 100644 index 202bcd7438..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Commands/PathControlPointsCommandProxy.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Commands; - -namespace osu.Game.Rulesets.Osu.Edit.Commands -{ - public class PathControlPointsCommandProxy : CommandProxy, IList - { - public PathControlPointsCommandProxy(EditorCommandHandler? commandHandler, BindableList controlPoints) - : base(commandHandler) - { - ControlPoints = controlPoints; - } - - public readonly BindableList ControlPoints; - - public int IndexOf(PathControlPointCommandProxy item) - { - return ControlPoints.IndexOf(item.ControlPoint); - } - - public void Insert(int index, PathControlPointCommandProxy item) => Insert(index, item.ControlPoint); - - public void Insert(int index, PathControlPoint controlPoint) => Submit(new AddControlPointCommand(ControlPoints, index, controlPoint)); - - public void RemoveAt(int index) => Submit(new RemoveControlPointCommand(ControlPoints, index)); - - public PathControlPointCommandProxy this[int index] - { - get => new PathControlPointCommandProxy(CommandHandler, ControlPoints[index]); - set => Submit(new AddControlPointCommand(ControlPoints, index, value.ControlPoint)); - } - - public void RemoveRange(int index, int count) - { - for (int i = 0; i < count; i++) - Submit(new RemoveControlPointCommand(ControlPoints, index)); - } - - public void Add(PathControlPointCommandProxy item) => Add(item.ControlPoint); - - public void Add(PathControlPoint controlPoint) => Submit(new AddControlPointCommand(ControlPoints, ControlPoints.Count, controlPoint)); - - public void Clear() - { - while (ControlPoints.Count > 0) - Remove(ControlPoints[0]); - } - - public bool Contains(PathControlPointCommandProxy item) - { - return ControlPoints.Any(c => c.Equals(item.ControlPoint)); - } - - public void CopyTo(PathControlPointCommandProxy[] array, int arrayIndex) - { - for (int i = 0; i < ControlPoints.Count; i++) - array[arrayIndex + i] = new PathControlPointCommandProxy(CommandHandler, ControlPoints[i]); - } - - public bool Remove(PathControlPointCommandProxy item) => Remove(item.ControlPoint); - - public bool Remove(PathControlPoint controlPoint) - { - if (!ControlPoints.Contains(controlPoint)) - return false; - - Submit(new RemoveControlPointCommand(ControlPoints, controlPoint)); - return true; - } - - public int Count => ControlPoints.Count; - - public bool IsReadOnly => ControlPoints.IsReadOnly; - - public IEnumerator GetEnumerator() => new PathControlPointsCommandProxyEnumerator(CommandHandler, ControlPoints.GetEnumerator()); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private readonly struct PathControlPointsCommandProxyEnumerator : IEnumerator - { - public PathControlPointsCommandProxyEnumerator( - EditorCommandHandler? commandHandler, - IEnumerator enumerator - ) - { - this.commandHandler = commandHandler; - this.enumerator = enumerator; - } - - private readonly EditorCommandHandler? commandHandler; - - private readonly IEnumerator enumerator; - - public bool MoveNext() => enumerator.MoveNext(); - - public void Reset() => enumerator.Reset(); - - public PathControlPointCommandProxy Current => new PathControlPointCommandProxy(commandHandler, enumerator.Current); - - object IEnumerator.Current => Current; - - public void Dispose() => enumerator.Dispose(); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/RemoveControlPointCommand.cs b/osu.Game.Rulesets.Osu/Edit/Commands/RemoveControlPointCommand.cs deleted file mode 100644 index b9e1d63388..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Commands/RemoveControlPointCommand.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Commands; - -namespace osu.Game.Rulesets.Osu.Edit.Commands -{ - public class RemoveControlPointCommand : IEditorCommand - { - public readonly BindableList ControlPoints; - - public readonly int Index; - - public RemoveControlPointCommand(BindableList controlPoints, int index) - { - ControlPoints = controlPoints; - Index = index; - } - - public RemoveControlPointCommand(BindableList controlPoints, PathControlPoint controlPoint) - { - ControlPoints = controlPoints; - Index = controlPoints.IndexOf(controlPoint); - } - - public void Apply() => ControlPoints.RemoveAt(Index); - - public IEditorCommand CreateUndo() => new AddControlPointCommand(ControlPoints, Index, ControlPoints[Index]); - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/SetExpectedDistanceCommand.cs b/osu.Game.Rulesets.Osu/Edit/Commands/SetExpectedDistanceCommand.cs deleted file mode 100644 index a08d7a4501..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Commands/SetExpectedDistanceCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Commands; - -namespace osu.Game.Rulesets.Osu.Edit.Commands -{ - public class SetSliderVelocityMultiplierCommand : IEditorCommand - { - public readonly Slider Slider; - - public readonly double SliderVelocityMultiplier; - - public SetSliderVelocityMultiplierCommand(Slider slider, double sliderVelocityMultiplier) - { - Slider = slider; - SliderVelocityMultiplier = sliderVelocityMultiplier; - } - - public void Apply() => Slider.SliderVelocityMultiplier = SliderVelocityMultiplier; - - public IEditorCommand CreateUndo() => new SetSliderVelocityMultiplierCommand(Slider, Slider.SliderVelocityMultiplier); - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/SliderCommandProxy.cs b/osu.Game.Rulesets.Osu/Edit/Commands/SliderCommandProxy.cs index 4927a2e804..b84e0fc99d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Commands/SliderCommandProxy.cs +++ b/osu.Game.Rulesets.Osu/Edit/Commands/SliderCommandProxy.cs @@ -1,26 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit; - namespace osu.Game.Rulesets.Osu.Edit.Commands { - public class SliderCommandProxy : OsuHitObjectCommandProxy + public static class SliderCommandProxy { - public SliderCommandProxy(EditorCommandHandler? commandHandler, Slider hitObject) - : base(commandHandler, hitObject) - { - } - - protected new Slider HitObject => (Slider)base.HitObject; - - public SliderPathCommandProxy Path => new SliderPathCommandProxy(CommandHandler, HitObject.Path); - - public double SliderVelocityMultiplier - { - get => HitObject.SliderVelocityMultiplier; - set => Submit(new SetSliderVelocityMultiplierCommand(HitObject, value)); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/SliderPathCommandProxy.cs b/osu.Game.Rulesets.Osu/Edit/Commands/SliderPathCommandProxy.cs deleted file mode 100644 index 9e5ea841a3..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Commands/SliderPathCommandProxy.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Commands; - -namespace osu.Game.Rulesets.Osu.Edit.Commands -{ - public class SliderPathCommandProxy : CommandProxy - { - public SliderPathCommandProxy(EditorCommandHandler? commandHandler, SliderPath path) - : base(commandHandler) - { - Path = path; - } - - public readonly SliderPath Path; - - public double? ExpectedDistance - { - get => Path.ExpectedDistance.Value; - set => Submit(new SetExpectedDistanceCommand(Path, value)); - } - - public PathControlPointsCommandProxy ControlPoints => new PathControlPointsCommandProxy(CommandHandler, Path.ControlPoints); - - public IEnumerable GetSegmentEnds() => Path.GetSegmentEnds(); - } -} diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index c7559e49fd..dec47eb041 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -24,6 +24,19 @@ namespace osu.Game.Rulesets.Objects commandHandler.SafeSubmit(new SetExpectedDistanceCommand(hitObject.Path, distance)); } + /// + /// Snaps the provided 's duration using the . + /// + public static void SnapTo(this CommandProxy proxy, IDistanceSnapProvider? snapProvider) + where THitObject : HitObject, IHasPath + { + var hitObject = proxy.Target; + double distance = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? hitObject.Path.CalculatedDistance; + + proxy.Path().SetExpectedDistance(distance); + proxy.Path().ControlPoints(); + } + /// /// Reverse the direction of this path. /// diff --git a/osu.Game/Screens/Edit/Commands/CommandProxy.cs b/osu.Game/Screens/Edit/Commands/CommandProxy.cs index 1a248ce992..2147d1bfe5 100644 --- a/osu.Game/Screens/Edit/Commands/CommandProxy.cs +++ b/osu.Game/Screens/Edit/Commands/CommandProxy.cs @@ -1,21 +1,126 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Screens.Edit.Commands { - public abstract class CommandProxy + public interface ICommandProxy { - protected EditorCommandHandler? CommandHandler; + EditorCommandHandler? CommandHandler { get; init; } + T Target { get; init; } + void Submit(IEditorCommand command); + } - protected CommandProxy(EditorCommandHandler? commandHandler) + public readonly struct CommandProxy : ICommandProxy + { + public CommandProxy(EditorCommandHandler? commandHandler, T target) { CommandHandler = commandHandler; + Target = target; } - protected void Submit(IEditorCommand command) => CommandHandler.SafeSubmit(command); + public EditorCommandHandler? CommandHandler { get; init; } + public T Target { get; init; } + public void Submit(IEditorCommand command) => CommandHandler.SafeSubmit(command); + } - protected void Submit(IEnumerable command) => CommandHandler.SafeSubmit(command); + public readonly struct ListCommandProxy : ICommandProxy, IList where T : IList where TItemProxy : ICommandProxy, new() + { + public ListCommandProxy(EditorCommandHandler? commandHandler, T target) + { + CommandHandler = commandHandler; + Target = target; + } + + public EditorCommandHandler? CommandHandler { get; init; } + public T Target { get; init; } + public void Submit(IEditorCommand command) => CommandHandler.SafeSubmit(command); + + public IEnumerator GetEnumerator() + { + var commandHandler = CommandHandler; + return Target.Select(o => new TItemProxy { CommandHandler = commandHandler, Target = o }).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(TItemProxy item) + { + Add(item.Target); + } + + public void Add(TItem item) + { + Insert(Count, item); + } + + public void Clear() + { + while (Target.Count > 0) + RemoveAt(Count - 1); + } + + public bool Contains(TItemProxy item) + { + return Target.Contains(item.Target); + } + + public void CopyTo(TItemProxy[] array, int arrayIndex) + { + for (int i = 0; i < Target.Count; i++) + array[arrayIndex + i] = new TItemProxy { CommandHandler = CommandHandler, Target = Target[i] }; + } + + public bool Remove(TItemProxy item) => Remove(item.Target); + + public bool Remove(TItem item) + { + if (!Target.Contains(item)) + return false; + + Submit(new RemoveCommand(Target, item)); + return true; + } + + public int Count => Target.Count; + public bool IsReadOnly => Target.IsReadOnly; + + public int IndexOf(TItemProxy item) + { + return Target.IndexOf(item.Target); + } + + public void Insert(int index, TItemProxy item) + { + Insert(index, item.Target); + } + + public void Insert(int index, TItem item) + { + Submit(new InsertCommand(Target, index, item)); + } + + public void RemoveAt(int index) + { + Submit(new RemoveCommand(Target, index)); + } + + public void RemoveRange(int index, int count) + { + for (int i = 0; i < count; i++) + RemoveAt(index); + } + + public TItemProxy this[int index] + { + get => new TItemProxy { CommandHandler = CommandHandler, Target = Target[index] }; + set => Submit(new InsertCommand(Target, index, value.Target)); + } } } diff --git a/osu.Game/Screens/Edit/Commands/HitObjectCommandProxy.cs b/osu.Game/Screens/Edit/Commands/HitObjectCommandProxy.cs index c07d40b47f..bdd58c2e20 100644 --- a/osu.Game/Screens/Edit/Commands/HitObjectCommandProxy.cs +++ b/osu.Game/Screens/Edit/Commands/HitObjectCommandProxy.cs @@ -2,23 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Commands { - public class HitObjectCommandProxy : CommandProxy + public static class HitObjectCommandProxy { - public HitObjectCommandProxy(EditorCommandHandler? commandHandler, HitObject hitObject) - : base(commandHandler) - { - HitObject = hitObject; - } + public static double StartTime(this CommandProxy proxy) where T : HitObject => proxy.Target.StartTime; - protected HitObject HitObject; + public static void SetStartTime(this CommandProxy proxy, double value) where T : HitObject => proxy.Submit(new SetStartTimeCommand(proxy.Target, value)); - public double StartTime - { - get => HitObject.StartTime; - set => Submit(new SetStartTimeCommand(HitObject, value)); - } + public static CommandProxy Path(this CommandProxy proxy) where T : IHasPath => + new CommandProxy(proxy.CommandHandler, proxy.Target.Path); + + public static double SliderVelocityMultiplier(this CommandProxy proxy) where T : IHasSliderVelocity => proxy.Target.SliderVelocityMultiplier; + + public static void SetSliderVelocityMultiplier(this CommandProxy proxy, double value) where T : IHasSliderVelocity => + proxy.Submit(new SetSliderVelocityMultiplierCommand(proxy.Target, value)); } } diff --git a/osu.Game/Screens/Edit/Commands/InsertCommand.cs b/osu.Game/Screens/Edit/Commands/InsertCommand.cs new file mode 100644 index 0000000000..de3a6dbefd --- /dev/null +++ b/osu.Game/Screens/Edit/Commands/InsertCommand.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; + +namespace osu.Game.Screens.Edit.Commands +{ + public class InsertCommand : IEditorCommand where T : IList + { + public readonly T Target; + + public readonly int InsertionIndex; + + public readonly T2 Item; + + public InsertCommand(T target, int insertionIndex, T2 item) + { + Target = target; + InsertionIndex = insertionIndex; + Item = item; + } + + public void Apply() => Target.Insert(InsertionIndex, Item); + + public IEditorCommand CreateUndo() => new RemoveCommand(Target, InsertionIndex); + } +} diff --git a/osu.Game/Screens/Edit/Commands/PathControlPointCommandProxy.cs b/osu.Game/Screens/Edit/Commands/PathControlPointCommandProxy.cs new file mode 100644 index 0000000000..ec1e6ad84d --- /dev/null +++ b/osu.Game/Screens/Edit/Commands/PathControlPointCommandProxy.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Screens.Edit.Commands +{ + public static class PathControlPointCommandProxy + { + public static Vector2 Position(this CommandProxy proxy) where T : PathControlPoint => proxy.Target.Position; + + public static void SetPosition(this CommandProxy proxy, Vector2 value) => proxy.Submit(new UpdateControlPointCommand(proxy.Target) { Position = value }); + + public static PathType? Type(this CommandProxy proxy) where T : PathControlPoint => proxy.Target.Type; + + public static void SetType(this CommandProxy proxy, PathType? value) => proxy.Submit(new UpdateControlPointCommand(proxy.Target) { Type = value }); + } +} diff --git a/osu.Game/Screens/Edit/Commands/RemoveCommand.cs b/osu.Game/Screens/Edit/Commands/RemoveCommand.cs new file mode 100644 index 0000000000..25547e79f5 --- /dev/null +++ b/osu.Game/Screens/Edit/Commands/RemoveCommand.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; + +namespace osu.Game.Screens.Edit.Commands +{ + public class RemoveCommand : IEditorCommand where T : IList + { + public readonly T Target; + + public readonly int Index; + + public RemoveCommand(T target, int index) + { + Target = target; + Index = index; + } + + public RemoveCommand(T target, T2 item) + { + Target = target; + Index = target.IndexOf(item); + } + + public void Apply() => Target.RemoveAt(Index); + + public IEditorCommand CreateUndo() => new InsertCommand(Target, Index, Target[Index]); + } +} diff --git a/osu.Game/Screens/Edit/Commands/SetSliderVelocityMultiplierCommand.cs b/osu.Game/Screens/Edit/Commands/SetSliderVelocityMultiplierCommand.cs new file mode 100644 index 0000000000..573cfad49d --- /dev/null +++ b/osu.Game/Screens/Edit/Commands/SetSliderVelocityMultiplierCommand.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Screens.Edit.Commands +{ + public class SetSliderVelocityMultiplierCommand : IEditorCommand + { + public readonly IHasSliderVelocity Target; + + public readonly double SliderVelocityMultiplier; + + public SetSliderVelocityMultiplierCommand(IHasSliderVelocity target, double sliderVelocityMultiplier) + { + Target = target; + SliderVelocityMultiplier = sliderVelocityMultiplier; + } + + public void Apply() + { + Target.SliderVelocityMultiplier = SliderVelocityMultiplier; + } + + public IEditorCommand CreateUndo() + { + return new SetSliderVelocityMultiplierCommand(Target, Target.SliderVelocityMultiplier); + } + } +} diff --git a/osu.Game/Screens/Edit/Commands/SliderPathCommandProxy.cs b/osu.Game/Screens/Edit/Commands/SliderPathCommandProxy.cs new file mode 100644 index 0000000000..39e3dca8aa --- /dev/null +++ b/osu.Game/Screens/Edit/Commands/SliderPathCommandProxy.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit.Commands +{ + public static class SliderPathCommandProxy + { + public static double? ExpectedDistance(this CommandProxy proxy) => proxy.Target.ExpectedDistance.Value; + + public static void SetExpectedDistance(this CommandProxy proxy, double? value) => proxy.Submit(new SetExpectedDistanceCommand(proxy.Target, value)); + + public static ListCommandProxy, PathControlPoint, CommandProxy> ControlPoints(this CommandProxy proxy) => + new ListCommandProxy, PathControlPoint, CommandProxy>(proxy.CommandHandler, proxy.Target.ControlPoints); + + public static IEnumerable GetSegmentEnds(this ICommandProxy proxy) => proxy.Target.GetSegmentEnds(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Commands/UpdateControlPointCommand.cs b/osu.Game/Screens/Edit/Commands/UpdateControlPointCommand.cs similarity index 93% rename from osu.Game.Rulesets.Osu/Edit/Commands/UpdateControlPointCommand.cs rename to osu.Game/Screens/Edit/Commands/UpdateControlPointCommand.cs index b1f6a6b5de..cb4dde1437 100644 --- a/osu.Game.Rulesets.Osu/Edit/Commands/UpdateControlPointCommand.cs +++ b/osu.Game/Screens/Edit/Commands/UpdateControlPointCommand.cs @@ -3,10 +3,9 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Edit.Commands; using osuTK; -namespace osu.Game.Rulesets.Osu.Edit.Commands +namespace osu.Game.Screens.Edit.Commands { public class UpdateControlPointCommand : IEditorCommand {