2020-04-09 12:22:07 +00: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 ;
using System.Text ;
2020-04-15 06:50:43 +00:00
using osu.Framework.Bindables ;
2020-04-09 12:22:07 +00:00
using osu.Game.Beatmaps.Formats ;
using osu.Game.Rulesets.Objects ;
namespace osu.Game.Screens.Edit
{
/// <summary>
/// Tracks changes to the <see cref="Editor"/>.
/// </summary>
public class EditorChangeHandler : IEditorChangeHandler
{
2020-04-15 06:50:43 +00:00
public readonly Bindable < bool > CanUndo = new Bindable < bool > ( ) ;
public readonly Bindable < bool > CanRedo = new Bindable < bool > ( ) ;
2020-04-13 08:18:50 +00:00
2020-04-15 06:50:43 +00:00
private readonly LegacyEditorBeatmapPatcher patcher ;
2020-04-13 08:18:50 +00:00
private readonly List < byte [ ] > savedStates = new List < byte [ ] > ( ) ;
2020-04-09 12:22:07 +00:00
private int currentState = - 1 ;
private readonly EditorBeatmap editorBeatmap ;
private int bulkChangesStarted ;
private bool isRestoring ;
2020-04-13 11:34:18 +00:00
public const int MAX_SAVED_STATES = 50 ;
2020-04-09 12:22:07 +00:00
/// <summary>
/// Creates a new <see cref="EditorChangeHandler"/>.
/// </summary>
/// <param name="editorBeatmap">The <see cref="EditorBeatmap"/> to track the <see cref="HitObject"/>s of.</param>
public EditorChangeHandler ( EditorBeatmap editorBeatmap )
{
this . editorBeatmap = editorBeatmap ;
editorBeatmap . HitObjectAdded + = hitObjectAdded ;
editorBeatmap . HitObjectRemoved + = hitObjectRemoved ;
editorBeatmap . HitObjectUpdated + = hitObjectUpdated ;
2020-04-13 08:20:01 +00:00
patcher = new LegacyEditorBeatmapPatcher ( editorBeatmap ) ;
2020-04-09 12:22:07 +00:00
// Initial state.
SaveState ( ) ;
}
private void hitObjectAdded ( HitObject obj ) = > SaveState ( ) ;
private void hitObjectRemoved ( HitObject obj ) = > SaveState ( ) ;
private void hitObjectUpdated ( HitObject obj ) = > SaveState ( ) ;
public void BeginChange ( ) = > bulkChangesStarted + + ;
public void EndChange ( )
{
if ( bulkChangesStarted = = 0 )
throw new InvalidOperationException ( $"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}." ) ;
if ( - - bulkChangesStarted = = 0 )
SaveState ( ) ;
}
/// <summary>
/// Saves the current <see cref="Editor"/> state.
/// </summary>
public void SaveState ( )
{
if ( bulkChangesStarted > 0 )
return ;
if ( isRestoring )
return ;
if ( currentState < savedStates . Count - 1 )
savedStates . RemoveRange ( currentState + 1 , savedStates . Count - currentState - 1 ) ;
2020-04-13 11:34:18 +00:00
if ( savedStates . Count > MAX_SAVED_STATES )
savedStates . RemoveAt ( 0 ) ;
2020-04-13 08:18:50 +00:00
using ( var stream = new MemoryStream ( ) )
{
using ( var sw = new StreamWriter ( stream , Encoding . UTF8 , 1024 , true ) )
new LegacyBeatmapEncoder ( editorBeatmap ) . Encode ( sw ) ;
savedStates . Add ( stream . ToArray ( ) ) ;
}
2020-04-09 12:22:07 +00:00
currentState = savedStates . Count - 1 ;
2020-04-15 06:50:43 +00:00
updateBindables ( ) ;
2020-04-09 12:22:07 +00: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 )
{
if ( bulkChangesStarted > 0 )
return ;
if ( savedStates . Count = = 0 )
return ;
int newState = Math . Clamp ( currentState + direction , 0 , savedStates . Count - 1 ) ;
if ( currentState = = newState )
return ;
isRestoring = true ;
2020-04-13 08:20:01 +00:00
patcher . Patch ( savedStates [ currentState ] , savedStates [ newState ] ) ;
2020-04-09 12:22:07 +00:00
currentState = newState ;
isRestoring = false ;
2020-04-15 06:50:43 +00:00
updateBindables ( ) ;
}
private void updateBindables ( )
{
CanUndo . Value = savedStates . Count > 0 & & currentState > 0 ;
CanRedo . Value = currentState < savedStates . Count - 1 ;
2020-04-09 12:22:07 +00:00
}
}
}