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 if (!current_menu_)
135 return *this;
136
137 MenuItem item;
139 item.callback = draw_callback;
140 current_menu_->items.push_back(item);
141 return *this;
142}
143
145 for (const auto& menu : menus_) {
146 // Don't add icons to top-level menus as they get cut off
147 std::string menu_label = tr(menu.label.c_str());
148
149 const bool open = ImGui::BeginMenu(menu_label.c_str());
151 std::string("MainMenu/menu:") + menu.label, "menu", ImGui::GetItemID());
152 if (!open)
153 continue;
154 if (menu.is_custom) {
155 if (menu.custom_draw)
156 menu.custom_draw();
157 } else {
158 submenu_stack_.clear();
159 skip_depth_ = 0;
160 for (const auto& item : menu.items)
161 DrawMenuItem(item);
162 }
163 ImGui::EndMenu();
164 }
165}
166
168 switch (item.type) {
170 if (skip_depth_ == 0) {
171 ImGui::Separator();
172 }
173 break;
174
176 if (skip_depth_ == 0 && item.callback) {
177 item.callback();
178 }
179 break;
180
182 if (skip_depth_ == 0) {
183 std::string label =
184 item.icon.empty()
185 ? std::string(tr(item.label.c_str()))
186 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
187 ImGui::BeginDisabled();
188 ImGui::MenuItem(label.c_str(), nullptr, false, false);
189 ImGui::EndDisabled();
190 }
191 break;
192 }
193
195 // If we're already skipping, increment skip depth and continue
196 if (skip_depth_ > 0) {
197 skip_depth_++;
198 submenu_stack_.push_back(false);
199 break;
200 }
201
202 std::string label =
203 item.icon.empty()
204 ? std::string(tr(item.label.c_str()))
205 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
206
207 bool enabled = !item.enabled || item.enabled();
208 bool opened = false;
209
210 if (!enabled) {
211 // Disabled submenu - show as disabled item but don't open
212 ImGui::BeginDisabled();
213 ImGui::MenuItem(label.c_str(), nullptr, false, false);
214 ImGui::EndDisabled();
215 submenu_stack_.push_back(false);
216 skip_depth_++; // Skip contents of disabled submenu
217 } else {
218 // BeginMenu returns true if submenu is currently open/visible
219 opened = ImGui::BeginMenu(label.c_str());
220 submenu_stack_.push_back(opened);
221 if (!opened) {
222 skip_depth_++; // Skip contents of closed submenu
223 }
224 }
225 break;
226 }
227
229 // Decrement skip depth if we were skipping
230 if (skip_depth_ > 0) {
231 skip_depth_--;
232 }
233
234 // Pop the stack and call EndMenu only if submenu was opened
235 if (!submenu_stack_.empty()) {
236 bool was_opened = submenu_stack_.back();
237 submenu_stack_.pop_back();
238 if (was_opened && skip_depth_ == 0) {
239 ImGui::EndMenu();
240 }
241 }
242 break;
243
245 if (skip_depth_ > 0) {
246 break; // Skip items in closed submenus
247 }
248
249 std::string label =
250 item.icon.empty()
251 ? std::string(tr(item.label.c_str()))
252 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
253
254 bool enabled = !item.enabled || item.enabled();
255 bool checked = item.checked && item.checked();
256
257 const char* shortcut_str =
258 item.shortcut.empty() ? nullptr : item.shortcut.c_str();
259
260 const bool activated =
261 ImGui::MenuItem(label.c_str(), shortcut_str, checked, enabled);
262 if (item.label == "Save ROM" || item.label == "Quit") {
263 gui::AutoRegisterLastItem("menuitem", item.label,
264 "Stable file-menu harness action");
265 }
266 if (activated) {
267 if (item.callback) {
268 item.callback();
269 }
270 }
271 break;
272 }
273 }
274}
275
277 menus_.clear();
278 current_menu_ = nullptr;
279}
280
281} // namespace editor
282} // namespace yaze
Fluent interface for building ImGui menus with icons.
MenuBuilder & CustomItems(Callback draw_callback)
Add items drawn by a callback inside the current menu.
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 RegisterWidget(const std::string &full_path, const std::string &type, ImGuiID imgui_id, const std::string &description="", const WidgetMetadata &metadata=WidgetMetadata())
static WidgetIdRegistry & Instance()
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.