diff --git a/osu.Game/Screens/Edit/Commands/IMergeableCommand.cs b/osu.Game/Screens/Edit/Commands/IMergeableCommand.cs new file mode 100644 index 0000000000..d0afc6a23e --- /dev/null +++ b/osu.Game/Screens/Edit/Commands/IMergeableCommand.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Edit.Commands +{ + public interface IMergeableCommand : IEditorCommand + { + public IEditorCommand? MergeWith(IEditorCommand previous); + } +} diff --git a/osu.Game/Screens/Edit/Commands/MoveCommand.cs b/osu.Game/Screens/Edit/Commands/MoveCommand.cs index 360080fe4e..6ab4606fd2 100644 --- a/osu.Game/Screens/Edit/Commands/MoveCommand.cs +++ b/osu.Game/Screens/Edit/Commands/MoveCommand.cs @@ -6,7 +6,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Commands { - public class MoveCommand : IEditorCommand + public class MoveCommand : IEditorCommand, IMergeableCommand { public readonly IHasMutablePosition Target; @@ -23,5 +23,13 @@ namespace osu.Game.Screens.Edit.Commands public IEditorCommand CreateUndo() => new MoveCommand(Target, Target.Position); public bool IsRedundant => Position == Target.Position; + + public IEditorCommand? MergeWith(IEditorCommand previous) + { + if (previous is MoveCommand moveCommand) + return moveCommand.Target != Target ? null : this; + + return null; + } } } diff --git a/osu.Game/Screens/Edit/EditorCommandHandler.cs b/osu.Game/Screens/Edit/EditorCommandHandler.cs index 1cb7fdcb47..b5fe13891e 100644 --- a/osu.Game/Screens/Edit/EditorCommandHandler.cs +++ b/osu.Game/Screens/Edit/EditorCommandHandler.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit public readonly Bindable CanRedo = new BindableBool(); - public bool HasUncommittedChanges => currentTransaction.Entries.Count != 0; + public bool HasUncommittedChanges => currentTransaction.Commands.Count != 0; public void Submit(IEditorCommand command, bool commitImmediately = false) { @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit undoStack.Push(currentTransaction); redoStack.Clear(); - Logger.Log($"Added {currentTransaction.Entries.Count} command(s) to undo stack"); + Logger.Log($"Added {currentTransaction.Commands.Count} command(s) to undo stack"); currentTransaction = new Transaction(); @@ -71,10 +71,11 @@ namespace osu.Game.Screens.Edit return false; var transaction = undoStack.Pop(); + var redoTransaction = transaction.Reverse(); revertTransaction(transaction); - redoStack.Push(transaction); + redoStack.Push(redoTransaction); historyChanged(); @@ -87,11 +88,12 @@ namespace osu.Game.Screens.Edit return false; var transaction = redoStack.Pop(); + var undoTransaction = transaction.Reverse(); - foreach (var entry in transaction.Entries) - apply(entry.Command); + foreach (var command in transaction.Commands) + apply(command); - undoStack.Push(transaction); + undoStack.Push(undoTransaction); historyChanged(); @@ -112,8 +114,8 @@ namespace osu.Game.Screens.Edit private void revertTransaction(Transaction transaction) { - foreach (var entry in transaction.Entries.Reverse()) - apply(entry.Reverse); + foreach (var command in transaction.Commands.Reverse()) + apply(command); } private void historyChanged() @@ -138,7 +140,7 @@ namespace osu.Game.Screens.Edit { var reverse = command.CreateUndo(); - currentTransaction.Add(new HistoryEntry(command, reverse)); + currentTransaction.Add(reverse); } private readonly record struct HistoryEntry(IEditorCommand Command, IEditorCommand Reverse); @@ -149,11 +151,47 @@ namespace osu.Game.Screens.Edit { } - private readonly List entries = new List(); + private readonly List commands = new List(); - public IReadOnlyList Entries => entries; + public IReadOnlyList Commands => commands; - public void Add(HistoryEntry entry) => entries.Add(entry); + public void Add(IEditorCommand command) + { + if (command is IMergeableCommand mergeable) + { + for (int i = 0; i < commands.Count; 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.Add(command); + } + + public Transaction Reverse() + { + var reversed = new Transaction(); + + foreach (var command in Commands.Reverse()) + reversed.Add(command.CreateUndo()); + + return reversed; + } } } } diff --git a/osu.Game/Screens/Edit/EditorCommandManagerExtension.cs b/osu.Game/Screens/Edit/EditorCommandHandlerExtension.cs similarity index 94% rename from osu.Game/Screens/Edit/EditorCommandManagerExtension.cs rename to osu.Game/Screens/Edit/EditorCommandHandlerExtension.cs index 029a0c5878..3b8e8dc404 100644 --- a/osu.Game/Screens/Edit/EditorCommandManagerExtension.cs +++ b/osu.Game/Screens/Edit/EditorCommandHandlerExtension.cs @@ -6,7 +6,7 @@ using osu.Game.Screens.Edit.Commands; namespace osu.Game.Screens.Edit { - public static class EditorCommandManagerExtension + public static class EditorCommandHandlerExtension { public static void SafeSubmit(this EditorCommandHandler? manager, IEditorCommand command, bool commitImmediately = false) {