2024-10-09 02:18:16 +08:00
|
|
|
// 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;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using osu.Framework.Bindables;
|
2024-10-10 03:20:07 +08:00
|
|
|
using osu.Framework.Logging;
|
2024-10-09 02:18:16 +08:00
|
|
|
using osu.Game.Screens.Edit.Commands;
|
|
|
|
|
|
|
|
namespace osu.Game.Screens.Edit
|
|
|
|
{
|
2024-10-09 02:56:04 +08:00
|
|
|
public partial class EditorCommandHandler
|
2024-10-09 02:18:16 +08:00
|
|
|
{
|
|
|
|
public EditorCommandHandler()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
public event Action<IEditorCommand>? CommandApplied;
|
|
|
|
|
|
|
|
public readonly Bindable<bool> CanUndo = new BindableBool();
|
|
|
|
|
|
|
|
public readonly Bindable<bool> CanRedo = new BindableBool();
|
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
public bool HasUncommittedChanges => currentTransaction.Commands.Count != 0;
|
2024-10-09 02:18:16 +08:00
|
|
|
|
|
|
|
public void Submit(IEditorCommand command, bool commitImmediately = false)
|
|
|
|
{
|
|
|
|
if (command.IsRedundant)
|
|
|
|
return;
|
|
|
|
|
|
|
|
record(command);
|
|
|
|
apply(command);
|
|
|
|
|
|
|
|
if (commitImmediately)
|
|
|
|
Commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Submit(IEnumerable<IEditorCommand> commands, bool commitImmediately = false)
|
|
|
|
{
|
|
|
|
foreach (var command in commands)
|
|
|
|
Submit(command);
|
|
|
|
|
|
|
|
if (commitImmediately)
|
|
|
|
Commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool Commit()
|
|
|
|
{
|
|
|
|
if (!HasUncommittedChanges)
|
2024-10-10 03:20:07 +08:00
|
|
|
{
|
|
|
|
Logger.Log("Nothing to commit");
|
2024-10-09 02:18:16 +08:00
|
|
|
return false;
|
2024-10-10 03:20:07 +08:00
|
|
|
}
|
2024-10-09 02:18:16 +08:00
|
|
|
|
|
|
|
undoStack.Push(currentTransaction);
|
|
|
|
redoStack.Clear();
|
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
Logger.Log($"Added {currentTransaction.Commands.Count} command(s) to undo stack");
|
2024-10-10 03:20:07 +08:00
|
|
|
|
2024-10-09 02:18:16 +08:00
|
|
|
currentTransaction = new Transaction();
|
|
|
|
|
|
|
|
historyChanged();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool Undo()
|
|
|
|
{
|
|
|
|
if (undoStack.Count == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
var transaction = undoStack.Pop();
|
2024-10-10 20:05:50 +08:00
|
|
|
var redoTransaction = transaction.Reverse();
|
2024-10-09 02:18:16 +08:00
|
|
|
|
|
|
|
revertTransaction(transaction);
|
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
redoStack.Push(redoTransaction);
|
2024-10-09 02:18:16 +08:00
|
|
|
|
|
|
|
historyChanged();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool Redo()
|
|
|
|
{
|
|
|
|
if (redoStack.Count == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
var transaction = redoStack.Pop();
|
2024-10-10 20:05:50 +08:00
|
|
|
var undoTransaction = transaction.Reverse();
|
2024-10-09 02:18:16 +08:00
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
foreach (var command in transaction.Commands)
|
|
|
|
apply(command);
|
2024-10-09 02:18:16 +08:00
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
undoStack.Push(undoTransaction);
|
2024-10-09 02:18:16 +08:00
|
|
|
|
|
|
|
historyChanged();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool RevertUncommitedChanges()
|
|
|
|
{
|
|
|
|
if (!HasUncommittedChanges)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
revertTransaction(currentTransaction);
|
|
|
|
|
|
|
|
currentTransaction = new Transaction();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void revertTransaction(Transaction transaction)
|
|
|
|
{
|
2024-10-10 20:05:50 +08:00
|
|
|
foreach (var command in transaction.Commands.Reverse())
|
|
|
|
apply(command);
|
2024-10-09 02:18:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private void historyChanged()
|
|
|
|
{
|
|
|
|
CanUndo.Value = undoStack.Count > 0;
|
|
|
|
CanRedo.Value = redoStack.Count > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Transaction currentTransaction = new Transaction();
|
|
|
|
|
|
|
|
private readonly Stack<Transaction> undoStack = new Stack<Transaction>();
|
|
|
|
|
|
|
|
private readonly Stack<Transaction> redoStack = new Stack<Transaction>();
|
|
|
|
|
|
|
|
private void apply(IEditorCommand command)
|
|
|
|
{
|
|
|
|
command.Apply();
|
|
|
|
CommandApplied?.Invoke(command);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void record(IEditorCommand command)
|
|
|
|
{
|
|
|
|
var reverse = command.CreateUndo();
|
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
currentTransaction.Add(reverse);
|
2024-10-09 02:18:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private readonly record struct HistoryEntry(IEditorCommand Command, IEditorCommand Reverse);
|
|
|
|
|
|
|
|
private readonly struct Transaction
|
|
|
|
{
|
|
|
|
public Transaction()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
private readonly List<IEditorCommand> commands = new List<IEditorCommand>();
|
2024-10-09 02:18:16 +08:00
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
public IReadOnlyList<IEditorCommand> Commands => commands;
|
2024-10-09 02:18:16 +08:00
|
|
|
|
2024-10-10 20:05:50 +08:00
|
|
|
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;
|
|
|
|
}
|
2024-10-09 02:18:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|