yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
menu_builder.cc
Go to the documentation of this file.
2
3#include "absl/strings/str_cat.h"
5#include "util/i18n/tr.h"
6
7namespace yaze {
8namespace editor {
9
10MenuBuilder& MenuBuilder::BeginMenu(const char* label, const char* icon) {
11 Menu menu;
12 menu.label = label;
13 if (icon) {
14 menu.icon = icon;
15 }
16 menus_.push_back(menu);
17 current_menu_ = &menus_.back();
18 return *this;
19}
20
21MenuBuilder& MenuBuilder::BeginSubMenu(const char* label, const char* icon,
22 EnabledCheck enabled) {
23 if (!current_menu_)
24 return *this;
25
26 MenuItem item;
28 item.label = label;
29 if (icon) {
30 item.icon = icon;
31 }
32 item.enabled = enabled;
33 current_menu_->items.push_back(item);
34 return *this;
35}
36
38 if (!current_menu_)
39 return *this;
40
41 // Check if we're ending a submenu or top-level menu
42 // We need to track nesting depth to handle nested submenus correctly
43 bool is_submenu = false;
44 int depth = 0;
45
46 for (auto it = current_menu_->items.rbegin();
47 it != current_menu_->items.rend(); ++it) {
48 if (it->type == MenuItem::Type::kSubMenuEnd) {
49 depth++; // Found an end, so we need to skip its matching begin
50 } else if (it->type == MenuItem::Type::kSubMenuBegin) {
51 if (depth == 0) {
52 // Found an unmatched begin - this is what we're closing
53 is_submenu = true;
54 break;
55 }
56 depth--; // This begin matches a previous end
57 }
58 }
59
60 if (is_submenu) {
61 MenuItem item;
63 current_menu_->items.push_back(item);
64 } else {
65 current_menu_ = nullptr;
66 }
67
68 return *this;
69}
70
71MenuBuilder& MenuBuilder::Item(const char* label, const char* icon,
72 Callback callback, const char* shortcut,
73 EnabledCheck enabled, EnabledCheck checked) {
74 if (!current_menu_)
75 return *this;
76
77 MenuItem item;
79 item.label = label;
80 if (icon) {
81 item.icon = icon;
82 }
83 if (shortcut) {
84 item.shortcut = shortcut;
85 }
86 item.callback = callback;
87 item.enabled = enabled;
88 item.checked = checked;
89 current_menu_->items.push_back(item);
90 return *this;
91}
92
93MenuBuilder& MenuBuilder::Item(const char* label, Callback callback,
94 const char* shortcut, EnabledCheck enabled) {
95 return Item(label, nullptr, callback, shortcut, enabled, nullptr);
96}
97
99 if (!current_menu_)
100 return *this;
101
102 MenuItem item;
104 current_menu_->items.push_back(item);
105 return *this;
106}
107
108MenuBuilder& MenuBuilder::DisabledItem(const char* label, const char* icon) {
109 if (!current_menu_)
110 return *this;
111
112 MenuItem item;
114 item.label = label;
115 if (icon) {
116 item.icon = icon;
117 }
118 current_menu_->items.push_back(item);
119 return *this;
120}
121
123 Callback draw_callback) {
124 Menu menu;
125 menu.label = label;
126 menu.custom_draw = draw_callback;
127 menu.is_custom = true;
128 menus_.push_back(menu);
129 current_menu_ = nullptr; // Custom menus don't use the builder pattern
130 return *this;
131}
132
134 for (const auto& menu : menus_) {
135 // Don't add icons to top-level menus as they get cut off
136 std::string menu_label = tr(menu.label.c_str());
137
138 if (menu.is_custom) {
139 // Custom menu with callback for dynamic content
140 if (ImGui::BeginMenu(menu_label.c_str())) {
141 if (menu.custom_draw) {
142 menu.custom_draw();
143 }
144 ImGui::EndMenu();
145 }
146 } else {
147 // Standard menu with predefined items
148 if (ImGui::BeginMenu(menu_label.c_str())) {
149 submenu_stack_.clear(); // Reset submenu stack for each top-level menu
150 skip_depth_ = 0; // Reset skip depth
151 for (const auto& item : menu.items) {
152 DrawMenuItem(item);
153 }
154 ImGui::EndMenu();
155 }
156 }
157 }
158}
159
161 switch (item.type) {
163 if (skip_depth_ == 0) {
164 ImGui::Separator();
165 }
166 break;
167
169 if (skip_depth_ == 0) {
170 std::string label =
171 item.icon.empty()
172 ? std::string(tr(item.label.c_str()))
173 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
174 ImGui::BeginDisabled();
175 ImGui::MenuItem(label.c_str(), nullptr, false, false);
176 ImGui::EndDisabled();
177 }
178 break;
179 }
180
182 // If we're already skipping, increment skip depth and continue
183 if (skip_depth_ > 0) {
184 skip_depth_++;
185 submenu_stack_.push_back(false);
186 break;
187 }
188
189 std::string label =
190 item.icon.empty()
191 ? std::string(tr(item.label.c_str()))
192 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
193
194 bool enabled = !item.enabled || item.enabled();
195 bool opened = false;
196
197 if (!enabled) {
198 // Disabled submenu - show as disabled item but don't open
199 ImGui::BeginDisabled();
200 ImGui::MenuItem(label.c_str(), nullptr, false, false);
201 ImGui::EndDisabled();
202 submenu_stack_.push_back(false);
203 skip_depth_++; // Skip contents of disabled submenu
204 } else {
205 // BeginMenu returns true if submenu is currently open/visible
206 opened = ImGui::BeginMenu(label.c_str());
207 submenu_stack_.push_back(opened);
208 if (!opened) {
209 skip_depth_++; // Skip contents of closed submenu
210 }
211 }
212 break;
213 }
214
216 // Decrement skip depth if we were skipping
217 if (skip_depth_ > 0) {
218 skip_depth_--;
219 }
220
221 // Pop the stack and call EndMenu only if submenu was opened
222 if (!submenu_stack_.empty()) {
223 bool was_opened = submenu_stack_.back();
224 submenu_stack_.pop_back();
225 if (was_opened && skip_depth_ == 0) {
226 ImGui::EndMenu();
227 }
228 }
229 break;
230
232 if (skip_depth_ > 0) {
233 break; // Skip items in closed submenus
234 }
235
236 std::string label =
237 item.icon.empty()
238 ? std::string(tr(item.label.c_str()))
239 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
240
241 bool enabled = !item.enabled || item.enabled();
242 bool checked = item.checked && item.checked();
243
244 const char* shortcut_str =
245 item.shortcut.empty() ? nullptr : item.shortcut.c_str();
246
247 const bool activated =
248 ImGui::MenuItem(label.c_str(), shortcut_str, checked, enabled);
249 if (item.label == "Save ROM" || item.label == "Quit") {
250 gui::AutoRegisterLastItem("menuitem", item.label,
251 "Stable file-menu harness action");
252 }
253 if (activated) {
254 if (item.callback) {
255 item.callback();
256 }
257 }
258 break;
259 }
260 }
261}
262
264 menus_.clear();
265 current_menu_ = nullptr;
266}
267
268} // namespace editor
269} // namespace yaze
Fluent interface for building ImGui menus with icons.
MenuBuilder & Item(const char *label, const char *icon, Callback callback, const char *shortcut=nullptr, EnabledCheck enabled=nullptr, EnabledCheck checked=nullptr)
Add a menu item.
MenuBuilder & CustomMenu(const char *label, Callback draw_callback)
Add a custom menu with a callback for drawing dynamic content.
void Draw()
Draw the menu bar (call in main menu bar)
void Clear()
Clear all menus.
std::function< bool()> EnabledCheck
MenuBuilder & BeginMenu(const char *label, const char *icon=nullptr)
Begin a top-level menu.
MenuBuilder & Separator()
Add a separator.
MenuBuilder & EndMenu()
End the current menu/submenu.
MenuBuilder & BeginSubMenu(const char *label, const char *icon=nullptr, EnabledCheck enabled=nullptr)
Begin a submenu.
std::function< void()> Callback
void DrawMenuItem(const MenuItem &item)
std::vector< Menu > menus_
MenuBuilder & DisabledItem(const char *label, const char *icon=nullptr)
Add a disabled item (grayed out)
std::vector< bool > submenu_stack_
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
std::vector< MenuItem > items
Automatic widget registration helpers for ImGui Test Engine integration.