Editor/Commands/SuiCommandStack.cs
using System;
using System.Collections.Generic;
using SboxUiDesigner.Runtime;
namespace SboxUiDesigner.EditorUi.Commands;
/// <summary>
/// Two-stack undo/redo manager. Commands are pushed onto the undo stack as
/// they are applied; pushing always clears the redo stack.
/// </summary>
public sealed class SuiCommandStack
{
private readonly Stack<ISuiCommand> _undo = new();
private readonly Stack<ISuiCommand> _redo = new();
/// <summary>Maximum number of commands kept in the undo stack. 0 = unlimited.</summary>
public int MaxDepth { get; set; } = 256;
public bool CanUndo => _undo.Count > 0;
public bool CanRedo => _redo.Count > 0;
public string UndoName => CanUndo ? _undo.Peek().Description : null;
public string RedoName => CanRedo ? _redo.Peek().Description : null;
public event Action Changed;
public void Push( ISuiCommand cmd, SuiDocument doc )
{
if ( cmd == null || doc == null ) return;
cmd.Apply( doc );
_undo.Push( cmd );
_redo.Clear();
TrimDepth();
Changed?.Invoke();
}
public void Undo( SuiDocument doc )
{
if ( !CanUndo || doc == null ) return;
var cmd = _undo.Pop();
cmd.Undo( doc );
_redo.Push( cmd );
Changed?.Invoke();
}
public void Redo( SuiDocument doc )
{
if ( !CanRedo || doc == null ) return;
var cmd = _redo.Pop();
cmd.Apply( doc );
_undo.Push( cmd );
Changed?.Invoke();
}
public void Clear()
{
_undo.Clear();
_redo.Clear();
Changed?.Invoke();
}
private void TrimDepth()
{
if ( MaxDepth <= 0 ) return;
while ( _undo.Count > MaxDepth )
{
// Stack<T> doesn't support trimming the bottom; rebuild.
var arr = _undo.ToArray();
_undo.Clear();
for ( int i = arr.Length - 2; i >= 0; i-- )
_undo.Push( arr[i] );
}
}
}