Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
undo_manager.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <chrono>
7#include <string>
8#include <vector>
9#include <functional>
10#include <memory>
11
12namespace pulp::state {
13
23struct UndoAction {
24 std::string name;
25 std::function<void()> undo_fn;
26 std::function<void()> redo_fn;
27
28 static std::unique_ptr<UndoAction> create(
29 std::string action_name,
30 std::function<void()> undo,
31 std::function<void()> redo)
32 {
33 auto a = std::make_unique<UndoAction>();
34 a->name = std::move(action_name);
35 a->undo_fn = std::move(undo);
36 a->redo_fn = std::move(redo);
37 return a;
38 }
39};
40
43 std::string name;
44 std::vector<std::unique_ptr<UndoAction>> actions;
45};
46
71public:
80 void perform(std::unique_ptr<UndoAction> action) {
81 if (action->redo_fn) action->redo_fn();
82
83 if (in_transaction_) {
84 current_transaction_->actions.push_back(std::move(action));
85 return;
86 }
87
88 const auto now = Clock::now();
89 if (coalesce_window_.count() > 0 && !undo_stack_.empty() &&
90 (now - last_perform_time_) <= coalesce_window_) {
91 // A new edit invalidates redo history even when it merges into
92 // the prior transaction — otherwise undo+new-edit could leave
93 // stale redo entries that no longer make sense.
94 redo_stack_.clear();
95 undo_stack_.back().actions.push_back(std::move(action));
96 last_perform_time_ = now;
98 return;
99 }
100
102 tx.name = action->name;
103 tx.actions.push_back(std::move(action));
104 push_undo(std::move(tx));
105 last_perform_time_ = now;
106 }
107
110 void add_without_executing(std::unique_ptr<UndoAction> action) {
111 if (in_transaction_) {
112 current_transaction_->actions.push_back(std::move(action));
113 } else {
115 tx.name = action->name;
116 tx.actions.push_back(std::move(action));
117 push_undo(std::move(tx));
118 }
119 }
120
122 void begin_transaction(const std::string& name) {
123 current_transaction_ = std::make_unique<UndoTransaction>();
124 current_transaction_->name = name;
125 in_transaction_ = true;
126 }
127
130 if (in_transaction_ && current_transaction_ && !current_transaction_->actions.empty()) {
131 push_undo(std::move(*current_transaction_));
132 }
133 current_transaction_.reset();
134 in_transaction_ = false;
135 }
136
139 // Undo all actions in the transaction
140 if (in_transaction_ && current_transaction_) {
141 for (auto it = current_transaction_->actions.rbegin();
142 it != current_transaction_->actions.rend(); ++it) {
143 if ((*it)->undo_fn) (*it)->undo_fn();
144 }
145 }
146 current_transaction_.reset();
147 in_transaction_ = false;
148 }
149
151 bool undo() {
152 if (undo_stack_.empty()) return false;
153
154 auto tx = std::move(undo_stack_.back());
155 undo_stack_.pop_back();
156
157 // Undo actions in reverse order
158 for (auto it = tx.actions.rbegin(); it != tx.actions.rend(); ++it) {
159 if ((*it)->undo_fn) (*it)->undo_fn();
160 }
161
162 redo_stack_.push_back(std::move(tx));
163 // Reset the coalesce timestamp so the next edit starts a fresh
164 // window — a `perform()` after `undo()` must never merge into
165 // the older entry that's still on the undo stack.
166 last_perform_time_ = Clock::time_point{};
168 return true;
169 }
170
172 bool redo() {
173 if (redo_stack_.empty()) return false;
174
175 auto tx = std::move(redo_stack_.back());
176 redo_stack_.pop_back();
177
178 // Redo actions in forward order
179 for (auto& action : tx.actions) {
180 if (action->redo_fn) action->redo_fn();
181 }
182
183 undo_stack_.push_back(std::move(tx));
184 last_perform_time_ = Clock::time_point{};
186 return true;
187 }
188
190 bool can_undo() const { return !undo_stack_.empty(); }
191
193 bool can_redo() const { return !redo_stack_.empty(); }
194
196 std::string undo_name() const {
197 return undo_stack_.empty() ? "" : undo_stack_.back().name;
198 }
199
201 std::string redo_name() const {
202 return redo_stack_.empty() ? "" : redo_stack_.back().name;
203 }
204
206 int undo_count() const { return static_cast<int>(undo_stack_.size()); }
207
209 int redo_count() const { return static_cast<int>(redo_stack_.size()); }
210
212 void clear() {
213 undo_stack_.clear();
214 redo_stack_.clear();
216 }
217
219 void set_max_history(int max) {
220 max_history_ = max < 0 ? 0 : max;
221 trim_to_max_history();
222 }
223 int max_history() const { return max_history_; }
224
226 std::function<void()> on_state_changed;
227
233 void set_coalesce_window(std::chrono::milliseconds window) {
234 coalesce_window_ = window;
235 }
236 std::chrono::milliseconds coalesce_window() const { return coalesce_window_; }
237
238private:
239 using Clock = std::chrono::steady_clock;
240
241 std::vector<UndoTransaction> undo_stack_;
242 std::vector<UndoTransaction> redo_stack_;
243 int max_history_ = 100;
244 bool in_transaction_ = false;
245 std::unique_ptr<UndoTransaction> current_transaction_;
246 std::chrono::milliseconds coalesce_window_{0};
247 Clock::time_point last_perform_time_{};
248
249 void push_undo(UndoTransaction tx) {
250 redo_stack_.clear(); // new action invalidates redo
251 undo_stack_.push_back(std::move(tx));
252 trim_to_max_history();
254 }
255
256 void trim_to_max_history() {
257 while (static_cast<int>(undo_stack_.size()) > max_history_) {
258 undo_stack_.erase(undo_stack_.begin());
259 }
260 }
261};
262
263} // namespace pulp::state
Manages undo/redo history with configurable depth.
Definition undo_manager.hpp:70
std::string redo_name() const
Get the name of the next redo action.
Definition undo_manager.hpp:201
std::function< void()> on_state_changed
Called whenever the undo/redo state changes.
Definition undo_manager.hpp:226
int max_history() const
Definition undo_manager.hpp:223
void add_without_executing(std::unique_ptr< UndoAction > action)
Perform an action without immediately executing it.
Definition undo_manager.hpp:110
std::chrono::milliseconds coalesce_window() const
Definition undo_manager.hpp:236
void set_coalesce_window(std::chrono::milliseconds window)
Time-window coalescing.
Definition undo_manager.hpp:233
void cancel_transaction()
Cancel the current transaction (discard without pushing).
Definition undo_manager.hpp:138
void end_transaction()
End the current transaction and push it to the undo stack.
Definition undo_manager.hpp:129
bool redo()
Redo the most recently undone action/transaction.
Definition undo_manager.hpp:172
void begin_transaction(const std::string &name)
Begin a transaction — all subsequent perform() calls are grouped.
Definition undo_manager.hpp:122
std::string undo_name() const
Get the name of the next undo action (for menu display).
Definition undo_manager.hpp:196
int redo_count() const
Number of redo steps available.
Definition undo_manager.hpp:209
void perform(std::unique_ptr< UndoAction > action)
Perform an action and add it to the undo history.
Definition undo_manager.hpp:80
void set_max_history(int max)
Set maximum undo history depth. Oldest entries are discarded.
Definition undo_manager.hpp:219
void clear()
Clear all undo/redo history.
Definition undo_manager.hpp:212
bool undo()
Undo the most recent action/transaction.
Definition undo_manager.hpp:151
bool can_undo() const
Check if undo is available.
Definition undo_manager.hpp:190
bool can_redo() const
Check if redo is available.
Definition undo_manager.hpp:193
int undo_count() const
Number of undo steps available.
Definition undo_manager.hpp:206
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
A single undoable action with undo and redo functions.
Definition undo_manager.hpp:23
std::string name
Definition undo_manager.hpp:24
static std::unique_ptr< UndoAction > create(std::string action_name, std::function< void()> undo, std::function< void()> redo)
Definition undo_manager.hpp:28
std::function< void()> undo_fn
Definition undo_manager.hpp:25
std::function< void()> redo_fn
Definition undo_manager.hpp:26
A group of actions that undo/redo together as one step.
Definition undo_manager.hpp:42
std::vector< std::unique_ptr< UndoAction > > actions
Definition undo_manager.hpp:44
std::string name
Definition undo_manager.hpp:43