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