yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
layout_manager.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <string>
6
10#include "imgui/imgui.h"
11#include "imgui/imgui_internal.h"
12
13#if defined(__APPLE__)
14#include <TargetConditionals.h>
15#endif
16#include "util/json.h"
17#include "util/log.h"
18#include "util/platform_paths.h"
19
20namespace yaze {
21namespace editor {
22
23namespace {
24
25// Helper function to show default cards from LayoutPresets
27 if (!registry) return;
28
29 auto default_panels = LayoutPresets::GetDefaultPanels(type);
30 for (const auto& panel_id : default_panels) {
31 registry->ShowPanel(panel_id);
32 }
33
34 LOG_INFO("LayoutManager", "Showing %zu default panels for editor type %d",
35 default_panels.size(), static_cast<int>(type));
36}
37
38yaze::Json BoolMapToJson(const std::unordered_map<std::string, bool>& map) {
40 for (const auto& [key, value] : map) {
41 obj[key] = value;
42 }
43 return obj;
44}
45
46void JsonToBoolMap(const yaze::Json& obj,
47 std::unordered_map<std::string, bool>* map) {
48 if (!map || !obj.is_object()) {
49 return;
50 }
51 map->clear();
52 for (const auto& [key, value] : obj.items()) {
53 if (value.is_boolean()) {
54 (*map)[key] = value.get<bool>();
55 }
56 }
57}
58
59std::filesystem::path GetLayoutsFilePath(LayoutScope scope,
60 const std::string& project_key) {
61 auto layouts_dir = util::PlatformPaths::GetAppDataSubdirectory("layouts");
62 if (!layouts_dir.ok()) {
63 return {};
64 }
65
66 if (scope == LayoutScope::kProject && !project_key.empty()) {
67 std::filesystem::path projects_dir = *layouts_dir / "projects";
69 return projects_dir / (project_key + ".json");
70 }
71
72 if (scope == LayoutScope::kProject) {
73 return {};
74 }
75
76 return *layouts_dir / "layouts.json";
77}
78
79bool TryGetNamedPreset(const std::string& preset_name,
80 PanelLayoutPreset* preset_out) {
81 if (!preset_out) {
82 return false;
83 }
84
85 if (preset_name == "Minimal") {
86 *preset_out = LayoutPresets::GetMinimalPreset();
87 return true;
88 }
89 if (preset_name == "Developer") {
91 return true;
92 }
93 if (preset_name == "Designer") {
95 return true;
96 }
97 if (preset_name == "Modder") {
98 *preset_out = LayoutPresets::GetModderPreset();
99 return true;
100 }
101 if (preset_name == "Overworld Expert") {
103 return true;
104 }
105 if (preset_name == "Dungeon Expert") {
107 return true;
108 }
109 if (preset_name == "Testing") {
111 return true;
112 }
113 if (preset_name == "Audio") {
115 return true;
116 }
117
118 return false;
119}
120
121std::string ResolveProfilePresetName(const std::string& profile_id,
122 EditorType editor_type) {
123 if (profile_id == "mapping") {
124 if (editor_type == EditorType::kDungeon) {
125 return "Dungeon Expert";
126 }
127 return "Overworld Expert";
128 }
129 if (profile_id == "code") {
130 return "Minimal";
131 }
132 if (profile_id == "debug") {
133 return "Developer";
134 }
135 if (profile_id == "chat") {
136 return "Modder";
137 }
138 return "";
139}
140
141} // namespace
142
144 ImGuiID dockspace_id) {
145 // Don't reinitialize if already set up
146 if (IsLayoutInitialized(type)) {
147 LOG_INFO("LayoutManager",
148 "Layout for editor type %d already initialized, skipping",
149 static_cast<int>(type));
150 return;
151 }
152
153 // Store dockspace ID and current editor type for potential rebuilds
154 last_dockspace_id_ = dockspace_id;
156
157 LOG_INFO("LayoutManager", "Initializing layout for editor type %d",
158 static_cast<int>(type));
159
160 // Clear existing layout for this dockspace
161 ImGui::DockBuilderRemoveNode(dockspace_id);
162 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
163
164 ImVec2 dockspace_size = ImVec2(1280, 720); // Safe default
165 if (auto* viewport = ImGui::GetMainViewport()) {
166 dockspace_size = viewport->WorkSize;
167 }
168
169 const ImVec2 last_size = gui::DockSpaceRenderer::GetLastDockspaceSize();
170 if (last_size.x > 0.0f && last_size.y > 0.0f) {
171 dockspace_size = last_size;
172 }
173 ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size);
174
175 // Build layout based on editor type using generic builder
176 BuildLayoutFromPreset(type, dockspace_id);
177
178 // Show default cards from LayoutPresets (single source of truth)
179 ShowDefaultPanelsForEditor(panel_manager_, type);
180
181 // Finalize the layout
182 ImGui::DockBuilderFinish(dockspace_id);
183
184 // Mark as initialized
186}
187
188void LayoutManager::RebuildLayout(EditorType type, ImGuiID dockspace_id) {
189 // Validate dockspace exists
190 ImGuiDockNode* node = ImGui::DockBuilderGetNode(dockspace_id);
191 if (!node) {
192 LOG_ERROR("LayoutManager",
193 "Cannot rebuild layout: dockspace ID %u not found", dockspace_id);
194 return;
195 }
196
197 LOG_INFO("LayoutManager", "Forcing rebuild of layout for editor type %d",
198 static_cast<int>(type));
199
200 // Store dockspace ID and current editor type
201 last_dockspace_id_ = dockspace_id;
203
204 // Clear the layout initialization flag to force rebuild
205 layouts_initialized_[type] = false;
206
207 // Clear existing layout for this dockspace
208 ImGui::DockBuilderRemoveNode(dockspace_id);
209 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
210 ImVec2 dockspace_size = ImGui::GetMainViewport()->WorkSize;
211 const ImVec2 last_size = gui::DockSpaceRenderer::GetLastDockspaceSize();
212 if (last_size.x > 0.0f && last_size.y > 0.0f) {
213 dockspace_size = last_size;
214 }
215 ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size);
216
217 // Build layout based on editor type using generic builder
218 BuildLayoutFromPreset(type, dockspace_id);
219
220 // Show default cards from LayoutPresets (single source of truth)
221 ShowDefaultPanelsForEditor(panel_manager_, type);
222
223 // Finalize the layout
224 ImGui::DockBuilderFinish(dockspace_id);
225
226 // Mark as initialized
228
229 LOG_INFO("LayoutManager", "Layout rebuild complete for editor type %d",
230 static_cast<int>(type));
231}
232
233namespace {
234
236 float left = 0.17f;
237 float right = 0.24f;
238 float bottom = 0.22f;
239 float top = 0.12f;
240 float vertical_split = 0.52f;
241
242 // Per-editor type configuration
244 DockSplitConfig cfg;
245 switch (type) {
246 case EditorType::kDungeon:
247 // Dungeon: reserve more right-side space for object/property workflows.
248 cfg.left = 0.16f;
249 cfg.right = 0.26f;
250 cfg.bottom = 0.20f;
251 cfg.vertical_split = 0.50f;
252 break;
253 case EditorType::kOverworld:
254 cfg.left = 0.22f;
255 cfg.right = 0.26f;
256 cfg.bottom = 0.23f;
257 cfg.vertical_split = 0.42f;
258 break;
259 case EditorType::kGraphics:
260 cfg.left = 0.16f;
261 cfg.right = 0.24f;
262 cfg.bottom = 0.20f;
263 break;
264 case EditorType::kPalette:
265 cfg.left = 0.16f;
266 cfg.right = 0.22f;
267 cfg.bottom = 0.20f;
268 break;
269 case EditorType::kSprite:
270 cfg.left = 0.18f;
271 cfg.right = 0.24f;
272 cfg.bottom = 0.20f;
273 break;
274 case EditorType::kScreen:
275 cfg.left = 0.16f;
276 cfg.right = 0.22f;
277 cfg.bottom = 0.20f;
278 break;
279 case EditorType::kMessage:
280 cfg.left = 0.20f;
281 cfg.right = 0.26f;
282 cfg.bottom = 0.18f;
283 break;
284 case EditorType::kAssembly:
285 cfg.left = 0.24f;
286 cfg.right = 0.16f;
287 cfg.bottom = 0.20f;
288 break;
289 case EditorType::kEmulator:
290 cfg.left = 0.14f;
291 cfg.right = 0.28f;
292 cfg.bottom = 0.22f;
293 break;
294 case EditorType::kAgent:
295 cfg.left = 0.16f;
296 cfg.right = 0.30f;
297 cfg.bottom = 0.24f;
298 break;
299 default:
300 // Use defaults
301 break;
302 }
303 return cfg;
304 }
305};
306
308 ImGuiID center = 0;
309 ImGuiID left = 0;
310 ImGuiID right = 0;
311 ImGuiID bottom = 0;
312 ImGuiID top = 0;
313 ImGuiID left_top = 0;
314 ImGuiID left_bottom = 0;
315 ImGuiID right_top = 0;
316 ImGuiID right_bottom = 0;
317};
318
320 bool left = false;
321 bool right = false;
322 bool bottom = false;
323 bool top = false;
324 bool left_top = false;
325 bool left_bottom = false;
326 bool right_top = false;
327 bool right_bottom = false;
328};
329
331 DockSplitNeeds needs{};
332 for (const auto& [_, pos] : preset.panel_positions) {
333 switch (pos) {
335 needs.left = true;
336 break;
338 needs.right = true;
339 break;
341 needs.bottom = true;
342 break;
344 needs.top = true;
345 break;
347 needs.left = true;
348 needs.left_top = true;
349 break;
351 needs.left = true;
352 needs.left_bottom = true;
353 break;
355 needs.right = true;
356 needs.right_top = true;
357 break;
359 needs.right = true;
360 needs.right_bottom = true;
361 break;
363 default:
364 break;
365 }
366 }
367 return needs;
368}
369
370DockNodeIds BuildDockTree(ImGuiID dockspace_id, const DockSplitNeeds& needs,
371 const DockSplitConfig& cfg) {
372 DockNodeIds ids{};
373 ids.center = dockspace_id;
374
375 // Split major regions
376 if (needs.left) {
377 ids.left = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Left, cfg.left,
378 nullptr, &ids.center);
379 }
380 if (needs.right) {
381 ids.right = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Right,
382 cfg.right, nullptr, &ids.center);
383 }
384 if (needs.bottom) {
385 ids.bottom = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Down,
386 cfg.bottom, nullptr, &ids.center);
387 }
388 if (needs.top) {
389 ids.top = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Up, cfg.top,
390 nullptr, &ids.center);
391 }
392
393 // Sub-split Left region
394 if (ids.left && (needs.left_top || needs.left_bottom)) {
395 // If we only need one, the split still happens but we use the result accordingly
396 ids.left_bottom = ImGui::DockBuilderSplitNode(
397 ids.left, ImGuiDir_Down, cfg.vertical_split, nullptr, &ids.left_top);
398
399 // If one isn't needed, we technically don't have to split, but for a stable tree,
400 // we do it and get_dock_id will map to the leaf.
401 }
402
403 // Sub-split Right region
404 if (ids.right && (needs.right_top || needs.right_bottom)) {
405 ids.right_bottom = ImGui::DockBuilderSplitNode(
406 ids.right, ImGuiDir_Down, cfg.vertical_split, nullptr, &ids.right_top);
407 }
408
409 return ids;
410}
411
412} // namespace
413
414void LayoutManager::BuildLayoutFromPreset(EditorType type, ImGuiID dockspace_id) {
415 auto preset = LayoutPresets::GetDefaultPreset(type);
416
417 if (!panel_manager_) {
418 LOG_WARN("LayoutManager",
419 "PanelManager not available, skipping dock layout for type %d",
420 static_cast<int>(type));
421 return;
422 }
423
424 const size_t session_id =
426
427 // On compact/touch layouts, collapse all panels into center tabs instead of
428 // splitting into left/right/bottom regions. This gives each panel full
429 // screen width and makes tab-switching more natural on touch.
430 const ImGuiViewport* viewport = ImGui::GetMainViewport();
431 const float viewport_width = viewport ? viewport->WorkSize.x : 0.0f;
432 const bool is_compact =
433#if defined(__APPLE__) && TARGET_OS_IOS == 1
434 [&]() {
435 static bool compact_mode = true;
436 constexpr float kEnterCompactWidth = 900.0f;
437 constexpr float kExitCompactWidth = 940.0f;
438 if (viewport_width <= 0.0f) {
439 return compact_mode;
440 }
441 compact_mode = compact_mode ? (viewport_width < kExitCompactWidth)
442 : (viewport_width < kEnterCompactWidth);
443 return compact_mode;
444 }();
445#else
446 (viewport_width > 0.0f && viewport_width < 900.0f);
447#endif
448
449 DockSplitNeeds needs{};
450 DockSplitConfig cfg{};
451 if (!is_compact) {
452 needs = ComputeSplitNeeds(preset);
453 cfg = DockSplitConfig::ForEditor(type);
454 }
455 // When compact, needs is all-false → BuildDockTree produces center-only.
456 DockNodeIds ids = BuildDockTree(dockspace_id, needs, cfg);
457
458 auto get_dock_id = [&](DockPosition pos) -> ImGuiID {
459 switch (pos) {
461 if (ids.left_top || ids.left_bottom) {
462 // If sub-nodes exist, default "Left" to Top to avoid stacking in parent
463 return ids.left_top ? ids.left_top : ids.left_bottom;
464 }
465 return ids.left ? ids.left : ids.center;
467 if (ids.right_top || ids.right_bottom) {
468 return ids.right_top ? ids.right_top : ids.right_bottom;
469 }
470 return ids.right ? ids.right : ids.center;
472 return ids.bottom ? ids.bottom : ids.center;
474 return ids.top ? ids.top : ids.center;
476 return ids.left_top ? ids.left_top
477 : (ids.left ? ids.left : ids.center);
479 return ids.left_bottom ? ids.left_bottom
480 : (ids.left ? ids.left : ids.center);
482 return ids.right_top ? ids.right_top
483 : (ids.right ? ids.right : ids.center);
485 return ids.right_bottom ? ids.right_bottom
486 : (ids.right ? ids.right : ids.center);
488 default:
489 return ids.center;
490 }
491 };
492
493 // Iterate through positioned panels and dock them
494 for (const auto& [panel_id, position] : preset.panel_positions) {
495 const PanelDescriptor* desc =
497 ? panel_manager_->GetPanelDescriptor(session_id, panel_id)
498 : nullptr;
499 if (!desc) {
500 LOG_WARN("LayoutManager",
501 "Preset references panel '%s' that is not registered (session "
502 "%zu)",
503 panel_id.c_str(), session_id);
504 continue;
505 }
506
507 std::string window_title = panel_manager_->GetPanelWindowName(*desc);
508 if (window_title.empty()) {
509 LOG_WARN("LayoutManager",
510 "Cannot dock panel '%s': missing window name (session %zu)",
511 panel_id.c_str(), session_id);
512 continue;
513 }
514
515 ImGui::DockBuilderDockWindow(window_title.c_str(), get_dock_id(position));
516 }
517}
518
519// Deprecated individual build methods - redirected to generic or kept empty
531
532void LayoutManager::SaveCurrentLayout(const std::string& name, bool persist) {
533 if (!panel_manager_) {
534 LOG_WARN("LayoutManager",
535 "Cannot save layout '%s': PanelManager not available",
536 name.c_str());
537 return;
538 }
539
540 const LayoutScope scope = GetActiveScope();
541
542 // Serialize current panel visibility state
543 size_t session_id = panel_manager_->GetActiveSessionId();
544 auto visibility_state = panel_manager_->SerializeVisibilityState(session_id);
545
546 // Store in saved_layouts_ for later persistence
547 saved_layouts_[name] = visibility_state;
549 layout_scopes_[name] = scope;
550
551 // Also save ImGui docking layout to memory
552 size_t ini_size = 0;
553 const char* ini_data = ImGui::SaveIniSettingsToMemory(&ini_size);
554 if (ini_data && ini_size > 0) {
555 saved_imgui_layouts_[name] = std::string(ini_data, ini_size);
556 }
557
558 if (persist) {
559 SaveLayoutsToDisk(scope);
560 }
561
562 LOG_INFO("LayoutManager", "Saved layout '%s' with %zu panel states%s",
563 name.c_str(), visibility_state.size(),
564 persist ? "" : " (session-only)");
565}
566
567void LayoutManager::LoadLayout(const std::string& name) {
568 if (!panel_manager_) {
569 LOG_WARN("LayoutManager",
570 "Cannot load layout '%s': PanelManager not available",
571 name.c_str());
572 return;
573 }
574
575 // Find saved layout
576 auto layout_it = saved_layouts_.find(name);
577 if (layout_it == saved_layouts_.end()) {
578 LOG_WARN("LayoutManager", "Layout '%s' not found", name.c_str());
579 return;
580 }
581
582 // Restore panel visibility
583 size_t session_id = panel_manager_->GetActiveSessionId();
584 panel_manager_->RestoreVisibilityState(session_id, layout_it->second,
585 /*publish_events=*/true);
586
587 auto pinned_it = saved_pinned_layouts_.find(name);
588 if (pinned_it != saved_pinned_layouts_.end()) {
589 panel_manager_->RestorePinnedState(pinned_it->second);
590 }
591
592 // Restore ImGui docking layout if available
593 auto imgui_it = saved_imgui_layouts_.find(name);
594 if (imgui_it != saved_imgui_layouts_.end() && !imgui_it->second.empty()) {
595 ImGui::LoadIniSettingsFromMemory(imgui_it->second.c_str(),
596 imgui_it->second.size());
597 }
598
599 LOG_INFO("LayoutManager", "Loaded layout '%s'", name.c_str());
600}
601
603 if (!panel_manager_) {
604 return;
605 }
606
607 temp_session_id_ = session_id;
610
611 size_t ini_size = 0;
612 const char* ini_data = ImGui::SaveIniSettingsToMemory(&ini_size);
613 if (ini_data && ini_size > 0) {
614 temp_session_imgui_layout_ = std::string(ini_data, ini_size);
615 } else {
617 }
618
620 LOG_INFO("LayoutManager",
621 "Captured temporary session layout for session %zu (%zu panel states)",
622 session_id, temp_session_visibility_.size());
623}
624
626 bool clear_after_restore) {
628 return false;
629 }
630
631 if (session_id != temp_session_id_) {
632 LOG_WARN("LayoutManager",
633 "Session layout snapshot belongs to session %zu, requested %zu",
634 temp_session_id_, session_id);
635 return false;
636 }
637
639 /*publish_events=*/true);
641
642 if (!temp_session_imgui_layout_.empty()) {
643 ImGui::LoadIniSettingsFromMemory(temp_session_imgui_layout_.c_str(),
645 }
646
647 if (clear_after_restore) {
649 }
650
651 LOG_INFO("LayoutManager", "Restored temporary session layout for session %zu",
652 session_id);
653 return true;
654}
655
663
664bool LayoutManager::DeleteLayout(const std::string& name) {
665 auto layout_it = saved_layouts_.find(name);
666 if (layout_it == saved_layouts_.end()) {
667 LOG_WARN("LayoutManager", "Cannot delete layout '%s': not found",
668 name.c_str());
669 return false;
670 }
671
672 LayoutScope scope = GetActiveScope();
673 auto scope_it = layout_scopes_.find(name);
674 if (scope_it != layout_scopes_.end()) {
675 scope = scope_it->second;
676 }
677
678 saved_layouts_.erase(layout_it);
679 saved_imgui_layouts_.erase(name);
680 saved_pinned_layouts_.erase(name);
681 layout_scopes_.erase(name);
682
683 SaveLayoutsToDisk(scope);
684
685 LOG_INFO("LayoutManager", "Deleted layout '%s'", name.c_str());
686 return true;
687}
688
689std::vector<LayoutProfile> LayoutManager::GetBuiltInProfiles() {
690 return {
691 {.id = "code",
692 .label = "Code",
693 .description = "Focused editing workspace with minimal panel noise",
694 .preset_name = "Minimal",
695 .open_agent_chat = false},
696 {.id = "debug",
697 .label = "Debug",
698 .description = "Debugger-first workspace for tracing and memory tools",
699 .preset_name = "Developer",
700 .open_agent_chat = false},
701 {.id = "mapping",
702 .label = "Mapping",
703 .description = "Map-centric layout for overworld or dungeon workflows",
704 .preset_name = "Overworld Expert",
705 .open_agent_chat = false},
706 {.id = "chat",
707 .label = "Chat",
708 .description = "Collaboration-heavy layout with agent-centric tooling",
709 .preset_name = "Modder",
710 .open_agent_chat = true},
711 };
712}
713
714bool LayoutManager::ApplyBuiltInProfile(const std::string& profile_id,
715 size_t session_id,
716 EditorType editor_type,
717 LayoutProfile* out_profile) {
718 if (!panel_manager_) {
719 LOG_WARN("LayoutManager",
720 "Cannot apply profile '%s': PanelManager not available",
721 profile_id.c_str());
722 return false;
723 }
724
725 LayoutProfile matched_profile;
726 bool found = false;
727 for (const auto& profile : GetBuiltInProfiles()) {
728 if (profile.id == profile_id) {
729 matched_profile = profile;
730 found = true;
731 break;
732 }
733 }
734
735 if (!found) {
736 LOG_WARN("LayoutManager", "Unknown layout profile id: %s",
737 profile_id.c_str());
738 return false;
739 }
740
741 const std::string resolved_preset =
742 ResolveProfilePresetName(profile_id, editor_type);
743 if (!resolved_preset.empty()) {
744 matched_profile.preset_name = resolved_preset;
745 }
746
747 PanelLayoutPreset preset;
748 if (!TryGetNamedPreset(matched_profile.preset_name, &preset)) {
749 LOG_WARN("LayoutManager", "Unable to resolve preset '%s' for profile '%s'",
750 matched_profile.preset_name.c_str(), profile_id.c_str());
751 return false;
752 }
753
755 for (const auto& panel_id : preset.default_visible_panels) {
756 panel_manager_->ShowPanel(session_id, panel_id);
757 }
758
760
761 if (out_profile) {
762 *out_profile = matched_profile;
763 }
764
765 LOG_INFO("LayoutManager", "Applied profile '%s' via preset '%s'",
766 profile_id.c_str(), matched_profile.preset_name.c_str());
767 return true;
768}
769
770std::vector<std::string> LayoutManager::GetSavedLayoutNames() const {
771 std::vector<std::string> names;
772 names.reserve(saved_layouts_.size());
773 for (const auto& [name, _] : saved_layouts_) {
774 names.push_back(name);
775 }
776 return names;
777}
778
779bool LayoutManager::HasLayout(const std::string& name) const {
780 return saved_layouts_.find(name) != saved_layouts_.end();
781}
782
784 saved_layouts_.clear();
785 saved_imgui_layouts_.clear();
786 saved_pinned_layouts_.clear();
787 layout_scopes_.clear();
788
790 if (!project_layout_key_.empty()) {
792 }
793}
794
795void LayoutManager::SetProjectLayoutKey(const std::string& key) {
796 if (key.empty()) {
798 return;
799 }
802}
803
808
813
815 std::filesystem::path layout_path =
816 GetLayoutsFilePath(scope, project_layout_key_);
817 if (layout_path.empty()) {
818 return;
819 }
820
821 if (!std::filesystem::exists(layout_path)) {
822 if (!merge) {
823 LOG_INFO("LayoutManager", "No layouts file at %s",
824 layout_path.string().c_str());
825 }
826 return;
827 }
828
829 try {
830 std::ifstream file(layout_path);
831 if (!file.is_open()) {
832 LOG_WARN("LayoutManager", "Failed to open layouts file: %s",
833 layout_path.string().c_str());
834 return;
835 }
836
837 yaze::Json root;
838 file >> root;
839
840 if (!root.contains("layouts") || !root["layouts"].is_object()) {
841 LOG_WARN("LayoutManager", "Layouts file missing 'layouts' object: %s",
842 layout_path.string().c_str());
843 return;
844 }
845
846 for (auto& [name, entry] : root["layouts"].items()) {
847 if (!entry.is_object()) {
848 continue;
849 }
850
851 std::unordered_map<std::string, bool> panels;
852 std::unordered_map<std::string, bool> pinned;
853
854 if (entry.contains("panels")) {
855 JsonToBoolMap(entry["panels"], &panels);
856 }
857 if (entry.contains("pinned")) {
858 JsonToBoolMap(entry["pinned"], &pinned);
859 }
860
861 saved_layouts_[name] = std::move(panels);
862 saved_pinned_layouts_[name] = std::move(pinned);
863 layout_scopes_[name] = scope;
864
865 if (entry.contains("imgui_ini") && entry["imgui_ini"].is_string()) {
866 saved_imgui_layouts_[name] = entry["imgui_ini"].get<std::string>();
867 } else {
868 saved_imgui_layouts_.erase(name);
869 }
870 }
871
872 LOG_INFO("LayoutManager", "Loaded layouts from %s",
873 layout_path.string().c_str());
874 } catch (const std::exception& e) {
875 LOG_WARN("LayoutManager", "Failed to load layouts: %s", e.what());
876 }
877}
878
880 std::filesystem::path layout_path =
881 GetLayoutsFilePath(scope, project_layout_key_);
882 if (layout_path.empty()) {
883 LOG_WARN("LayoutManager", "No layout path resolved for scope");
884 return;
885 }
886
888 layout_path.parent_path());
889 if (!status.ok()) {
890 LOG_WARN("LayoutManager", "Failed to create layout directory: %s",
891 status.ToString().c_str());
892 return;
893 }
894
895 try {
896 yaze::Json root;
897 root["version"] = 1;
898 root["layouts"] = yaze::Json::object();
899
900 for (const auto& [name, panels] : saved_layouts_) {
901 auto scope_it = layout_scopes_.find(name);
902 if (scope_it != layout_scopes_.end() && scope_it->second != scope) {
903 continue;
904 }
905
906 yaze::Json entry;
907 entry["panels"] = BoolMapToJson(panels);
908
909 auto pinned_it = saved_pinned_layouts_.find(name);
910 if (pinned_it != saved_pinned_layouts_.end()) {
911 entry["pinned"] = BoolMapToJson(pinned_it->second);
912 }
913
914 auto imgui_it = saved_imgui_layouts_.find(name);
915 if (imgui_it != saved_imgui_layouts_.end()) {
916 entry["imgui_ini"] = imgui_it->second;
917 }
918
919 root["layouts"][name] = entry;
920 }
921
922 std::ofstream file(layout_path);
923 if (!file.is_open()) {
924 LOG_WARN("LayoutManager", "Failed to open layouts file for write: %s",
925 layout_path.string().c_str());
926 return;
927 }
928 file << root.dump(2);
929 file.close();
930 } catch (const std::exception& e) {
931 LOG_WARN("LayoutManager", "Failed to save layouts: %s", e.what());
932 }
933}
934
936 layouts_initialized_[type] = false;
937 LOG_INFO("LayoutManager", "Reset layout for editor type %d",
938 static_cast<int>(type));
939}
940
942 auto it = layouts_initialized_.find(type);
943 return it != layouts_initialized_.end() && it->second;
944}
945
947 layouts_initialized_[type] = true;
948 LOG_INFO("LayoutManager", "Marked layout for editor type %d as initialized",
949 static_cast<int>(type));
950}
951
953 layouts_initialized_.clear();
954 LOG_INFO("LayoutManager", "Cleared all layout initialization flags");
955}
956
957std::string LayoutManager::GetWindowTitle(const std::string& card_id) const {
958 if (!panel_manager_) {
959 return "";
960 }
961
962 const size_t session_id = panel_manager_->GetActiveSessionId();
963 return panel_manager_->GetPanelWindowName(session_id, card_id);
964}
965
966} // namespace editor
967} // namespace yaze
bool is_object() const
Definition json.h:57
static Json object()
Definition json.h:34
items_view items()
Definition json.h:88
std::string dump(int=-1, char=' ', bool=false, int=0) const
Definition json.h:91
bool contains(const std::string &) const
Definition json.h:53
void LoadLayout(const std::string &name)
Load a saved layout by name.
std::string GetWindowTitle(const std::string &card_id) const
Get window title for a card ID from registry.
void BuildScreenLayout(ImGuiID dockspace_id)
void BuildPaletteLayout(ImGuiID dockspace_id)
void BuildDungeonLayout(ImGuiID dockspace_id)
void CaptureTemporarySessionLayout(size_t session_id)
Capture the current workspace as a temporary session layout.
void SetProjectLayoutKey(const std::string &key)
Set the active project layout key (enables project scope)
std::unordered_map< std::string, std::unordered_map< std::string, bool > > saved_layouts_
void SaveLayoutsToDisk(LayoutScope scope) const
void BuildGraphicsLayout(ImGuiID dockspace_id)
void RebuildLayout(EditorType type, ImGuiID dockspace_id)
Force rebuild of layout for a specific editor type.
void BuildEmulatorLayout(ImGuiID dockspace_id)
void LoadLayoutsFromDisk()
Load layouts for the active scope (global + optional project)
void UseGlobalLayouts()
Clear project scope and return to global layouts only.
void BuildAssemblyLayout(ImGuiID dockspace_id)
void ClearInitializationFlags()
Clear all initialization flags (for testing)
void BuildMessageLayout(ImGuiID dockspace_id)
bool DeleteLayout(const std::string &name)
Delete a saved layout by name.
std::unordered_map< std::string, std::string > saved_imgui_layouts_
std::vector< std::string > GetSavedLayoutNames() const
Get list of all saved layout names.
std::unordered_map< EditorType, bool > layouts_initialized_
void LoadLayoutsFromDiskInternal(LayoutScope scope, bool merge)
bool IsLayoutInitialized(EditorType type) const
Check if a layout has been initialized for an editor.
bool RestoreTemporarySessionLayout(size_t session_id, bool clear_after_restore=false)
Restore the temporary session layout if one has been captured.
void BuildSettingsLayout(ImGuiID dockspace_id)
std::unordered_map< std::string, std::unordered_map< std::string, bool > > saved_pinned_layouts_
void ResetToDefaultLayout(EditorType type)
Reset the layout for an editor to its default.
LayoutScope GetActiveScope() const
Get the active layout scope.
void BuildOverworldLayout(ImGuiID dockspace_id)
void ClearTemporarySessionLayout()
Clear temporary session layout snapshot state.
void MarkLayoutInitialized(EditorType type)
Mark a layout as initialized.
void SaveCurrentLayout(const std::string &name, bool persist=true)
Save the current layout with a custom name.
void RequestRebuild()
Request a layout rebuild on next frame.
static std::vector< LayoutProfile > GetBuiltInProfiles()
Get built-in layout profiles.
void InitializeEditorLayout(EditorType type, ImGuiID dockspace_id)
Initialize the default layout for a specific editor type.
bool HasLayout(const std::string &name) const
Check if a layout exists.
std::unordered_map< std::string, LayoutScope > layout_scopes_
void BuildLayoutFromPreset(EditorType type, ImGuiID dockspace_id)
void BuildSpriteLayout(ImGuiID dockspace_id)
bool ApplyBuiltInProfile(const std::string &profile_id, size_t session_id, EditorType editor_type, LayoutProfile *out_profile=nullptr)
Apply a built-in layout profile by profile ID.
std::unordered_map< std::string, bool > temp_session_pinned_
void BuildMusicLayout(ImGuiID dockspace_id)
std::unordered_map< std::string, bool > temp_session_visibility_
static std::vector< std::string > GetDefaultPanels(EditorType type)
Get default visible panels for an editor.
static PanelLayoutPreset GetLogicDebuggerPreset()
Get the "logic debugger" workspace preset (QA and debug focused)
static PanelLayoutPreset GetDungeonMasterPreset()
Get the "dungeon master" workspace preset.
static PanelLayoutPreset GetAudioEngineerPreset()
Get the "audio engineer" workspace preset (music focused)
static PanelLayoutPreset GetDesignerPreset()
Get the "designer" workspace preset (visual-focused)
static PanelLayoutPreset GetOverworldArtistPreset()
Get the "overworld artist" workspace preset.
static PanelLayoutPreset GetDefaultPreset(EditorType type)
Get the default layout preset for an editor type.
static PanelLayoutPreset GetModderPreset()
Get the "modder" workspace preset (full-featured)
static PanelLayoutPreset GetMinimalPreset()
Get the "minimal" workspace preset (minimal cards)
static PanelLayoutPreset GetDeveloperPreset()
Get the "developer" workspace preset (debug-focused)
Central registry for all editor cards with session awareness and dependency injection.
std::unordered_map< std::string, bool > SerializeVisibilityState(size_t session_id) const
Serialize panel visibility state for persistence.
const PanelDescriptor * GetPanelDescriptor(size_t session_id, const std::string &base_card_id) const
void RestorePinnedState(const std::unordered_map< std::string, bool > &state)
Restore pinned panel state from persistence.
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
std::unordered_map< std::string, bool > SerializePinnedState() const
Serialize pinned panel state for persistence.
void HideAllPanelsInSession(size_t session_id)
bool ShowPanel(size_t session_id, const std::string &base_card_id)
std::string GetPanelWindowName(size_t session_id, const std::string &base_card_id) const
Resolve the exact ImGui window name for a panel by base ID.
size_t GetActiveSessionId() const
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
static absl::Status EnsureDirectoryExists(const std::filesystem::path &path)
Ensure a directory exists, creating it if necessary.
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
DockSplitNeeds ComputeSplitNeeds(const PanelLayoutPreset &preset)
std::string ResolveProfilePresetName(const std::string &profile_id, EditorType editor_type)
bool TryGetNamedPreset(const std::string &preset_name, PanelLayoutPreset *preset_out)
yaze::Json BoolMapToJson(const std::unordered_map< std::string, bool > &map)
std::filesystem::path GetLayoutsFilePath(LayoutScope scope, const std::string &project_key)
void JsonToBoolMap(const yaze::Json &obj, std::unordered_map< std::string, bool > *map)
void ShowDefaultPanelsForEditor(PanelManager *registry, EditorType type)
DockNodeIds BuildDockTree(ImGuiID dockspace_id, const DockSplitNeeds &needs, const DockSplitConfig &cfg)
LayoutScope
Storage scope for saved layouts.
DockPosition
Preferred dock position for a card in a layout.
Built-in workflow-oriented layout profiles.
Metadata for an editor panel (formerly PanelInfo)
Defines default panel visibility for an editor type.
std::unordered_map< std::string, DockPosition > panel_positions
std::vector< std::string > default_visible_panels