yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
editor_layout.cc
Go to the documentation of this file.
1#define IMGUI_DEFINE_MATH_OPERATORS
2
4
5#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
12#include "imgui/imgui_internal.h"
13
14namespace yaze {
15namespace gui {
16
17// ============================================================================
18// PanelWindow Static Variables (for duplicate rendering detection)
19// ============================================================================
21std::vector<std::string> PanelWindow::panels_begun_this_frame_;
24
25// ============================================================================
26// Toolset Implementation
27// ============================================================================
28
30 // Ultra-compact toolbar with minimal padding
31 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 2));
32 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(3, 3));
33 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 4));
34
35 // Don't use BeginGroup - it causes stretching. Just use direct layout.
36 in_toolbar_ = true;
37 button_count_ = 0;
39}
40
42 // End the current line
43 ImGui::NewLine();
44
45 ImGui::PopStyleVar(3);
46 ImGui::Separator();
47 in_toolbar_ = false;
49}
50
52 // Compact inline mode buttons without child window to avoid scroll issues
53 // Just use a simple colored background rect
54 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.17f, 0.5f));
55
56 // Use a frameless child with exact button height to avoid scrolling
57 const float button_size = 28.0f; // Smaller buttons to match toolbar height
58 const float padding = 4.0f;
59 const int num_buttons = 2;
60 const float item_spacing = ImGui::GetStyle().ItemSpacing.x;
61
62 float total_width = (num_buttons * button_size) +
63 ((num_buttons - 1) * item_spacing) + (padding * 2);
64
65 ImGui::BeginChild("##ModeGroup", ImVec2(total_width, button_size + padding),
66 ImGuiChildFlags_AlwaysUseWindowPadding,
67 ImGuiWindowFlags_NoScrollbar);
68
69 // Store for button sizing
70 mode_group_button_size_ = button_size;
71}
72
73bool Toolset::ModeButton(const char* icon, bool selected, const char* tooltip) {
74 if (selected) {
75 ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor());
76 }
77
78 // Use smaller buttons that fit the toolbar height
79 float size = mode_group_button_size_ > 0 ? mode_group_button_size_ : 28.0f;
80 bool clicked = ImGui::Button(icon, ImVec2(size, size));
81
82 if (selected) {
83 ImGui::PopStyleColor();
84 }
85
86 if (tooltip && ImGui::IsItemHovered()) {
87 ImGui::SetTooltip("%s", tooltip);
88 }
89
90 ImGui::SameLine();
92
93 return clicked;
94}
95
97 ImGui::EndChild();
98 ImGui::PopStyleColor();
99 ImGui::SameLine();
100 AddSeparator();
101}
102
104 // Use a proper separator that doesn't stretch
105 ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
106 ImGui::SameLine();
107}
108
109void Toolset::AddRomBadge(uint8_t version, std::function<void()> on_upgrade) {
111 version == 0xFF ? "Vanilla" : absl::StrFormat("v%d", version).c_str(),
112 version == 0xFF);
113
114 if (on_upgrade && (version == 0xFF || version < 3)) {
115 ImGui::SameLine(0, 2); // Tighter spacing
116 if (ImGui::SmallButton(ICON_MD_UPGRADE)) {
117 on_upgrade();
118 }
119 if (ImGui::IsItemHovered()) {
120 ImGui::SetTooltip("Upgrade to ZSCustomOverworld v3");
121 }
122 }
123
124 ImGui::SameLine();
125}
126
127bool Toolset::AddProperty(const char* icon, const char* label, uint8_t* value,
128 std::function<void()> on_change) {
129 ImGui::Text("%s", icon);
130 ImGui::SameLine();
131 ImGui::SetNextItemWidth(55);
132
133 bool changed = InputHexByte(label, value);
134 if (changed && on_change) {
135 on_change();
136 }
137
138 ImGui::SameLine();
139 return changed;
140}
141
142bool Toolset::AddProperty(const char* icon, const char* label, uint16_t* value,
143 std::function<void()> on_change) {
144 ImGui::Text("%s", icon);
145 ImGui::SameLine();
146 ImGui::SetNextItemWidth(70);
147
148 bool changed = InputHexWord(label, value);
149 if (changed && on_change) {
150 on_change();
151 }
152
153 ImGui::SameLine();
154 return changed;
155}
156
157bool Toolset::AddCombo(const char* icon, int* current,
158 const char* const items[], int count) {
159 ImGui::Text("%s", icon);
160 ImGui::SameLine(0, 2); // Reduce spacing between icon and combo
161 ImGui::SetNextItemWidth(100); // Slightly narrower for better fit
162
163 bool changed = ImGui::Combo("##combo", current, items, count);
164 ImGui::SameLine();
165
166 return changed;
167}
168
169bool Toolset::AddToggle(const char* icon, bool* state, const char* tooltip) {
170 bool result = ToggleIconButton(icon, icon, state, tooltip);
171 ImGui::SameLine();
172 return result;
173}
174
175bool Toolset::AddAction(const char* icon, const char* tooltip) {
176 bool clicked = ImGui::SmallButton(icon);
177
178 // Register for test automation
179 if (ImGui::GetItemID() != 0 && tooltip) {
180 std::string button_path = absl::StrFormat("ToolbarAction:%s", tooltip);
181 WidgetIdRegistry::Instance().RegisterWidget(button_path, "button",
182 ImGui::GetItemID(), tooltip);
183 }
184
185 if (tooltip && ImGui::IsItemHovered()) {
186 ImGui::SetTooltip("%s", tooltip);
187 }
188
189 ImGui::SameLine();
190 return clicked;
191}
192
193bool Toolset::BeginCollapsibleSection(const char* label, bool* p_open) {
194 ImGui::NewLine(); // Start on new line
195 bool is_open = ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_None);
196 if (p_open)
197 *p_open = is_open;
198 in_section_ = is_open;
199 return is_open;
200}
201
205
206void Toolset::AddV3StatusBadge(uint8_t version,
207 std::function<void()> on_settings) {
208 if (version >= 3 && version != 0xFF) {
209 StatusBadge("v3 Active", ButtonType::Success);
210 ImGui::SameLine();
211 if (ImGui::SmallButton(ICON_MD_TUNE " Settings") && on_settings) {
212 on_settings();
213 }
214 } else {
215 StatusBadge("v3 Available", ButtonType::Default);
216 ImGui::SameLine();
217 if (ImGui::SmallButton(ICON_MD_UPGRADE " Upgrade")) {
218 ImGui::OpenPopup("UpgradeROMVersion");
219 }
220 }
221 ImGui::SameLine();
222}
223
224bool Toolset::AddUsageStatsButton(const char* tooltip) {
225 bool clicked = ImGui::SmallButton(ICON_MD_ANALYTICS " Usage");
226 if (tooltip && ImGui::IsItemHovered()) {
227 ImGui::SetTooltip("%s", tooltip);
228 }
229 ImGui::SameLine();
230 return clicked;
231}
232
233// ============================================================================
234// PanelWindow Implementation
235// ============================================================================
236
237PanelWindow::PanelWindow(const char* title, const char* icon)
238 : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) {
239 window_name_ = icon_.empty() ? title_ : icon_ + " " + title_;
240}
241
242PanelWindow::PanelWindow(const char* title, const char* icon, bool* p_open)
243 : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) {
244 p_open_ = p_open;
245 window_name_ = icon_.empty() ? title_ : icon_ + " " + title_;
246}
247
248void PanelWindow::SetDefaultSize(float width, float height) {
249 default_size_ = ImVec2(width, height);
250}
251
253 position_ = pos;
254}
255
256void PanelWindow::AddHeaderButton(const char* icon, const char* tooltip, std::function<void()> callback) {
257 header_buttons_.push_back({icon, tooltip, callback});
258}
259
260bool PanelWindow::Begin(bool* p_open) {
261 // Check visibility flag first - if provided and false, don't show the panel
262 if (p_open && !*p_open) {
263 imgui_begun_ = false;
264 return false;
265 }
266
267 // === DEBUG: Track duplicate rendering ===
268 int current_frame = ImGui::GetFrameCount();
269 if (current_frame != last_frame_count_) {
270 // New frame - reset tracking
271 last_frame_count_ = current_frame;
273 duplicate_detected_ = false;
274 duplicate_panel_name_.clear();
275 }
276
277 // Check if this panel was already begun this frame
278 for (const auto& panel_name : panels_begun_this_frame_) {
279 if (panel_name == window_name_) {
280 duplicate_detected_ = true;
282 // Log the duplicate detection
283 fprintf(stderr, "[PanelWindow] DUPLICATE DETECTED: '%s' Begin() called twice in frame %d\n",
284 window_name_.c_str(), current_frame);
285 break;
286 }
287 }
289 // === END DEBUG ===
290
291 // Handle icon-collapsed state
294 imgui_begun_ = false;
295 return false;
296 }
297
298 ImGuiWindowFlags flags = ImGuiWindowFlags_None;
299
300 // Apply headless mode
301 if (headless_) {
302 flags |= ImGuiWindowFlags_NoTitleBar;
303 flags |= ImGuiWindowFlags_NoCollapse;
304 }
305
306 // Control docking
307 if (!docking_allowed_) {
308 flags |= ImGuiWindowFlags_NoDocking;
309 }
310
311 // Prevent persisting window settings (position, size, docking state)
312 if (!save_settings_) {
313 flags |= ImGuiWindowFlags_NoSavedSettings;
314 }
315
316 // Set initial position based on position enum
317 if (first_draw_) {
318 float display_width = ImGui::GetIO().DisplaySize.x;
319 float display_height = ImGui::GetIO().DisplaySize.y;
320
321 switch (position_) {
322 case Position::Right:
323 ImGui::SetNextWindowPos(
324 ImVec2(display_width - default_size_.x - 10, 30),
325 ImGuiCond_FirstUseEver);
326 break;
327 case Position::Left:
328 ImGui::SetNextWindowPos(ImVec2(10, 30), ImGuiCond_FirstUseEver);
329 break;
330 case Position::Bottom:
331 ImGui::SetNextWindowPos(
332 ImVec2(10, display_height - default_size_.y - 10),
333 ImGuiCond_FirstUseEver);
334 break;
335 case Position::Top:
336 ImGui::SetNextWindowPos(ImVec2(10, 30), ImGuiCond_FirstUseEver);
337 break;
339 case Position::Free:
340 ImGui::SetNextWindowPos(
341 ImVec2(display_width * 0.5f - default_size_.x * 0.5f,
342 display_height * 0.3f),
343 ImGuiCond_FirstUseEver);
344 break;
345 }
346
347 ImGui::SetNextWindowSize(default_size_, ImGuiCond_FirstUseEver);
348 first_draw_ = false;
349 }
350
351 // Create window title with icon
352 std::string window_title = icon_.empty() ? title_ : icon_ + " " + title_;
353
354 // Modern panel styling
355 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
356 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10));
357 ImGui::PushStyleColor(ImGuiCol_TitleBg, GetThemeColor(ImGuiCol_TitleBg));
358 ImGui::PushStyleColor(ImGuiCol_TitleBgActive, GetAccentColor());
359
360 // Use p_open parameter if provided, otherwise use stored p_open_
361 bool* actual_p_open = p_open ? p_open : p_open_;
362
363 // If closable is false, don't pass p_open (removes X button)
364 bool visible = ImGui::Begin(window_title.c_str(),
365 closable_ ? actual_p_open : nullptr, flags);
366
367 // Mark that ImGui::Begin() was called - End() must always be called now
368 imgui_begun_ = true;
369
370 // Draw custom header buttons if visible
371 if (visible) {
373 }
374
375 // Register panel window for test automation
376 if (ImGui::GetCurrentWindow() && ImGui::GetCurrentWindow()->ID != 0) {
377 std::string panel_path = absl::StrFormat("PanelWindow:%s", title_.c_str());
379 panel_path, "window", ImGui::GetCurrentWindow()->ID,
380 absl::StrFormat("Editor panel: %s", title_.c_str()));
381 }
382
383 return visible;
384}
385
387 // Only call ImGui::End() and pop styles if ImGui::Begin() was called
388 if (imgui_begun_) {
389 // Check if window was focused this frame
390 focused_ = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
391
392 ImGui::End();
393 ImGui::PopStyleColor(2);
394 ImGui::PopStyleVar(2);
395 imgui_begun_ = false;
396 }
397}
398
400 // Set window focus using ImGui's focus system
401 ImGui::SetWindowFocus(window_name_.c_str());
402 focused_ = true;
403}
404
406 // Draw a small floating button with the icon
407 ImGui::SetNextWindowPos(saved_icon_pos_, ImGuiCond_Always);
408 ImGui::SetNextWindowSize(ImVec2(50, 50));
409
410 ImGuiWindowFlags flags =
411 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
412 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse;
413
414 std::string icon_window_name = window_name_ + "##IconCollapsed";
415
416 if (ImGui::Begin(icon_window_name.c_str(), nullptr, flags)) {
417 // Draw icon button
418 if (ImGui::Button(icon_.c_str(), ImVec2(40, 40))) {
419 collapsed_to_icon_ = false; // Expand back to full window
420 }
421
422 if (ImGui::IsItemHovered()) {
423 ImGui::SetTooltip("Expand %s", title_.c_str());
424 }
425
426 // Allow dragging the icon
427 if (ImGui::IsWindowHovered() &&
428 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
429 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
430 saved_icon_pos_.x += mouse_delta.x;
431 saved_icon_pos_.y += mouse_delta.y;
432 }
433 }
434 ImGui::End();
435}
436
438 // Note: Drawing buttons in docked window title bars is problematic with ImGui's
439 // docking system. The pin functionality is better managed through the Activity Bar
440 // sidebar where each panel entry can have a pin toggle. This avoids layout issues
441 // with docked windows and provides a cleaner UI.
442 //
443 // For now, pin state is tracked internally but the button is not rendered.
444 // Right-click context menu in Activity Bar can be used for pinning.
445
446 // Skip drawing header buttons in content area - they interfere with docking
447 // and take up vertical space. The pin state is still tracked and used by
448 // PanelManager for category filtering.
449}
450
451// ============================================================================
452// EditorLayout Implementation
453// ============================================================================
454
456 toolbar_.Begin();
457 in_layout_ = true;
458}
459
461 toolbar_.End();
462 in_layout_ = false;
463}
464
466 // Main canvas takes remaining space
467 ImGui::BeginChild("##MainCanvas", ImVec2(0, 0), false);
468}
469
471 ImGui::EndChild();
472}
473
475 panels_.push_back(panel);
476}
477
478} // namespace gui
479} // namespace yaze
void RegisterPanel(PanelWindow *panel)
std::vector< PanelWindow * > panels_
Draggable, dockable panel for editor sub-windows.
static std::string duplicate_panel_name_
PanelWindow(const char *title, const char *icon=nullptr)
std::vector< HeaderButton > header_buttons_
void SetPosition(Position pos)
bool Begin(bool *p_open=nullptr)
void AddHeaderButton(const char *icon, const char *tooltip, std::function< void()> callback)
static bool duplicate_detected_
static std::vector< std::string > panels_begun_this_frame_
void SetDefaultSize(float width, float height)
bool ModeButton(const char *icon, bool selected, const char *tooltip)
bool AddUsageStatsButton(const char *tooltip)
bool BeginCollapsibleSection(const char *label, bool *p_open)
bool AddProperty(const char *icon, const char *label, uint8_t *value, std::function< void()> on_change=nullptr)
bool AddAction(const char *icon, const char *tooltip)
bool AddCombo(const char *icon, int *current, const char *const items[], int count)
void AddRomBadge(uint8_t version, std::function< void()> on_upgrade=nullptr)
void AddV3StatusBadge(uint8_t version, std::function< void()> on_settings)
bool AddToggle(const char *icon, bool *state, const char *tooltip)
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()
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_ANALYTICS
Definition icons.h:154
bool ToggleIconButton(const char *icon_on, const char *icon_off, bool *state, const char *tooltip)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:344
void RomVersionBadge(const char *version, bool is_vanilla)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:370
Color GetThemeColor(const std::string &color_name)
void StatusBadge(const char *text, ButtonType type)
ImVec4 GetAccentColor()
Definition ui_helpers.cc:41