2020-04-09 20:22:07 +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.IO ;
2020-09-28 14:30:18 +08:00
using System.Linq ;
2020-04-15 14:50:43 +08:00
using osu.Framework.Bindables ;
2020-09-09 18:40:41 +08:00
using osu.Framework.Extensions ;
2020-04-09 20:22:07 +08:00
namespace osu.Game.Screens.Edit
{
/// <summary>
/// Tracks changes to the <see cref="Editor"/>.
/// </summary>
2023-02-03 16:53:54 +08:00
public abstract partial class EditorChangeHandler : TransactionalCommitComponent , IEditorChangeHandler
2020-04-09 20:22:07 +08:00
{
2020-04-15 14:50:43 +08:00
public readonly Bindable < bool > CanUndo = new Bindable < bool > ( ) ;
public readonly Bindable < bool > CanRedo = new Bindable < bool > ( ) ;
2020-04-13 16:18:50 +08:00
2023-02-03 16:13:24 +08:00
public event Action ? OnStateChange ;
2020-10-02 14:44:32 +08:00
2020-04-13 16:18:50 +08:00
private readonly List < byte [ ] > savedStates = new List < byte [ ] > ( ) ;
2020-04-09 20:22:07 +08:00
private int currentState = - 1 ;
2020-09-09 18:40:41 +08:00
/// <summary>
/// A SHA-2 hash representing the current visible editor state.
/// </summary>
public string CurrentStateHash
{
get
{
2023-02-07 15:22:51 +08:00
ensureStateSaved ( ) ;
2020-09-09 18:40:41 +08:00
using ( var stream = new MemoryStream ( savedStates [ currentState ] ) )
return stream . ComputeSHA2Hash ( ) ;
}
}
2020-04-09 20:22:07 +08:00
private bool isRestoring ;
2020-04-13 19:34:18 +08:00
public const int MAX_SAVED_STATES = 50 ;
2023-02-07 15:22:51 +08:00
public override void BeginChange ( )
{
ensureStateSaved ( ) ;
base . BeginChange ( ) ;
}
private void ensureStateSaved ( )
{
if ( savedStates . Count = = 0 )
SaveState ( ) ;
}
2020-10-08 15:57:04 +08:00
protected override void UpdateState ( )
2020-04-09 20:22:07 +08:00
{
if ( isRestoring )
return ;
2020-04-13 16:18:50 +08:00
using ( var stream = new MemoryStream ( ) )
{
2023-02-03 16:53:54 +08:00
WriteCurrentStateToStream ( stream ) ;
2021-10-27 12:04:41 +08:00
byte [ ] newState = stream . ToArray ( ) ;
2020-09-28 14:30:18 +08:00
// if the previous state is binary equal we don't need to push a new one, unless this is the initial state.
2020-11-27 15:57:11 +08:00
if ( savedStates . Count > 0 & & newState . SequenceEqual ( savedStates [ currentState ] ) ) return ;
2020-04-13 16:18:50 +08:00
2020-09-28 14:30:18 +08:00
if ( currentState < savedStates . Count - 1 )
savedStates . RemoveRange ( currentState + 1 , savedStates . Count - currentState - 1 ) ;
2020-04-15 14:50:43 +08:00
2020-09-28 14:30:18 +08:00
if ( savedStates . Count > MAX_SAVED_STATES )
savedStates . RemoveAt ( 0 ) ;
savedStates . Add ( newState ) ;
currentState = savedStates . Count - 1 ;
2020-10-02 14:44:32 +08:00
OnStateChange ? . Invoke ( ) ;
2020-09-28 14:30:18 +08:00
updateBindables ( ) ;
}
2020-04-09 20:22:07 +08:00
}
/// <summary>
/// Restores an older or newer state.
/// </summary>
/// <param name="direction">The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used.</param>
public void RestoreState ( int direction )
{
2020-10-08 15:57:04 +08:00
if ( TransactionActive )
2020-04-09 20:22:07 +08:00
return ;
if ( savedStates . Count = = 0 )
return ;
int newState = Math . Clamp ( currentState + direction , 0 , savedStates . Count - 1 ) ;
if ( currentState = = newState )
return ;
isRestoring = true ;
2023-02-03 16:53:54 +08:00
ApplyStateChange ( savedStates [ currentState ] , savedStates [ newState ] ) ;
2020-04-09 20:22:07 +08:00
currentState = newState ;
isRestoring = false ;
2020-04-15 14:50:43 +08:00
2020-10-02 14:44:32 +08:00
OnStateChange ? . Invoke ( ) ;
2020-04-15 14:50:43 +08:00
updateBindables ( ) ;
}
2023-02-06 13:11:40 +08:00
/// <summary>
/// Write a serialised copy of the currently tracked state to the provided stream.
/// This will be stored as a state which can be restored in the future.
/// </summary>
/// <param name="stream">The stream which the state should be written to.</param>
2023-02-03 16:53:54 +08:00
protected abstract void WriteCurrentStateToStream ( MemoryStream stream ) ;
2023-02-06 13:11:40 +08:00
/// <summary>
/// Given a previous and new state, apply any changes required to bring the current state in line with the new state.
/// </summary>
/// <param name="previousState">The previous (current before this call) serialised state.</param>
/// <param name="newState">The new state to be applied.</param>
2023-02-03 16:53:54 +08:00
protected abstract void ApplyStateChange ( byte [ ] previousState , byte [ ] newState ) ;
2020-04-15 14:50:43 +08:00
private void updateBindables ( )
{
CanUndo . Value = savedStates . Count > 0 & & currentState > 0 ;
CanRedo . Value = currentState < savedStates . Count - 1 ;
2020-04-09 20:22:07 +08:00
}
}
}