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 const bool open = ImGui::BeginMenu(menu_label.c_str());
140 std::string("MainMenu/menu:") + menu.label, "menu", ImGui::GetItemID());
141 if (!open)
142 continue;
143 if (menu.is_custom) {
144 if (menu.custom_draw)
145 menu.custom_draw();
146 } else {
147 submenu_stack_.clear();
148 skip_depth_ = 0;
149 for (const auto& item : menu.items)
150 DrawMenuItem(item);
151 }
152 ImGui::EndMenu();
153 }
154}
155
157 switch (item.type) {
159 if (skip_depth_ == 0) {
160 ImGui::Separator();
161 }
162 break;
163
165 if (skip_depth_ == 0) {
166 std::string label =
167 item.icon.empty()
168 ? std::string(tr(item.label.c_str()))
169 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
170 ImGui::BeginDisabled();
171 ImGui::MenuItem(label.c_str(), nullptr, false, false);
172 ImGui::EndDisabled();
173 }
174 break;
175 }
176
178 // If we're already skipping, increment skip depth and continue
179 if (skip_depth_ > 0) {
180 skip_depth_++;
181 submenu_stack_.push_back(false);
182 break;
183 }
184
185 std::string label =
186 item.icon.empty()
187 ? std::string(tr(item.label.c_str()))
188 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
189
190 bool enabled = !item.enabled || item.enabled();
191 bool opened = false;
192
193 if (!enabled) {
194 // Disabled submenu - show as disabled item but don't open
195 ImGui::BeginDisabled();
196 ImGui::MenuItem(label.c_str(), nullptr, false, false);
197 ImGui::EndDisabled();
198 submenu_stack_.push_back(false);
199 skip_depth_++; // Skip contents of disabled submenu
200 } else {
201 // BeginMenu returns true if submenu is currently open/visible
202 opened = ImGui::BeginMenu(label.c_str());
203 submenu_stack_.push_back(opened);
204 if (!opened) {
205 skip_depth_++; // Skip contents of closed submenu
206 }
207 }
208 break;
209 }
210
212 // Decrement skip depth if we were skipping
213 if (skip_depth_ > 0) {
214 skip_depth_--;
215 }
216
217 // Pop the stack and call EndMenu only if submenu was opened
218 if (!submenu_stack_.empty()) {
219 bool was_opened = submenu_stack_.back();
220 submenu_stack_.pop_back();
221 if (was_opened && skip_depth_ == 0) {
222 ImGui::EndMenu();
223 }
224 }
225 break;
226
228 if (skip_depth_ > 0) {
229 break; // Skip items in closed submenus
230 }
231
232 std::string label =
233 item.icon.empty()
234 ? std::string(tr(item.label.c_str()))
235 : absl::StrCat(item.icon, " ", tr(item.label.c_str()));
236
237 bool enabled = !item.enabled || item.enabled();
238 bool checked = item.checked && item.checked();
239
240 const char* shortcut_str =
241 item.shortcut.empty() ? nullptr : item.shortcut.c_str();
242
243 const bool activated =
244 ImGui::MenuItem(label.c_str(), shortcut_str, checked, enabled);
245 if (item.label == "Save ROM" || item.label == "Quit") {
246 gui::AutoRegisterLastItem("menuitem", item.label,
247 "Stable file-menu harness action");
248 }
249 if (activated) {
250 if (item.callback) {
251 item.callback();
252 }
253 }
254 break;
255 }
256 }
257}
258
260 menus_.clear();
261 current_menu_ = nullptr;
262}
263
264} // namespace editor
265} // 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 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.