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