Unravel Engine C++ Reference
Loading...
Searching...
No Matches
undo_redo_panel.cpp
Go to the documentation of this file.
1#include "undo_redo_panel.h"
2#include "../panel.h"
3
5#include <editor/shortcuts.h>
6
7#include <imgui/imgui.h>
8#include <array>
9
10namespace unravel
11{
12
14{
15}
16
17void undo_redo_panel::show(bool show)
18{
19 visible_ = show;
20}
21
23{
24 if (!visible_)
25 {
26 return;
27 }
28
29 if (ImGui::Begin("Undo History", &visible_))
30 {
31 auto& editing_mgr = ctx.get_cached<editing_manager>();
32 const auto& undo_stack = editing_mgr.undo_stack;
33
34
35 auto undo = editing_mgr.can_undo() ? "Undo*" : "Undo";
36 auto redo = editing_mgr.can_redo() ? "Redo*" : "Redo";
37 // Action buttons
38 if (ImGui::Button(undo) && editing_mgr.can_undo())
39 {
40 editing_mgr.undo();
41 }
42
43 ImGui::SameLine();
44 if (ImGui::Button(redo) && editing_mgr.can_redo())
45 {
46 editing_mgr.redo();
47 }
48
49 ImGui::SameLine();
50 if (ImGui::Button("Clear Stack"))
51 {
52 editing_mgr.undo_stack.clear();
53 }
54
55 ImGui::Separator();
56
57 if (undo_stack.actions.empty())
58 {
59 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "(No actions in stack)");
60 }
61 else
62 {
63 // Add option to jump to latest state (after all actions) - at top since it's most recent
64 bool is_at_latest_state = (undo_stack.current_index == undo_stack.actions.size());
65 ImGui::PushStyleColor(ImGuiCol_Text, is_at_latest_state ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
66
67 if (ImGui::Selectable(is_at_latest_state ? ICON_MDI_ARROW_RIGHT " [Latest State]" : ICON_MDI_FAST_FORWARD " [Latest State]", is_at_latest_state))
68 {
69 // Redo everything to get to latest state
70 while (editing_mgr.can_redo())
71 {
72 editing_mgr.redo();
73 }
74 }
75
76 if (ImGui::IsItemHovered())
77 {
78 ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(FLT_MAX, 800.0f));
79 ImGui::BeginTooltip();
80 ImGui::Text("Latest State (all actions executed)");
81 if (!is_at_latest_state)
82 {
83 size_t redo_steps = undo_stack.actions.size() - undo_stack.current_index;
84 ImGui::Separator();
85 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.0f, 1.0f),
86 "Click to redo %zu step%s to latest state",
87 redo_steps,
88 redo_steps == 1 ? "" : "s");
89 }
90 else
91 {
92 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "← Current Position");
93 }
94 ImGui::EndTooltip();
95 }
96
97 ImGui::PopStyleColor();
98
99 // Draw separator
100 ImGui::Separator();
101
102 // Draw the stack with visual indicators (in reverse order - most recent first)
103 for (size_t idx = 0; idx < undo_stack.actions.size(); ++idx)
104 {
105 // Reverse the index to draw from newest to oldest
106 size_t i = undo_stack.actions.size() - 1 - idx;
107 const auto& action = undo_stack.actions[i];
108
109 // Determine the status of this action
110 bool is_executed = i < undo_stack.current_index;
111 bool is_current = (i == undo_stack.current_index - 1) && undo_stack.current_index > 0;
112
113 // Only check validity for actions adjacent to current position
114 bool is_adjacent_to_current = (i == undo_stack.current_index - 1) || (i == undo_stack.current_index);
115 bool is_invalid = is_adjacent_to_current && !action->is_valid();
116
117 // Choose colors based on status
118 ImVec4 text_color;
119 const char* status_icon = "";
120
121 if(is_invalid)
122 {
123 text_color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Red for invalid
124 status_icon = ICON_MDI_ALERT " ";
125 }
126 else
127 {
128 if (is_current)
129 {
130 text_color = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); // Green for current
131 status_icon = ICON_MDI_ARROW_RIGHT " ";
132 }
133 else if (is_executed)
134 {
135 text_color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White for executed
136 status_icon = ICON_MDI_CHECK " ";
137 }
138 else
139 {
140 text_color = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Gray for future
141 status_icon = ICON_MDI_CLOCK_OUTLINE " ";
142 }
143 }
144 // Make the action clickable/selectable
145 ImGui::PushStyleColor(ImGuiCol_Text, text_color);
146
147 // Use Selectable to make it clickable with hover effects
148 bool is_selected = is_current;
149
150 auto selectable_label = fmt::format("{} [{}] {}", status_icon, i, action->get_name());
151
152 if (ImGui::Selectable(selectable_label.c_str(), is_selected))
153 {
154 // Calculate target position: clicking on action i means we want current_index to be i+1
155 size_t target_index = i + 1;
156
157 // Execute undo/redo operations to reach the target position
158 while (undo_stack.current_index != target_index)
159 {
160 if (undo_stack.current_index > target_index)
161 {
162 // Need to undo
163 if (editing_mgr.can_undo())
164 {
165 editing_mgr.undo();
166 }
167 else
168 {
169 break; // Safety break
170 }
171 }
172 else
173 {
174 // Need to redo
175 if (editing_mgr.can_redo())
176 {
177 editing_mgr.redo();
178 }
179 else
180 {
181 break; // Safety break
182 }
183 }
184 }
185 }
186
187 ImGui::PopStyleColor();
188
189 // Add tooltip with additional info and click instructions
190 if (ImGui::IsItemHovered())
191 {
192 ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(FLT_MAX, 800.0f));
193 ImGui::BeginTooltip();
194 if(is_invalid)
195 {
196 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Invalid Action due to missing dependencies.");
197 }
198 else
199 {
200 ImGui::Text("Action: %s", action->get_name().c_str());
201 ImGui::Text("Status: %s", is_executed ? "Executed" : "Not Executed");
202 ImGui::Text("Undoable: %s", action->is_undoable() ? "Yes" : "No");
203
204 action->draw_in_inspector(ctx);
205 }
206 // Add click instruction based on current state
207 size_t target_index = i + 1;
208 if (target_index != undo_stack.current_index)
209 {
210 ImGui::Separator();
211 if (target_index < undo_stack.current_index)
212 {
213 size_t undo_steps = undo_stack.current_index - target_index;
214 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.0f, 1.0f),
215 "Click to undo %zu step%s",
216 undo_steps,
217 undo_steps == 1 ? "" : "s");
218 }
219 else
220 {
221 size_t redo_steps = target_index - undo_stack.current_index;
222 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.0f, 1.0f),
223 "Click to redo %zu step%s",
224 redo_steps,
225 redo_steps == 1 ? "" : "s");
226 }
227 }
228 else if (is_current)
229 {
230 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s Current Position", ICON_MDI_ARROW_LEFT);
231 }
232
233 ImGui::EndTooltip();
234 }
235 }
236
237 // Draw separator
238 ImGui::Separator();
239
240 // Add option to jump to initial state (before any actions) - at bottom since it's oldest
241 bool is_at_initial_state = (undo_stack.current_index == 0);
242 ImGui::PushStyleColor(ImGuiCol_Text, is_at_initial_state ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
243
244 if (ImGui::Selectable(is_at_initial_state ? ICON_MDI_ARROW_RIGHT " [Initial State]" : ICON_MDI_RESTORE " [Initial State]", is_at_initial_state))
245 {
246 // Undo everything to get back to initial state
247 while (editing_mgr.can_undo())
248 {
249 editing_mgr.undo();
250 }
251 }
252
253 if (ImGui::IsItemHovered())
254 {
255 ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(FLT_MAX, 800.0f));
256 ImGui::BeginTooltip();
257 ImGui::Text("Initial State (no actions executed)");
258 if (!is_at_initial_state)
259 {
260 size_t undo_steps = undo_stack.current_index;
261 ImGui::Separator();
262 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.0f, 1.0f),
263 "Click to undo %zu step%s to initial state",
264 undo_steps,
265 undo_steps == 1 ? "" : "s");
266 }
267 else
268 {
269 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s Current Position", ICON_MDI_ARROW_LEFT);
270 }
271 ImGui::EndTooltip();
272 }
273
274 ImGui::PopStyleColor();
275
276 // Add visual indicator for the current position
277 ImGui::Spacing();
278 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.0f, 1.0f), "%s",
279 fmt::format("Current Position: {} / {}", undo_stack.current_index, undo_stack.actions.size()).c_str());
280
281
282 // Progress bar showing position in stack
283 if (!undo_stack.actions.empty())
284 {
285 float progress = static_cast<float>(undo_stack.current_index) / static_cast<float>(undo_stack.actions.size());
286 ImGui::ProgressBar(progress, ImVec2(-1.0f, 0.0f), "");
287 }
288 }
289 }
290 ImGui::End();
291}
292
293} // namespace unravel
undo_redo_panel(imgui_panels *parent)
void on_frame_ui_render(rtti::context &ctx)
void show(bool show=true)
#define ICON_MDI_CLOCK_OUTLINE
#define ICON_MDI_ARROW_LEFT
#define ICON_MDI_ALERT
#define ICON_MDI_RESTORE
#define ICON_MDI_CHECK
#define ICON_MDI_ARROW_RIGHT
#define ICON_MDI_FAST_FORWARD
auto get_cached() -> T &
Definition context.hpp:49