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
3#include <algorithm>
4#include <functional>
5#include "util/i18n/tr.h"
6
8
9#include "absl/strings/str_format.h"
12#include "app/gui/core/icons.h"
13#include "app/gui/core/input.h"
16#include "imgui/imgui.h"
17#include "imgui/imgui_internal.h"
18
19namespace yaze {
20namespace gui {
21
22// ============================================================================
23// PanelWindow Static Variables (for duplicate rendering detection)
24// ============================================================================
26std::vector<std::string> PanelWindow::panels_begun_this_frame_;
29
30// ============================================================================
31// Toolset Implementation
32// ============================================================================
33
35 // Ultra-compact toolbar with minimal padding
36 toolbar_var_guard_.emplace(std::initializer_list<StyleVarGuard::Entry>{
37 {ImGuiStyleVar_FramePadding, ImVec2(4, 2)},
38 {ImGuiStyleVar_ItemSpacing, ImVec2(3, 3)},
39 {ImGuiStyleVar_WindowPadding, ImVec2(6, 4)}});
40
41 // Don't use BeginGroup - it causes stretching. Just use direct layout.
42 in_toolbar_ = true;
43 button_count_ = 0;
45}
46
48 // End the current line
49 ImGui::NewLine();
50
51 toolbar_var_guard_.reset();
52 ImGui::Separator();
53 in_toolbar_ = false;
55}
56
58 // Compact inline mode buttons without child window to avoid scroll issues
59 // Just use a simple colored background rect
60 mode_group_color_guard_.emplace(ImGuiCol_ChildBg,
61 ImVec4(0.15f, 0.15f, 0.17f, 0.5f));
62
63 // Use a frameless child with exact button height to avoid scrolling
64 const float button_size = 28.0f; // Smaller buttons to match toolbar height
65 const float padding = 4.0f;
66 const int num_buttons = 2;
67 const float item_spacing = ImGui::GetStyle().ItemSpacing.x;
68
69 float total_width = (num_buttons * button_size) +
70 ((num_buttons - 1) * item_spacing) + (padding * 2);
71
72 ImGui::BeginChild("##ModeGroup", ImVec2(total_width, button_size + padding),
73 ImGuiChildFlags_AlwaysUseWindowPadding,
74 ImGuiWindowFlags_NoScrollbar);
75
76 // Store for button sizing
77 mode_group_button_size_ = button_size;
78}
79
80bool Toolset::ModeButton(const char* icon, bool selected, const char* tooltip) {
81 std::optional<StyleColorGuard> sel_guard;
82 if (selected) {
83 sel_guard.emplace(ImGuiCol_Button, GetAccentColor());
84 }
85
86 // Use smaller buttons that fit the toolbar height
87 float size = mode_group_button_size_ > 0 ? mode_group_button_size_ : 28.0f;
88 bool clicked = ImGui::Button(icon, ImVec2(size, size));
89
90 if (tooltip && ImGui::IsItemHovered()) {
91 ImGui::SetTooltip("%s", tooltip);
92 }
93
94 ImGui::SameLine();
96
97 return clicked;
98}
99
101 ImGui::EndChild();
103 ImGui::SameLine();
104 AddSeparator();
105}
106
108 // Use a proper separator that doesn't stretch
109 ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
110 ImGui::SameLine();
111}
112
113void Toolset::AddRomBadge(uint8_t version, std::function<void()> on_upgrade) {
115 version == 0xFF ? "Vanilla" : absl::StrFormat("v%d", version).c_str(),
116 version == 0xFF);
117
118 if (on_upgrade && (version == 0xFF || version < 3)) {
119 ImGui::SameLine(0, 2); // Tighter spacing
120 if (ImGui::SmallButton(ICON_MD_UPGRADE)) {
121 on_upgrade();
122 }
123 if (ImGui::IsItemHovered()) {
124 ImGui::SetTooltip(tr("Upgrade to ZSCustomOverworld v3"));
125 }
126 }
127
128 ImGui::SameLine();
129}
130
131bool Toolset::AddProperty(const char* icon, const char* label, uint8_t* value,
132 std::function<void()> on_change) {
133 ImGui::Text("%s", icon);
134 ImGui::SameLine();
135 ImGui::SetNextItemWidth(55);
136
137 bool changed = InputHexByte(label, value);
138 if (changed && on_change) {
139 on_change();
140 }
141
142 ImGui::SameLine();
143 return changed;
144}
145
146bool Toolset::AddProperty(const char* icon, const char* label, uint16_t* value,
147 std::function<void()> on_change) {
148 ImGui::Text("%s", icon);
149 ImGui::SameLine();
150 ImGui::SetNextItemWidth(70);
151
152 bool changed = InputHexWord(label, value);
153 if (changed && on_change) {
154 on_change();
155 }
156
157 ImGui::SameLine();
158 return changed;
159}
160
161bool Toolset::AddCombo(const char* icon, int* current,
162 const char* const items[], int count) {
163 ImGui::Text("%s", icon);
164 ImGui::SameLine(0, 2); // Reduce spacing between icon and combo
165 ImGui::SetNextItemWidth(100); // Slightly narrower for better fit
166
167 bool changed = ImGui::Combo("##combo", current, items, count);
168 ImGui::SameLine();
169
170 return changed;
171}
172
173bool Toolset::AddToggle(const char* icon, bool* state, const char* tooltip) {
174 bool result = ToggleIconButton(icon, icon, state, tooltip);
175 ImGui::SameLine();
176 return result;
177}
178
179bool Toolset::AddAction(const char* icon, const char* tooltip) {
180 bool clicked = ImGui::SmallButton(icon);
181
182 // Register for test automation
183 if (ImGui::GetItemID() != 0 && tooltip) {
184 std::string button_path = absl::StrFormat("ToolbarAction:%s", tooltip);
185 WidgetIdRegistry::Instance().RegisterWidget(button_path, "button",
186 ImGui::GetItemID(), tooltip);
187 }
188
189 if (tooltip && ImGui::IsItemHovered()) {
190 ImGui::SetTooltip("%s", tooltip);
191 }
192
193 ImGui::SameLine();
194 return clicked;
195}
196
197bool Toolset::BeginCollapsibleSection(const char* label, bool* p_open) {
198 ImGui::NewLine(); // Start on new line
199 bool is_open = ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_None);
200 if (p_open)
201 *p_open = is_open;
202 in_section_ = is_open;
203 return is_open;
204}
205
209
210void Toolset::AddV3StatusBadge(uint8_t version,
211 std::function<void()> on_settings) {
212 if (version >= 3 && version != 0xFF) {
213 StatusBadge("v3 Active", ButtonType::Success);
214 ImGui::SameLine();
215 if (ImGui::SmallButton(ICON_MD_TUNE " Settings") && on_settings) {
216 on_settings();
217 }
218 } else {
219 StatusBadge("v3 Available", ButtonType::Default);
220 ImGui::SameLine();
221 if (ImGui::SmallButton(ICON_MD_UPGRADE " Upgrade")) {
222 ImGui::OpenPopup("UpgradeROMVersion");
223 }
224 }
225 ImGui::SameLine();
226}
227
228bool Toolset::AddUsageStatsButton(const char* tooltip) {
229 bool clicked = ImGui::SmallButton(ICON_MD_ANALYTICS " Usage");
230 if (tooltip && ImGui::IsItemHovered()) {
231 ImGui::SetTooltip("%s", tooltip);
232 }
233 ImGui::SameLine();
234 return clicked;
235}
236
237// ============================================================================
238// PanelWindow Implementation
239// ============================================================================
240
241PanelWindow::PanelWindow(const char* title, const char* icon)
242 : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) {
244}
245
246PanelWindow::PanelWindow(const char* title, const char* icon, bool* p_open)
247 : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) {
248 p_open_ = p_open;
250}
251
252void PanelWindow::SetDefaultSize(float width, float height) {
253 default_size_ = ImVec2(width, height);
254 default_size_set_ = true;
255}
256
258 position_ = pos;
259}
260
261void PanelWindow::SetStableId(const std::string& stable_id) {
262 stable_id_ = stable_id;
264}
265
266void PanelWindow::AddHeaderButton(const char* icon, const char* tooltip,
267 std::function<void()> callback) {
268 header_buttons_.push_back({icon, tooltip, callback});
269}
270
271bool PanelWindow::Begin(bool* p_open) {
272 // Check visibility flag first - if provided and false, don't show the panel
273 if (p_open && !*p_open) {
274 imgui_begun_ = false;
275 return false;
276 }
277
278 // === DEBUG: Track duplicate rendering ===
279 int current_frame = ImGui::GetFrameCount();
280 if (current_frame != last_frame_count_) {
281 // New frame - reset tracking
282 last_frame_count_ = current_frame;
284 duplicate_detected_ = false;
285 duplicate_panel_name_.clear();
286 }
287
288 // Check if this panel was already begun this frame
289 for (const auto& panel_name : panels_begun_this_frame_) {
290 if (panel_name == window_name_) {
291 duplicate_detected_ = true;
293 // Log the duplicate detection
294 fprintf(stderr,
295 "[PanelWindow] DUPLICATE DETECTED: '%s' Begin() called twice in "
296 "frame %d\n",
297 window_name_.c_str(), current_frame);
298 break;
299 }
300 }
302 // === END DEBUG ===
303
304 // Handle icon-collapsed state
307 imgui_begun_ = false;
308 return false;
309 }
310
311 ImGuiWindowFlags flags = ImGuiWindowFlags_None;
312
313 // Apply headless mode
314 if (headless_) {
315 flags |= ImGuiWindowFlags_NoTitleBar;
316 flags |= ImGuiWindowFlags_NoCollapse;
317 }
318
319 // Control docking
320 if (!docking_allowed_) {
321 flags |= ImGuiWindowFlags_NoDocking;
322 }
323
324 // Prevent persisting window settings (position, size, docking state)
325 if (!save_settings_) {
326 flags |= ImGuiWindowFlags_NoSavedSettings;
327 }
328
329 // Set initial position based on position enum
330 if (first_draw_) {
331 float display_width = ImGui::GetIO().DisplaySize.x;
332 float display_height = ImGui::GetIO().DisplaySize.y;
333 ImVec2 initial_size = default_size_;
334 ImVec2 start_offset = start_offset_;
335 const bool touch_device = LayoutHelpers::IsTouchDevice();
336
337 if (touch_device) {
338 ImVec2 work_size = ImGui::GetMainViewport()
339 ? ImGui::GetMainViewport()->WorkSize
340 : ImVec2(display_width, display_height);
341 ImVec2 min_size(work_size.x * 0.65f, work_size.y * 0.55f);
342 ImVec2 max_size(work_size.x * 0.96f, work_size.y * 0.92f);
343 ImVec2 desired =
345 ? ImVec2(default_size_.x * 1.2f, default_size_.y * 1.2f)
346 : ImVec2(work_size.x * 0.9f, work_size.y * 0.82f);
347 initial_size.x = std::clamp(desired.x, min_size.x, max_size.x);
348 initial_size.y = std::clamp(desired.y, min_size.y, max_size.y);
349
350 if (!start_offset_set_) {
351 const size_t hash = std::hash<std::string>{}(window_name_);
352 const float step = 18.0f;
353 start_offset.x += step * static_cast<float>(hash % 5);
354 start_offset.y += step * static_cast<float>((hash / 5) % 5);
355 }
356 }
357
358 switch (position_) {
359 case Position::Right:
360 ImGui::SetNextWindowPos(
361 ImVec2(display_width - initial_size.x - 10.0f, 30.0f) +
362 start_offset,
363 ImGuiCond_FirstUseEver);
364 break;
365 case Position::Left:
366 ImGui::SetNextWindowPos(ImVec2(10.0f, 30.0f) + start_offset,
367 ImGuiCond_FirstUseEver);
368 break;
369 case Position::Bottom:
370 ImGui::SetNextWindowPos(
371 ImVec2(10.0f, display_height - initial_size.y - 10.0f) +
372 start_offset,
373 ImGuiCond_FirstUseEver);
374 break;
375 case Position::Top:
376 ImGui::SetNextWindowPos(ImVec2(10.0f, 30.0f) + start_offset,
377 ImGuiCond_FirstUseEver);
378 break;
379 case Position::Center:
380 ImGui::SetNextWindowPos(
381 ImVec2(display_width * 0.5f - initial_size.x * 0.5f,
382 display_height * 0.5f - initial_size.y * 0.5f) +
383 start_offset,
384 ImGuiCond_FirstUseEver);
385 break;
387 case Position::Free:
388 ImGui::SetNextWindowPos(
389 ImVec2(display_width * 0.5f - initial_size.x * 0.5f,
390 display_height * 0.3f) +
391 start_offset,
392 ImGuiCond_FirstUseEver);
393 break;
394 }
395
396 ImGui::SetNextWindowSize(initial_size, ImGuiCond_FirstUseEver);
397 first_draw_ = false;
398 }
399
400 // Modern panel styling
401 panel_var_guard_.emplace(std::initializer_list<StyleVarGuard::Entry>{
402 {ImGuiStyleVar_WindowRounding, 8.0f},
403 {ImGuiStyleVar_WindowPadding, ImVec2(10, 10)}});
404 panel_color_guard_.emplace(std::initializer_list<StyleColorGuard::Entry>{
405 {ImGuiCol_TitleBg, GetThemeColor(ImGuiCol_TitleBg)},
406 {ImGuiCol_TitleBgActive, GetAccentColor()}});
407
408 // Use p_open parameter if provided, otherwise use stored p_open_
409 bool* actual_p_open = p_open ? p_open : p_open_;
410
411 // If closable is false, don't pass p_open (removes X button)
412 bool visible = ImGui::Begin(window_name_.c_str(),
413 closable_ ? actual_p_open : nullptr, flags);
414
415 // Mark that ImGui::Begin() was called - End() must always be called now
416 imgui_begun_ = true;
417
418 // Draw custom header buttons if visible
419 if (visible) {
420 ImGuiWindow* window = ImGui::GetCurrentWindow();
421 if (window && !window->DockNode && !window->Collapsed) {
422 const ImGuiViewport* viewport =
423 window->Viewport ? window->Viewport : ImGui::GetMainViewport();
424 const bool dragging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
425 ImGui::IsWindowHovered(
426 ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
427 if (!dragging && viewport) {
428 constexpr float kMinVisible = 32.0f;
429 const auto clamp = LayoutHelpers::ClampWindowToRect(
430 window->Pos, window->Size, viewport->WorkPos, viewport->WorkSize,
431 kMinVisible);
432 if (clamp.clamped) {
433 ImGui::SetWindowPos(clamp.pos, ImGuiCond_Always);
434 }
435 }
436 }
438 }
439
440 // Register panel window for test automation
441 if (ImGui::GetCurrentWindow() && ImGui::GetCurrentWindow()->ID != 0) {
442 std::string panel_path = absl::StrFormat("PanelWindow:%s", title_.c_str());
444 panel_path, "window", ImGui::GetCurrentWindow()->ID,
445 absl::StrFormat("Editor panel: %s", title_.c_str()));
446 }
447
448 return visible;
449}
450
452 // Only call ImGui::End() and pop styles if ImGui::Begin() was called
453 if (imgui_begun_) {
454 // Check if window was focused this frame
455 focused_ = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
456
457 ImGui::End();
458 panel_color_guard_.reset();
459 panel_var_guard_.reset();
460 imgui_begun_ = false;
461 }
462}
463
465 // Set window focus using ImGui's focus system
466 ImGui::SetWindowFocus(window_name_.c_str());
467 focused_ = true;
468}
469
471 // Draw a small floating button with the icon
472 ImGui::SetNextWindowPos(saved_icon_pos_, ImGuiCond_Always);
473 ImGui::SetNextWindowSize(ImVec2(50, 50));
474
475 ImGuiWindowFlags flags =
476 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
477 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse;
478
479 std::string icon_window_name = window_name_ + "##IconCollapsed";
480
481 if (ImGui::Begin(icon_window_name.c_str(), nullptr, flags)) {
482 // Draw icon button
483 if (ImGui::Button(icon_.c_str(), ImVec2(40, 40))) {
484 collapsed_to_icon_ = false; // Expand back to full window
485 }
486
487 if (ImGui::IsItemHovered()) {
488 ImGui::SetTooltip(tr("Expand %s"), title_.c_str());
489 }
490
491 // Allow dragging the icon
492 if (ImGui::IsWindowHovered() &&
493 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
494 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
495 saved_icon_pos_.x += mouse_delta.x;
496 saved_icon_pos_.y += mouse_delta.y;
497 }
498 }
499 ImGui::End();
500}
501
503 // Note: Drawing buttons in docked window title bars is problematic with ImGui's
504 // docking system. The pin functionality is better managed through the Activity Bar
505 // sidebar where each panel entry can have a pin toggle. This avoids layout issues
506 // with docked windows and provides a cleaner UI.
507 //
508 // For now, pin state is tracked internally but the button is not rendered.
509 // Right-click context menu in Activity Bar can be used for pinning.
510
511 // Skip drawing header buttons in content area - they interfere with docking
512 // and take up vertical space. The pin state is still tracked and used by
513 // WorkspaceWindowManager for category filtering.
514}
515
517 std::string label = icon_.empty() ? title_ : icon_ + " " + title_;
518 if (!stable_id_.empty()) {
519 window_name_ = label + "##" + stable_id_;
520 } else {
521 window_name_ = label;
522 }
523}
524
525// ============================================================================
526// EditorLayout Implementation
527// ============================================================================
528
530 toolbar_.Begin();
531 in_layout_ = true;
532}
533
535 toolbar_.End();
536 in_layout_ = false;
537}
538
540 // Main canvas takes remaining space
541 ImGui::BeginChild("##MainCanvas", ImVec2(0, 0), false);
542}
543
545 ImGui::EndChild();
546}
547
549 panels_.push_back(panel);
550}
551
552} // namespace gui
553} // namespace yaze
void RegisterPanel(PanelWindow *panel)
std::vector< PanelWindow * > panels_
static WindowClampResult ClampWindowToRect(const ImVec2 &pos, const ImVec2 &size, const ImVec2 &rect_pos, const ImVec2 &rect_size, float min_visible=32.0f)
Draggable, dockable panel for editor sub-windows.
static std::string duplicate_panel_name_
std::optional< StyleVarGuard > panel_var_guard_
PanelWindow(const char *title, const char *icon=nullptr)
std::vector< HeaderButton > header_buttons_
void SetStableId(const std::string &stable_id)
std::optional< StyleColorGuard > panel_color_guard_
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)
std::optional< StyleColorGuard > mode_group_color_guard_
std::optional< StyleVarGuard > toolbar_var_guard_
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:355
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:376
Color GetThemeColor(const std::string &color_name)
void StatusBadge(const char *text, ButtonType type)
ImVec4 GetAccentColor()
Definition ui_helpers.cc:69