Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
edit_history.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <chrono>
4#include <functional>
5#include <memory>
6#include <string>
7#include <vector>
8
9namespace pulp::state {
10
12struct EditAction {
13 virtual ~EditAction() = default;
14 virtual void undo() = 0;
15 virtual void redo() = 0;
16 virtual std::string description() const { return ""; }
17};
18
20class LambdaEdit : public EditAction {
21public:
22 LambdaEdit(std::function<void()> do_fn, std::function<void()> undo_fn,
23 std::string desc = "")
24 : do_(std::move(do_fn)), undo_(std::move(undo_fn)), desc_(std::move(desc)) {}
25
26 void undo() override { if (undo_) undo_(); }
27 void redo() override { if (do_) do_(); }
28 std::string description() const override { return desc_; }
29
30private:
31 std::function<void()> do_;
32 std::function<void()> undo_;
33 std::string desc_;
34};
35
40public:
41 explicit EditHistory(size_t max_depth = 100) : max_depth_(max_depth) {}
42
49 void perform(std::unique_ptr<EditAction> action) {
50 action->redo();
51 // A new edit invalidates redo history even when it coalesces into
52 // the current undo step.
53 redo_stack_.clear();
54 const auto now = Clock::now();
55 const bool same_description =
56 coalesce_ && !undo_stack_.empty() &&
57 !action->description().empty() &&
58 undo_stack_.back()->description() == action->description();
59 const bool within_window =
60 coalesce_window_.count() > 0 && !undo_stack_.empty() &&
61 (now - last_perform_time_) <= coalesce_window_;
62 if (same_description || (coalesce_ && within_window)) {
63 undo_stack_.back() = std::move(action);
64 last_perform_time_ = now;
65 return;
66 }
67 undo_stack_.push_back(std::move(action));
68 last_perform_time_ = now;
69 // Enforce depth limit
70 while (undo_stack_.size() > max_depth_)
71 undo_stack_.erase(undo_stack_.begin());
72 }
73
76 void set_coalesce(bool enabled) { coalesce_ = enabled; }
77
83 void set_coalesce_window(std::chrono::milliseconds window) {
84 coalesce_window_ = window;
85 }
86 std::chrono::milliseconds coalesce_window() const { return coalesce_window_; }
87
89 void perform(std::function<void()> do_fn, std::function<void()> undo_fn,
90 const std::string& desc = "") {
91 perform(std::make_unique<LambdaEdit>(std::move(do_fn), std::move(undo_fn), desc));
92 }
93
95 bool undo() {
96 if (undo_stack_.empty()) return false;
97 auto action = std::move(undo_stack_.back());
98 undo_stack_.pop_back();
99 action->undo();
100 redo_stack_.push_back(std::move(action));
101 // Reset coalesce timestamp so a new edit right after undo doesn't
102 // merge into the now-older `undo_stack_.back()` entry.
103 last_perform_time_ = Clock::time_point{};
104 return true;
105 }
106
108 bool redo() {
109 if (redo_stack_.empty()) return false;
110 auto action = std::move(redo_stack_.back());
111 redo_stack_.pop_back();
112 action->redo();
113 undo_stack_.push_back(std::move(action));
114 last_perform_time_ = Clock::time_point{};
115 return true;
116 }
117
118 bool can_undo() const { return !undo_stack_.empty(); }
119 bool can_redo() const { return !redo_stack_.empty(); }
120 size_t undo_count() const { return undo_stack_.size(); }
121 size_t redo_count() const { return redo_stack_.size(); }
122
124 void clear() {
125 undo_stack_.clear();
126 redo_stack_.clear();
127 }
128
130 std::string undo_description() const {
131 return undo_stack_.empty() ? "" : undo_stack_.back()->description();
132 }
133
134 void set_max_depth(size_t depth) {
135 max_depth_ = depth;
136 while (undo_stack_.size() > max_depth_)
137 undo_stack_.erase(undo_stack_.begin());
138 }
139 size_t max_depth() const { return max_depth_; }
140
141private:
142 using Clock = std::chrono::steady_clock;
143
144 std::vector<std::unique_ptr<EditAction>> undo_stack_;
145 std::vector<std::unique_ptr<EditAction>> redo_stack_;
146 size_t max_depth_;
147 bool coalesce_ = true;
148 std::chrono::milliseconds coalesce_window_{0};
149 Clock::time_point last_perform_time_{};
150};
151
152} // namespace pulp::state
void set_max_depth(size_t depth)
Definition edit_history.hpp:134
void perform(std::function< void()> do_fn, std::function< void()> undo_fn, const std::string &desc="")
Convenience: perform a lambda-based action.
Definition edit_history.hpp:89
void set_coalesce_window(std::chrono::milliseconds window)
Time-window coalescing.
Definition edit_history.hpp:83
std::string undo_description() const
Get the description of the next undo action.
Definition edit_history.hpp:130
size_t redo_count() const
Definition edit_history.hpp:121
std::chrono::milliseconds coalesce_window() const
Definition edit_history.hpp:86
bool can_redo() const
Definition edit_history.hpp:119
size_t undo_count() const
Definition edit_history.hpp:120
bool redo()
Redo the most recently undone action.
Definition edit_history.hpp:108
void perform(std::unique_ptr< EditAction > action)
Execute an action and add it to the history.
Definition edit_history.hpp:49
void clear()
Clear all history.
Definition edit_history.hpp:124
bool undo()
Undo the most recent action.
Definition edit_history.hpp:95
EditHistory(size_t max_depth=100)
Definition edit_history.hpp:41
void set_coalesce(bool enabled)
Enable/disable description-based coalescing (merge rapid changes with same description).
Definition edit_history.hpp:76
size_t max_depth() const
Definition edit_history.hpp:139
bool can_undo() const
Definition edit_history.hpp:118
std::string description() const override
Definition edit_history.hpp:28
void redo() override
Definition edit_history.hpp:27
void undo() override
Definition edit_history.hpp:26
LambdaEdit(std::function< void()> do_fn, std::function< void()> undo_fn, std::string desc="")
Definition edit_history.hpp:22
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
STL namespace.
Base class for undoable actions.
Definition edit_history.hpp:12
virtual std::string description() const
Definition edit_history.hpp:16
virtual ~EditAction()=default
virtual void redo()=0
virtual void undo()=0