yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_menu.cc
Go to the documentation of this file.
1#include "canvas_menu.h"
2
4#include "imgui/imgui_internal.h"
5
6namespace yaze {
7namespace gui {
8
9namespace {
10
11constexpr ImGuiStyleVar kPopupSpacingVars[] = {
12 ImGuiStyleVar_WindowPadding,
13 ImGuiStyleVar_FramePadding,
14 ImGuiStyleVar_ItemSpacing,
15 ImGuiStyleVar_ItemInnerSpacing,
16};
17
18} // namespace
19
20ImVec2 BaseStyleVarVec2(ImGuiStyleVar idx) {
21 ImGuiContext* g = ImGui::GetCurrentContext();
22 if (g == nullptr) {
23 return ImVec2(0, 0);
24 }
25 // The oldest backup of `idx` holds the value before any push of it.
26 for (const ImGuiStyleMod& mod : g->StyleVarStack) {
27 if (mod.VarIdx == idx) {
28 return ImVec2(mod.BackupFloat[0], mod.BackupFloat[1]);
29 }
30 }
31 const ImGuiStyleVarInfo* info = ImGui::GetStyleVarInfo(idx);
32 return *static_cast<const ImVec2*>(info->GetVarPtr(&g->Style));
33}
34
36 if (ImGui::GetCurrentContext() == nullptr) {
37 return;
38 }
39 for (const ImGuiStyleVar idx : kPopupSpacingVars) {
40 ImGui::PushStyleVar(idx, BaseStyleVarVec2(idx));
41 ++pushed_;
42 }
43}
44
46 if (pushed_ > 0) {
47 ImGui::PopStyleVar(pushed_);
48 }
49}
50
51void MenuConfirmState::NoteRendered(ImGuiID id, int frame) {
52 if (armed_id_ != id) {
53 return;
54 }
55 if (frame > last_frame_ + 1) {
56 // The item was not drawn on the previous frame: its menu was closed.
57 Reset();
58 return;
59 }
60 last_frame_ = frame;
61}
62
63bool MenuConfirmState::IsArmed(ImGuiID id, int frame) const {
64 return id != 0 && armed_id_ == id && frame <= last_frame_ + 1;
65}
66
67bool MenuConfirmState::Click(ImGuiID id, int frame) {
68 if (IsArmed(id, frame)) {
69 Reset();
70 return true;
71 }
72 armed_id_ = id;
73 last_frame_ = frame;
74 return false;
75}
76
78 static MenuConfirmState state;
79 return state;
80}
81
82std::string ConfirmLabel(const std::string& label) {
83 std::string base = label;
84 static const std::string kUtf8Ellipsis = "\xE2\x80\xA6";
85 if (base.size() >= 3 && base.compare(base.size() - 3, 3, "...") == 0) {
86 base.resize(base.size() - 3);
87 } else if (base.size() >= kUtf8Ellipsis.size() &&
88 base.compare(base.size() - kUtf8Ellipsis.size(),
89 kUtf8Ellipsis.size(), kUtf8Ellipsis) == 0) {
90 base.resize(base.size() - kUtf8Ellipsis.size());
91 }
92 return "Confirm " + base + "?";
93}
94
96 const CanvasMenuItem& item,
97 std::function<void(const std::string&, std::function<void()>)>
98 popup_opened_callback) {
99 // Check visibility
100 if (!item.visible_condition()) {
101 return;
102 }
103
104 const bool enabled = item.enabled_condition();
105 const bool checked =
106 item.checked_condition ? item.checked_condition() : false;
107
108 // Apply disabled state if needed
109 if (!enabled) {
110 ImGui::BeginDisabled();
111 }
112
113 // Build label with icon if present
114 std::string display_label = item.label;
115 if (!item.icon.empty()) {
116 display_label = item.icon + " " + item.label;
117 }
118
119 // Render menu item based on type
120 if (item.subitems.empty()) {
121 const bool has_custom_color = item.color.x != 1.0f ||
122 item.color.y != 1.0f ||
123 item.color.z != 1.0f || item.color.w != 1.0f;
124 const bool push_color = item.destructive || has_custom_color;
125
126 // Confirm-gated items keep a stable ImGui ID while their label changes.
127 bool armed = false;
128 ImGuiID confirm_id = 0;
129 const int frame = ImGui::GetFrameCount();
130 if (item.requires_confirmation) {
131 confirm_id = ImGui::GetID(item.label.c_str());
132 auto& confirm = GetMenuConfirmState();
133 confirm.NoteRendered(confirm_id, frame);
134 armed = confirm.IsArmed(confirm_id, frame);
135 std::string text = armed ? ConfirmLabel(item.label) : item.label;
136 if (!item.icon.empty()) {
137 text = item.icon + " " + text;
138 }
139 display_label = text + "###" + item.label;
140 }
141
142 const char* shortcut =
143 armed ? "click again"
144 : (item.shortcut.empty() ? nullptr : item.shortcut.c_str());
145
146 if (push_color) {
147 ImGui::PushStyleColor(ImGuiCol_Text,
148 item.destructive ? GetErrorColor() : item.color);
149 }
150 // The arming click must not close the popup; the confirming click does.
151 if (item.requires_confirmation) {
152 ImGui::PushItemFlag(ImGuiItemFlags_AutoClosePopups, armed);
153 }
154 bool selected =
156 ? ImGui::MenuItem(display_label.c_str(), shortcut, checked)
157 : ImGui::MenuItem(display_label.c_str(), shortcut);
158 if (item.requires_confirmation) {
159 ImGui::PopItemFlag();
160 }
161 if (push_color) {
162 ImGui::PopStyleColor();
163 }
164 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
165 if (armed && item.tooltip.empty()) {
166 ImGui::SetTooltip("This cannot be undone.");
167 } else if (!item.tooltip.empty()) {
168 ImGui::SetTooltip("%s", item.tooltip.c_str());
169 }
170 }
171
172 if (selected && item.requires_confirmation) {
173 selected = GetMenuConfirmState().Click(confirm_id, frame);
174 }
175
176 if (selected) {
177 // Invoke callback
178 if (item.callback) {
179 item.callback();
180 }
181
182 // Handle popup if defined
183 if (item.popup.has_value() && item.popup->auto_open_on_select &&
184 popup_opened_callback) {
185 popup_opened_callback(item.popup->popup_id,
186 item.popup->render_callback);
187 }
188 }
189 } else {
190 // Submenu
191 if (ImGui::BeginMenu(display_label.c_str())) {
192 for (const auto& subitem : item.subitems) {
193 RenderMenuItem(subitem, popup_opened_callback);
194 }
195 ImGui::EndMenu();
196 }
197 }
198
199 // Restore enabled state
200 if (!enabled) {
201 ImGui::EndDisabled();
202 }
203
204 // Render separator if requested
205 if (item.separator_after) {
206 ImGui::Separator();
207 }
208}
209
211 const CanvasMenuSection& section,
212 std::function<void(const std::string&, std::function<void()>)>
213 popup_opened_callback) {
214 // Skip empty sections
215 if (section.items.empty()) {
216 return;
217 }
218
219 // Render section title if present
220 if (!section.title.empty()) {
221 ImGui::TextColored(section.title_color, "%s", section.title.c_str());
222 ImGui::Separator();
223 }
224
225 // Render all items in section
226 for (const auto& item : section.items) {
227 RenderMenuItem(item, popup_opened_callback);
228 }
229
230 // Render separator after section if requested
231 if (section.separator_after) {
232 ImGui::Separator();
233 }
234}
235
237 const CanvasMenuDefinition& menu,
238 std::function<void(const std::string&, std::function<void()>)>
239 popup_opened_callback) {
240 // Skip disabled menus
241 if (!menu.enabled) {
242 return;
243 }
244
245 // Render all sections
246 for (const auto& section : menu.sections) {
247 RenderMenuSection(section, popup_opened_callback);
248 }
249}
250
251} // namespace gui
252} // namespace yaze
Arming state for confirm-gated menu items (ImGui-free, testable).
bool IsArmed(ImGuiID id, int frame) const
bool Click(ImGuiID id, int frame)
void NoteRendered(ImGuiID id, int frame)
MenuConfirmState & GetMenuConfirmState()
void RenderCanvasMenu(const CanvasMenuDefinition &menu, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a complete menu definition.
std::string ConfirmLabel(const std::string &label)
void RenderMenuSection(const CanvasMenuSection &section, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a menu section.
ImVec2 BaseStyleVarVec2(ImGuiStyleVar idx)
void RenderMenuItem(const CanvasMenuItem &item, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a single menu item.
Complete menu definition.
std::vector< CanvasMenuSection > sections
Declarative menu item definition.
Definition canvas_menu.h:64
std::function< bool()> checked_condition
Definition canvas_menu.h:95
std::vector< CanvasMenuItem > subitems
Definition canvas_menu.h:98
std::function< bool()> enabled_condition
Definition canvas_menu.h:85
std::function< void()> callback
Definition canvas_menu.h:79
std::optional< CanvasPopupDefinition > popup
Definition canvas_menu.h:82
std::function< bool()> visible_condition
Definition canvas_menu.h:90
Menu section grouping related menu items.
std::vector< CanvasMenuItem > items