diff --git a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs
index 87a29a6237..3d3539ee2f 100644
--- a/osu.Game/Screens/Edit/TransactionalCommitComponent.cs
+++ b/osu.Game/Screens/Edit/TransactionalCommitComponent.cs
@@ -2,14 +2,30 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Graphics;
namespace osu.Game.Screens.Edit
{
///
/// A component that tracks a batch change, only applying after all active changes are completed.
///
- public abstract class TransactionalCommitComponent
+ public abstract class TransactionalCommitComponent : Component
{
+ ///
+ /// Fires whenever a transaction begins. Will not fire on nested transactions.
+ ///
+ public event Action TransactionBegan;
+
+ ///
+ /// Fires when the last transaction completes.
+ ///
+ public event Action TransactionEnded;
+
+ ///
+ /// Fires when is called and results in a non-transactional state save.
+ ///
+ public event Action SaveStateTriggered;
+
public bool TransactionActive => bulkChangesStarted > 0;
private int bulkChangesStarted;
@@ -17,7 +33,11 @@ namespace osu.Game.Screens.Edit
///
/// Signal the beginning of a change.
///
- public void BeginChange() => bulkChangesStarted++;
+ public void BeginChange()
+ {
+ if (bulkChangesStarted++ == 0)
+ TransactionBegan?.Invoke();
+ }
///
/// Signal the end of a change.
@@ -29,14 +49,22 @@ namespace osu.Game.Screens.Edit
throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}.");
if (--bulkChangesStarted == 0)
+ {
UpdateState();
+ TransactionEnded?.Invoke();
+ }
}
+ ///
+ /// Force an update of the state with no attached transaction.
+ /// This is a no-op if a transaction is already active. Should generally be used as a safety measure to ensure granular changes are not left outside a transaction.
+ ///
public void SaveState()
{
if (bulkChangesStarted > 0)
return;
+ SaveStateTriggered?.Invoke();
UpdateState();
}