mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 10:45:36 +08:00
Merge pull request #2 from OliBomby/command-pattern-real-2
Implement variant type generic proxies without heap allocations
This commit is contained in:
commit
ce12b487a8
@ -44,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<T> proxy;
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
@ -79,6 +79,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
controlPoints.CollectionChanged += onControlPointsChanged;
|
||||
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
||||
|
||||
proxy = new CommandProxy<T>(commandHandler, hitObject);
|
||||
}
|
||||
|
||||
// Generally all the control points are within the visible area all the time.
|
||||
@ -407,6 +409,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
public void DragInProgress(DragEvent e)
|
||||
{
|
||||
var controlPointsProxy = proxy.Path().ControlPoints();
|
||||
Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray();
|
||||
Vector2 oldPosition = hitObject.Position;
|
||||
double oldStartTime = hitObject.StartTime;
|
||||
@ -419,18 +422,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
|
||||
|
||||
commandHandler.SafeSubmit(new MoveCommand(hitObject, hitObject.Position + movementDelta));
|
||||
commandHandler.SafeSubmit(new SetStartTimeCommand(hitObject, result?.Time ?? hitObject.StartTime));
|
||||
proxy.SetPosition(hitObject.Position + movementDelta);
|
||||
proxy.SetStartTime(result?.Time ?? hitObject.StartTime);
|
||||
|
||||
for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++)
|
||||
for (int i = 1; i < controlPointsProxy.Count; i++)
|
||||
{
|
||||
PathControlPoint controlPoint = hitObject.Path.ControlPoints[i];
|
||||
var controlPointProxy = controlPointsProxy[i];
|
||||
// Since control points are relative to the position of the hit object, 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))
|
||||
commandHandler.SafeSubmit(new UpdateControlPointCommand(controlPoint) { Position = controlPoint.Position - movementDelta });
|
||||
if (!selectedControlPoints.Contains(controlPointProxy.Target))
|
||||
controlPointProxy.SetPosition(controlPointProxy.Position() - movementDelta);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -439,32 +442,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? Parent!.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
||||
|
||||
for (int i = 0; i < controlPoints.Count; ++i)
|
||||
for (int i = 0; i < controlPointsProxy.Count; ++i)
|
||||
{
|
||||
PathControlPoint controlPoint = controlPoints[i];
|
||||
if (selectedControlPoints.Contains(controlPoint))
|
||||
commandHandler.SafeSubmit(new UpdateControlPointCommand(controlPoint) { Position = dragStartPositions[i] + movementDelta });
|
||||
var controlPointProxy = controlPointsProxy[i];
|
||||
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 < hitObject.Path.ControlPoints.Count; i++)
|
||||
commandHandler.SafeSubmit(new UpdateControlPointCommand(hitObject.Path.ControlPoints[i]) { Position = oldControlPoints[i] });
|
||||
for (int i = 0; i < controlPointsProxy.Count; i++)
|
||||
controlPointsProxy[i].SetPosition(oldControlPoints[i]);
|
||||
|
||||
commandHandler.SafeSubmit(new MoveCommand(hitObject, oldPosition));
|
||||
commandHandler.SafeSubmit(new SetStartTimeCommand(hitObject, 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 < hitObject.Path.ControlPoints.Count; i++)
|
||||
commandHandler.SafeSubmit(new UpdateControlPointCommand(hitObject.Path.ControlPoints[i]) { Type = dragPathTypes[i] });
|
||||
for (int i = 0; i < controlPointsProxy.Count; i++)
|
||||
controlPointsProxy[i].SetType(dragPathTypes[i]);
|
||||
|
||||
EnsureValidPathTypes();
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
|
||||
|
||||
protected SliderCommandProxy Proxy;
|
||||
protected CommandProxy<Slider> Proxy { get; private set; }
|
||||
|
||||
protected SliderBodyPiece BodyPiece { get; private set; } = null!;
|
||||
protected SliderCircleOverlay HeadOverlay { get; private set; } = null!;
|
||||
@ -98,8 +98,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
Proxy = new SliderCommandProxy(commandHandler, HitObject);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
BodyPiece = new SliderBodyPiece(),
|
||||
@ -121,6 +119,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Proxy = new CommandProxy<Slider>(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.
|
||||
/// </summary>
|
||||
/// <param name="sliderPath">The slider path to trim control points of.</param>
|
||||
private void trimExcessControlPoints(SliderPathCommandProxy sliderPath)
|
||||
private void trimExcessControlPoints(CommandProxy<SliderPath> 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<PathControlPoint>(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<BindableList<PathControlPoint>, CommandProxy<PathControlPoint>, PathControlPoint>(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()
|
||||
|
@ -1,29 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<PathControlPoint> ControlPoints;
|
||||
|
||||
public readonly int InsertionIndex;
|
||||
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
|
||||
public AddControlPointCommand(BindableList<PathControlPoint> 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);
|
||||
}
|
||||
}
|
@ -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<T>(this CommandProxy<T> proxy) where T : OsuHitObject => proxy.Target.Position;
|
||||
|
||||
protected new OsuHitObject HitObject => (OsuHitObject)base.HitObject;
|
||||
public static void SetPosition<T>(this CommandProxy<T> 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<T>(this CommandProxy<T> proxy) where T : OsuHitObject => proxy.Target.NewCombo;
|
||||
|
||||
public bool NewCombo
|
||||
{
|
||||
get => HitObject.NewCombo;
|
||||
set => Submit(new SetNewComboCommand(HitObject, value));
|
||||
}
|
||||
public static void SetNewCombo<T>(this CommandProxy<T> proxy, bool value) where T : OsuHitObject =>
|
||||
proxy.Submit(new SetNewComboCommand(proxy.Target, value));
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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 = Position });
|
||||
}
|
||||
|
||||
public PathType? Type
|
||||
{
|
||||
get => ControlPoint.Type;
|
||||
set => Submit(new UpdateControlPointCommand(ControlPoint) { Type = Type });
|
||||
}
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<PathControlPointCommandProxy>
|
||||
{
|
||||
public PathControlPointsCommandProxy(EditorCommandHandler? commandHandler, BindableList<PathControlPoint> controlPoints)
|
||||
: base(commandHandler)
|
||||
{
|
||||
ControlPoints = controlPoints;
|
||||
}
|
||||
|
||||
public readonly BindableList<PathControlPoint> 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<PathControlPointCommandProxy> GetEnumerator() => new PathControlPointsCommandProxyEnumerator(CommandHandler, ControlPoints.GetEnumerator());
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
private readonly struct PathControlPointsCommandProxyEnumerator : IEnumerator<PathControlPointCommandProxy>
|
||||
{
|
||||
public PathControlPointsCommandProxyEnumerator(
|
||||
EditorCommandHandler? commandHandler,
|
||||
IEnumerator<PathControlPoint> enumerator
|
||||
)
|
||||
{
|
||||
this.commandHandler = commandHandler;
|
||||
this.enumerator = enumerator;
|
||||
}
|
||||
|
||||
private readonly EditorCommandHandler? commandHandler;
|
||||
|
||||
private readonly IEnumerator<PathControlPoint> 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<PathControlPoint> ControlPoints;
|
||||
|
||||
public readonly int Index;
|
||||
|
||||
public RemoveControlPointCommand(BindableList<PathControlPoint> controlPoints, int index)
|
||||
{
|
||||
ControlPoints = controlPoints;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public RemoveControlPointCommand(BindableList<PathControlPoint> controlPoints, PathControlPoint controlPoint)
|
||||
{
|
||||
ControlPoints = controlPoints;
|
||||
Index = controlPoints.IndexOf(controlPoint);
|
||||
}
|
||||
|
||||
public void Apply() => ControlPoints.RemoveAt(Index);
|
||||
|
||||
public IEditorCommand CreateUndo() => new AddControlPointCommand(ControlPoints, Index, ControlPoints[Index]);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
|
||||
}
|
||||
}
|
@ -1,26 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<double> GetSegmentEnds() => Path.GetSegmentEnds();
|
||||
}
|
||||
}
|
@ -24,6 +24,19 @@ namespace osu.Game.Rulesets.Objects
|
||||
commandHandler.SafeSubmit(new SetExpectedDistanceCommand(hitObject.Path, distance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Snaps the provided <paramref name="proxy"/>'s duration using the <paramref name="snapProvider"/>.
|
||||
/// </summary>
|
||||
public static void SnapTo<THitObject>(this CommandProxy<THitObject> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverse the direction of this path.
|
||||
/// </summary>
|
||||
|
@ -1,21 +1,126 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<T>
|
||||
{
|
||||
protected EditorCommandHandler? CommandHandler;
|
||||
EditorCommandHandler? CommandHandler { get; init; }
|
||||
T Target { get; init; }
|
||||
void Submit(IEditorCommand command);
|
||||
}
|
||||
|
||||
protected CommandProxy(EditorCommandHandler? commandHandler)
|
||||
public readonly struct CommandProxy<T> : ICommandProxy<T>
|
||||
{
|
||||
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<IEditorCommand> command) => CommandHandler.SafeSubmit(command);
|
||||
public readonly struct ListCommandProxy<T, TItemProxy, TItem> : ICommandProxy<T>, IList<TItemProxy> where T : IList<TItem> where TItemProxy : ICommandProxy<TItem>, 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<TItemProxy> 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<T, TItem>(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<T, TItem>(Target, index, item));
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Submit(new RemoveCommand<T, TItem>(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<T, TItem>(Target, index, value.Target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<T>(this CommandProxy<T> proxy) where T : HitObject => proxy.Target.StartTime;
|
||||
|
||||
protected HitObject HitObject;
|
||||
public static void SetStartTime<T>(this CommandProxy<T> 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<SliderPath> Path<T>(this CommandProxy<T> proxy) where T : IHasPath =>
|
||||
new CommandProxy<SliderPath>(proxy.CommandHandler, proxy.Target.Path);
|
||||
|
||||
public static double SliderVelocityMultiplier<T>(this CommandProxy<T> proxy) where T : IHasSliderVelocity => proxy.Target.SliderVelocityMultiplier;
|
||||
|
||||
public static void SetSliderVelocityMultiplier<T>(this CommandProxy<T> proxy, double value) where T : IHasSliderVelocity =>
|
||||
proxy.Submit(new SetSliderVelocityMultiplierCommand(proxy.Target, value));
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ namespace osu.Game.Screens.Edit.Commands
|
||||
|
||||
public IEditorCommand CreateUndo();
|
||||
|
||||
public virtual bool IsRedundant => false;
|
||||
public bool IsRedundant => false;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ namespace osu.Game.Screens.Edit.Commands
|
||||
{
|
||||
public interface IMergeableCommand : IEditorCommand
|
||||
{
|
||||
public IEditorCommand? MergeWith(IEditorCommand previous);
|
||||
public IMergeableCommand? MergeWith(IEditorCommand previous);
|
||||
}
|
||||
}
|
||||
|
27
osu.Game/Screens/Edit/Commands/InsertCommand.cs
Normal file
27
osu.Game/Screens/Edit/Commands/InsertCommand.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<T, T2> : IEditorCommand where T : IList<T2>
|
||||
{
|
||||
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<T, T2>(Target, InsertionIndex);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Commands
|
||||
{
|
||||
public class MoveCommand : IEditorCommand, IMergeableCommand
|
||||
public class MoveCommand : IMergeableCommand
|
||||
{
|
||||
public readonly IHasMutablePosition Target;
|
||||
|
||||
@ -24,10 +24,10 @@ namespace osu.Game.Screens.Edit.Commands
|
||||
|
||||
public bool IsRedundant => Position == Target.Position;
|
||||
|
||||
public IEditorCommand? MergeWith(IEditorCommand previous)
|
||||
public IMergeableCommand? MergeWith(IEditorCommand previous)
|
||||
{
|
||||
if (previous is MoveCommand moveCommand)
|
||||
return moveCommand.Target != Target ? null : this;
|
||||
return moveCommand.Target != Target ? null : moveCommand;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<T>(this CommandProxy<T> proxy) where T : PathControlPoint => proxy.Target.Position;
|
||||
|
||||
public static void SetPosition(this CommandProxy<PathControlPoint> proxy, Vector2 value) => proxy.Submit(new UpdateControlPointCommand(proxy.Target) { Position = value });
|
||||
|
||||
public static PathType? Type<T>(this CommandProxy<T> proxy) where T : PathControlPoint => proxy.Target.Type;
|
||||
|
||||
public static void SetType(this CommandProxy<PathControlPoint> proxy, PathType? value) => proxy.Submit(new UpdateControlPointCommand(proxy.Target) { Type = value });
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
namespace osu.Game.Screens.Edit.Commands
|
||||
{
|
||||
public abstract class PropertyChangeCommand<TTarget, TProperty> : IEditorCommand, IMergeableCommand where TTarget : class
|
||||
public abstract class PropertyChangeCommand<TTarget, TProperty> : IMergeableCommand where TTarget : class
|
||||
{
|
||||
protected abstract TProperty ReadValue(TTarget target);
|
||||
|
||||
@ -25,10 +25,10 @@ namespace osu.Game.Screens.Edit.Commands
|
||||
|
||||
public IEditorCommand CreateUndo() => CreateInstance(Target, Value);
|
||||
|
||||
public IEditorCommand? MergeWith(IEditorCommand previous)
|
||||
public IMergeableCommand? MergeWith(IEditorCommand previous)
|
||||
{
|
||||
if (previous is PropertyChangeCommand<TTarget, TProperty> command && command.Target == Target)
|
||||
return this;
|
||||
return command;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
30
osu.Game/Screens/Edit/Commands/RemoveCommand.cs
Normal file
30
osu.Game/Screens/Edit/Commands/RemoveCommand.cs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<T, T2> : IEditorCommand where T : IList<T2>
|
||||
{
|
||||
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<T, T2>(Target, Index, Target[Index]);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
|
||||
}
|
||||
}
|
||||
}
|
21
osu.Game/Screens/Edit/Commands/SliderPathCommandProxy.cs
Normal file
21
osu.Game/Screens/Edit/Commands/SliderPathCommandProxy.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<SliderPath> proxy) => proxy.Target.ExpectedDistance.Value;
|
||||
|
||||
public static void SetExpectedDistance(this CommandProxy<SliderPath> proxy, double? value) => proxy.Submit(new SetExpectedDistanceCommand(proxy.Target, value));
|
||||
|
||||
public static ListCommandProxy<BindableList<PathControlPoint>, CommandProxy<PathControlPoint>, PathControlPoint> ControlPoints(this CommandProxy<SliderPath> proxy) =>
|
||||
new ListCommandProxy<BindableList<PathControlPoint>, CommandProxy<PathControlPoint>, PathControlPoint>(proxy.CommandHandler, proxy.Target.ControlPoints);
|
||||
|
||||
public static IEnumerable<double> GetSegmentEnds(this ICommandProxy<SliderPath> proxy) => proxy.Target.GetSegmentEnds();
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
@ -12,10 +12,6 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public partial class EditorCommandHandler
|
||||
{
|
||||
public EditorCommandHandler()
|
||||
{
|
||||
}
|
||||
|
||||
public event Action<IEditorCommand>? CommandApplied;
|
||||
|
||||
public readonly Bindable<bool> CanUndo = new BindableBool();
|
||||
@ -143,8 +139,6 @@ namespace osu.Game.Screens.Edit
|
||||
currentTransaction.Add(reverse);
|
||||
}
|
||||
|
||||
private readonly record struct HistoryEntry(IEditorCommand Command, IEditorCommand Reverse);
|
||||
|
||||
private readonly struct Transaction
|
||||
{
|
||||
public Transaction()
|
||||
@ -159,24 +153,15 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
if (command is IMergeableCommand mergeable)
|
||||
{
|
||||
for (int i = 0; i < commands.Count; i++)
|
||||
for (int i = commands.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var merged = mergeable.MergeWith(commands[i]);
|
||||
|
||||
if (merged == null)
|
||||
continue;
|
||||
|
||||
command = merged;
|
||||
commands.RemoveAt(i--);
|
||||
|
||||
if (command is IMergeableCommand newMergeable)
|
||||
{
|
||||
mergeable = newMergeable;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
commands[i] = merged;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user