1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 10:33:22 +08:00

Refactor command proxies

This commit is contained in:
Marvin Schürz 2024-10-10 23:11:08 +02:00
parent 57c12191e5
commit de5864ab1d
17 changed files with 207 additions and 282 deletions

View File

@ -24,10 +24,10 @@ 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.Edit.Commands;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Commands;
using osu.Game.Screens.Edit.Commands.Proxies;
using osuTK;
using osuTK.Input;
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return;
if (segment.Count > 3)
commandHandler.SafeSubmit(new UpdateControlPointCommand(first) { Type = PathType.BEZIER });
commandHandler.SafeSubmit(new SetPathTypeCommand(first, PathType.BEZIER));
if (segment.Count != 3)
return;
@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
ReadOnlySpan<Vector2> points = segment.Select(p => p.Position).ToArray();
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
commandHandler.SafeSubmit(new UpdateControlPointCommand(first) { Type = PathType.BEZIER });
commandHandler.SafeSubmit(new SetPathTypeCommand(first, PathType.BEZIER));
}
/// <summary>

View File

@ -21,11 +21,11 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Edit.Commands;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Commands;
using osu.Game.Screens.Edit.Commands.Proxies;
using osu.Game.Screens.Edit.Compose;
using osuTK;
using osuTK.Input;
@ -480,7 +480,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
Vector2 first = controlPoints[0].Position;
foreach (var c in controlPoints)
commandHandler.SafeSubmit(new UpdateControlPointCommand(c) { Position = c.Position - first });
commandHandler.SafeSubmit(new SetPositionCommand(c, c.Position - first));
commandHandler.SafeSubmit(new SetPositionCommand(HitObject, HitObject.Position + first));
}
@ -499,7 +499,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
editorBeatmap.SelectedHitObjects.Clear();
var controlPointsProxy = new ListCommandProxy<BindableList<PathControlPoint>, CommandProxy<PathControlPoint>, PathControlPoint>(commandHandler, controlPoints);
var controlPointsProxy = new ListCommandProxy<PathControlPoint, CommandProxy<PathControlPoint>>(commandHandler, controlPoints);
foreach (var splitPoint in controlPointsToSplitAt)
{

View File

@ -1,22 +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;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Commands
{
public static class OsuHitObjectCommandProxy
{
public static Vector2 Position<T>(this CommandProxy<T> proxy) where T : OsuHitObject => proxy.Target.Position;
public static void SetPosition<T>(this CommandProxy<T> proxy, Vector2 value) where T : OsuHitObject =>
proxy.Submit(new SetPositionCommand(proxy.Target, value));
public static bool NewCombo<T>(this CommandProxy<T> proxy) where T : OsuHitObject => proxy.Target.NewCombo;
public static void SetNewCombo<T>(this CommandProxy<T> proxy, bool value) where T : OsuHitObject =>
proxy.Submit(new SetNewComboCommand(proxy.Target, value));
}
}

View File

@ -1,27 +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 SetNewComboCommand : IEditorCommand
{
public OsuHitObject Target;
public bool NewCombo;
public SetNewComboCommand(OsuHitObject target, bool newCombo)
{
Target = target;
NewCombo = newCombo;
}
public void Apply() => Target.NewCombo = NewCombo;
public IEditorCommand CreateUndo() => new SetNewComboCommand(Target, Target.NewCombo);
public bool IsRedundant => NewCombo == Target.NewCombo;
}
}

View File

@ -1,9 +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.
namespace osu.Game.Rulesets.Osu.Edit.Commands
{
public static class SliderCommandProxy
{
}
}

View File

@ -10,7 +10,7 @@ using osuTK;
namespace osu.Game.Rulesets.Objects
{
public class PathControlPoint : IEquatable<PathControlPoint>
public class PathControlPoint : IEquatable<PathControlPoint>, IHasMutablePosition
{
private Vector2 position;
@ -31,6 +31,24 @@ namespace osu.Game.Rulesets.Objects
}
}
/// <summary>
/// The X component of <see cref="Position"/>.
/// </summary>
public float X
{
get => position.X;
set => Position = Position with { X = value };
}
/// <summary>
/// The Y component of <see cref="Position"/>.
/// </summary>
public float Y
{
get => position.Y;
set => Position = Position with { Y = value };
}
private PathType? type;
/// <summary>

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Commands;
using osu.Game.Screens.Edit.Commands.Proxies;
using osuTK;
namespace osu.Game.Rulesets.Objects

View File

@ -1,23 +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;
namespace osu.Game.Screens.Edit.Commands
{
public static class HitObjectCommandProxy
{
public static double StartTime<T>(this CommandProxy<T> proxy) where T : HitObject => proxy.Target.StartTime;
public static void SetStartTime<T>(this CommandProxy<T> proxy, double value) where T : HitObject => proxy.Submit(new SetStartTimeCommand(proxy.Target, 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));
}
}

View File

@ -1,27 +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;
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);
}
}

View File

@ -0,0 +1,72 @@
// 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 static class ListCommands
{
public class Update<T> : PropertyChangeCommand<IList<T>, T>
{
public int Index;
public Update(IList<T> target, int index, T value)
: base(target, value)
{
Index = index;
}
protected override T ReadValue(IList<T> target) => target[Index];
protected override void WriteValue(IList<T> target, T value) => target[Index] = value;
protected override Update<T> CreateInstance(IList<T> target, T value) => new Update<T>(target, Index, value);
}
public class Insert<T> : IEditorCommand
{
public readonly IList<T> Target;
public readonly int InsertionIndex;
public readonly T Item;
public Insert(IList<T> target, int insertionIndex, T item)
{
Target = target;
InsertionIndex = insertionIndex;
Item = item;
}
public void Apply() => Target.Insert(InsertionIndex, Item);
public IEditorCommand CreateUndo() => new Remove<T>(Target, InsertionIndex);
}
public class Remove<T> : IEditorCommand
{
public readonly IList<T> Target;
public readonly int Index;
public Remove(IList<T> target, int index)
{
Target = target;
Index = index;
}
public Remove(IList<T> target, T item)
{
Target = target;
Index = target.IndexOf(item);
}
public void Apply() => Target.RemoveAt(Index);
public bool IsRedundant => Index < 0 || Index >= Target.Count;
public IEditorCommand CreateUndo() => new Insert<T>(Target, Index, Target[Index]);
}
}
}

View File

@ -1,20 +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 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 });
}
}

View File

@ -0,0 +1,25 @@
// 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.
namespace osu.Game.Screens.Edit.Commands.Proxies
{
public interface ICommandProxy<T>
{
EditorCommandHandler? CommandHandler { get; init; }
T Target { get; init; }
void Submit(IEditorCommand command);
}
public readonly struct CommandProxy<T> : ICommandProxy<T>
{
public CommandProxy(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);
}
}

View File

@ -0,0 +1,70 @@
// 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.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Screens.Edit.Commands.Proxies
{
public static class CommandProxyExtensions
{
#region Position
public static Vector2 Position<T>(this CommandProxy<T> proxy) where T : IHasPosition => proxy.Target.Position;
public static void SetPosition<T>(this CommandProxy<T> proxy, Vector2 value) where T : IHasMutablePosition => proxy.Submit(new SetPositionCommand(proxy.Target, value));
public static float X<T>(this CommandProxy<T> proxy) where T : IHasXPosition => proxy.Target.X;
public static void SetX<T>(this CommandProxy<T> proxy, float value) where T : IHasMutableXPosition => proxy.Submit(new SetXCommand(proxy.Target, value));
public static float Y<T>(this CommandProxy<T> proxy) where T : IHasYPosition => proxy.Target.Y;
public static void SetY<T>(this CommandProxy<T> proxy, float value) where T : IHasMutableYPosition => proxy.Submit(new SetYCommand(proxy.Target, value));
#endregion
#region HitObject
public static double StartTime<T>(this CommandProxy<T> proxy) where T : HitObject => proxy.Target.StartTime;
public static void SetStartTime<T>(this CommandProxy<T> proxy, double value) where T : HitObject => proxy.Submit(new SetStartTimeCommand(proxy.Target, value));
public static bool NewCombo<T>(this CommandProxy<T> proxy) where T : IHasCombo => proxy.Target.NewCombo;
public static void SetNewCombo<T>(this CommandProxy<T> proxy, bool value) where T : IHasComboInformation => proxy.Submit(new SetNewComboCommand(proxy.Target, value));
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));
#endregion
#region PathControlPoint
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 SetPathTypeCommand(proxy.Target, value));
#endregion
#region SliderPath
public static CommandProxy<SliderPath> Path<T>(this CommandProxy<T> proxy) where T : IHasPath =>
new CommandProxy<SliderPath>(proxy.CommandHandler, proxy.Target.Path);
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<PathControlPoint, CommandProxy<PathControlPoint>> ControlPoints(this CommandProxy<SliderPath> proxy) =>
new ListCommandProxy<PathControlPoint, CommandProxy<PathControlPoint>>(proxy.CommandHandler, proxy.Target.ControlPoints);
public static IEnumerable<double> GetSegmentEnds(this ICommandProxy<SliderPath> proxy) => proxy.Target.GetSegmentEnds();
#endregion
}
}

View File

@ -5,38 +5,19 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Screens.Edit.Commands
namespace osu.Game.Screens.Edit.Commands.Proxies
{
public interface ICommandProxy<T>
public readonly struct ListCommandProxy<TItem, TItemProxy> : ICommandProxy<IList<TItem>>, IList<TItemProxy> where TItemProxy : ICommandProxy<TItem>, new()
{
EditorCommandHandler? CommandHandler { get; init; }
T Target { get; init; }
void Submit(IEditorCommand command);
}
public readonly struct CommandProxy<T> : ICommandProxy<T>
{
public CommandProxy(EditorCommandHandler? commandHandler, T target)
public ListCommandProxy(EditorCommandHandler? commandHandler, IList<TItem> target)
{
CommandHandler = commandHandler;
Target = target;
}
public EditorCommandHandler? CommandHandler { get; init; }
public T Target { get; init; }
public void Submit(IEditorCommand command) => CommandHandler.SafeSubmit(command);
}
public IList<TItem> Target { get; init; }
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()
@ -45,20 +26,11 @@ namespace osu.Game.Screens.Edit.Commands
return Target.Select(o => new TItemProxy { CommandHandler = commandHandler, Target = o }).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(TItemProxy item)
{
Add(item.Target);
}
public void Add(TItemProxy item) => Add(item.Target);
public void Add(TItem item)
{
Insert(Count, item);
}
public void Add(TItem item) => Insert(Count, item);
public void Clear()
{
@ -66,10 +38,7 @@ namespace osu.Game.Screens.Edit.Commands
RemoveAt(Count - 1);
}
public bool Contains(TItemProxy item)
{
return Target.Contains(item.Target);
}
public bool Contains(TItemProxy item) => Target.Contains(item.Target);
public void CopyTo(TItemProxy[] array, int arrayIndex)
{
@ -84,11 +53,12 @@ namespace osu.Game.Screens.Edit.Commands
if (!Target.Contains(item))
return false;
Submit(new RemoveCommand<T, TItem>(Target, item));
Submit(new ListCommands.Remove<TItem>(Target, item));
return true;
}
public int Count => Target.Count;
public bool IsReadOnly => Target.IsReadOnly;
public int IndexOf(TItemProxy item)
@ -96,20 +66,11 @@ namespace osu.Game.Screens.Edit.Commands
return Target.IndexOf(item.Target);
}
public void Insert(int index, TItemProxy item)
{
Insert(index, 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 Insert(int index, TItem item) => Submit(new ListCommands.Insert<TItem>(Target, index, item));
public void RemoveAt(int index)
{
Submit(new RemoveCommand<T, TItem>(Target, index));
}
public void RemoveAt(int index) => Submit(new ListCommands.Remove<TItem>(Target, index));
public void RemoveRange(int index, int count)
{
@ -120,7 +81,7 @@ namespace osu.Game.Screens.Edit.Commands
public TItemProxy this[int index]
{
get => new TItemProxy { CommandHandler = CommandHandler, Target = Target[index] };
set => Submit(new InsertCommand<T, TItem>(Target, index, value.Target));
set => Submit(new ListCommands.Update<TItem>(Target, index, value.Target));
}
}
}

View File

@ -1,30 +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;
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]);
}
}

View File

@ -1,21 +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.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();
}
}

View File

@ -1,43 +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 osuTK;
namespace osu.Game.Screens.Edit.Commands
{
public class UpdateControlPointCommand : IEditorCommand
{
public PathControlPoint ControlPoint;
public Vector2 Position;
public PathType? Type;
public UpdateControlPointCommand(PathControlPoint controlPoint)
{
ControlPoint = controlPoint;
Position = controlPoint.Position;
Type = controlPoint.Type;
}
public UpdateControlPointCommand(PathControlPoint controlPoint, Vector2 position, PathType? type)
{
ControlPoint = controlPoint;
Position = position;
Type = type;
}
public void Apply()
{
ControlPoint.Position = Position;
ControlPoint.Type = Type;
}
public IEditorCommand CreateUndo()
{
return new UpdateControlPointCommand(ControlPoint, ControlPoint.Position, ControlPoint.Type);
}
}
}