10#include "imgui/imgui.h"
11#include "imgui/imgui_internal.h"
14#include <TargetConditionals.h>
27 if (!registry)
return;
30 for (
const auto& panel_id : default_panels) {
34 LOG_INFO(
"LayoutManager",
"Showing %zu default panels for editor type %d",
35 default_panels.size(),
static_cast<int>(type));
40 for (
const auto& [key, value] : map) {
47 std::unordered_map<std::string, bool>* map) {
52 for (
const auto& [key, value] : obj.
items()) {
53 if (value.is_boolean()) {
54 (*map)[key] = value.get<
bool>();
60 const std::string& project_key) {
62 if (!layouts_dir.ok()) {
67 std::filesystem::path projects_dir = *layouts_dir /
"projects";
69 return projects_dir / (project_key +
".json");
76 return *layouts_dir /
"layouts.json";
85 if (preset_name ==
"Minimal") {
89 if (preset_name ==
"Developer") {
93 if (preset_name ==
"Designer") {
97 if (preset_name ==
"Modder") {
101 if (preset_name ==
"Overworld Expert") {
105 if (preset_name ==
"Dungeon Expert") {
109 if (preset_name ==
"Testing") {
113 if (preset_name ==
"Audio") {
123 if (profile_id ==
"mapping") {
125 return "Dungeon Expert";
127 return "Overworld Expert";
129 if (profile_id ==
"code") {
132 if (profile_id ==
"debug") {
135 if (profile_id ==
"chat") {
144 ImGuiID dockspace_id) {
148 "Layout for editor type %d already initialized, skipping",
149 static_cast<int>(type));
157 LOG_INFO(
"LayoutManager",
"Initializing layout for editor type %d",
158 static_cast<int>(type));
161 ImGui::DockBuilderRemoveNode(dockspace_id);
162 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
164 ImVec2 dockspace_size = ImVec2(1280, 720);
165 if (
auto* viewport = ImGui::GetMainViewport()) {
166 dockspace_size = viewport->WorkSize;
170 if (last_size.x > 0.0f && last_size.y > 0.0f) {
171 dockspace_size = last_size;
173 ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size);
182 ImGui::DockBuilderFinish(dockspace_id);
190 ImGuiDockNode* node = ImGui::DockBuilderGetNode(dockspace_id);
193 "Cannot rebuild layout: dockspace ID %u not found", dockspace_id);
197 LOG_INFO(
"LayoutManager",
"Forcing rebuild of layout for editor type %d",
198 static_cast<int>(type));
208 ImGui::DockBuilderRemoveNode(dockspace_id);
209 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
210 ImVec2 dockspace_size = ImGui::GetMainViewport()->WorkSize;
212 if (last_size.x > 0.0f && last_size.y > 0.0f) {
213 dockspace_size = last_size;
215 ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size);
224 ImGui::DockBuilderFinish(dockspace_id);
229 LOG_INFO(
"LayoutManager",
"Layout rebuild complete for editor type %d",
230 static_cast<int>(type));
238 float bottom = 0.22f;
240 float vertical_split = 0.52f;
246 case EditorType::kDungeon:
253 case EditorType::kOverworld:
259 case EditorType::kGraphics:
264 case EditorType::kPalette:
269 case EditorType::kSprite:
274 case EditorType::kScreen:
279 case EditorType::kMessage:
284 case EditorType::kAssembly:
289 case EditorType::kEmulator:
294 case EditorType::kAgent:
313 ImGuiID left_top = 0;
314 ImGuiID left_bottom = 0;
315 ImGuiID right_top = 0;
316 ImGuiID right_bottom = 0;
324 bool left_top =
false;
325 bool left_bottom =
false;
326 bool right_top =
false;
327 bool right_bottom =
false;
348 needs.left_top =
true;
352 needs.left_bottom =
true;
356 needs.right_top =
true;
360 needs.right_bottom =
true;
373 ids.
center = dockspace_id;
377 ids.left = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Left, cfg.
left,
378 nullptr, &ids.center);
381 ids.right = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Right,
382 cfg.
right,
nullptr, &ids.center);
385 ids.bottom = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Down,
386 cfg.
bottom,
nullptr, &ids.center);
389 ids.top = ImGui::DockBuilderSplitNode(ids.center, ImGuiDir_Up, cfg.
top,
390 nullptr, &ids.center);
396 ids.left_bottom = ImGui::DockBuilderSplitNode(
397 ids.left, ImGuiDir_Down, cfg.
vertical_split,
nullptr, &ids.left_top);
405 ids.right_bottom = ImGui::DockBuilderSplitNode(
406 ids.right, ImGuiDir_Down, cfg.
vertical_split,
nullptr, &ids.right_top);
419 "PanelManager not available, skipping dock layout for type %d",
420 static_cast<int>(type));
424 const size_t session_id =
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
435 static bool compact_mode =
true;
436 constexpr float kEnterCompactWidth = 900.0f;
437 constexpr float kExitCompactWidth = 940.0f;
438 if (viewport_width <= 0.0f) {
441 compact_mode = compact_mode ? (viewport_width < kExitCompactWidth)
442 : (viewport_width < kEnterCompactWidth);
446 (viewport_width > 0.0f && viewport_width < 900.0f);
449 DockSplitNeeds needs{};
450 DockSplitConfig cfg{};
452 needs = ComputeSplitNeeds(preset);
453 cfg = DockSplitConfig::ForEditor(type);
456 DockNodeIds ids = BuildDockTree(dockspace_id, needs, cfg);
461 if (ids.left_top || ids.left_bottom) {
463 return ids.left_top ? ids.left_top : ids.left_bottom;
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;
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);
494 for (
const auto& [panel_id, position] : preset.panel_positions) {
501 "Preset references panel '%s' that is not registered (session "
503 panel_id.c_str(), session_id);
508 if (window_title.empty()) {
510 "Cannot dock panel '%s': missing window name (session %zu)",
511 panel_id.c_str(), session_id);
515 ImGui::DockBuilderDockWindow(window_title.c_str(), get_dock_id(position));
535 "Cannot save layout '%s': PanelManager not available",
553 const char* ini_data = ImGui::SaveIniSettingsToMemory(&ini_size);
554 if (ini_data && ini_size > 0) {
562 LOG_INFO(
"LayoutManager",
"Saved layout '%s' with %zu panel states%s",
563 name.c_str(), visibility_state.size(),
564 persist ?
"" :
" (session-only)");
570 "Cannot load layout '%s': PanelManager not available",
578 LOG_WARN(
"LayoutManager",
"Layout '%s' not found", name.c_str());
595 ImGui::LoadIniSettingsFromMemory(imgui_it->second.c_str(),
596 imgui_it->second.size());
599 LOG_INFO(
"LayoutManager",
"Loaded layout '%s'", name.c_str());
612 const char* ini_data = ImGui::SaveIniSettingsToMemory(&ini_size);
613 if (ini_data && ini_size > 0) {
621 "Captured temporary session layout for session %zu (%zu panel states)",
626 bool clear_after_restore) {
633 "Session layout snapshot belongs to session %zu, requested %zu",
647 if (clear_after_restore) {
651 LOG_INFO(
"LayoutManager",
"Restored temporary session layout for session %zu",
667 LOG_WARN(
"LayoutManager",
"Cannot delete layout '%s': not found",
675 scope = scope_it->second;
685 LOG_INFO(
"LayoutManager",
"Deleted layout '%s'", name.c_str());
693 .description =
"Focused editing workspace with minimal panel noise",
694 .preset_name =
"Minimal",
695 .open_agent_chat =
false},
698 .description =
"Debugger-first workspace for tracing and memory tools",
699 .preset_name =
"Developer",
700 .open_agent_chat =
false},
703 .description =
"Map-centric layout for overworld or dungeon workflows",
704 .preset_name =
"Overworld Expert",
705 .open_agent_chat =
false},
708 .description =
"Collaboration-heavy layout with agent-centric tooling",
709 .preset_name =
"Modder",
710 .open_agent_chat =
true},
720 "Cannot apply profile '%s': PanelManager not available",
728 if (profile.id == profile_id) {
729 matched_profile = profile;
736 LOG_WARN(
"LayoutManager",
"Unknown layout profile id: %s",
741 const std::string resolved_preset =
742 ResolveProfilePresetName(profile_id, editor_type);
743 if (!resolved_preset.empty()) {
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());
762 *out_profile = matched_profile;
765 LOG_INFO(
"LayoutManager",
"Applied profile '%s' via preset '%s'",
766 profile_id.c_str(), matched_profile.
preset_name.c_str());
771 std::vector<std::string> names;
774 names.push_back(name);
815 std::filesystem::path layout_path =
817 if (layout_path.empty()) {
821 if (!std::filesystem::exists(layout_path)) {
823 LOG_INFO(
"LayoutManager",
"No layouts file at %s",
824 layout_path.string().c_str());
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());
841 LOG_WARN(
"LayoutManager",
"Layouts file missing 'layouts' object: %s",
842 layout_path.string().c_str());
846 for (
auto& [name, entry] : root[
"layouts"].
items()) {
847 if (!entry.is_object()) {
851 std::unordered_map<std::string, bool> panels;
852 std::unordered_map<std::string, bool> pinned;
854 if (entry.contains(
"panels")) {
855 JsonToBoolMap(entry[
"panels"], &panels);
857 if (entry.contains(
"pinned")) {
858 JsonToBoolMap(entry[
"pinned"], &pinned);
865 if (entry.contains(
"imgui_ini") && entry[
"imgui_ini"].is_string()) {
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());
880 std::filesystem::path layout_path =
882 if (layout_path.empty()) {
883 LOG_WARN(
"LayoutManager",
"No layout path resolved for scope");
888 layout_path.parent_path());
890 LOG_WARN(
"LayoutManager",
"Failed to create layout directory: %s",
891 status.ToString().c_str());
902 if (scope_it !=
layout_scopes_.end() && scope_it->second != scope) {
907 entry[
"panels"] = BoolMapToJson(panels);
911 entry[
"pinned"] = BoolMapToJson(pinned_it->second);
916 entry[
"imgui_ini"] = imgui_it->second;
919 root[
"layouts"][name] = entry;
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());
928 file << root.
dump(2);
930 }
catch (
const std::exception& e) {
931 LOG_WARN(
"LayoutManager",
"Failed to save layouts: %s", e.what());
937 LOG_INFO(
"LayoutManager",
"Reset layout for editor type %d",
938 static_cast<int>(type));
948 LOG_INFO(
"LayoutManager",
"Marked layout for editor type %d as initialized",
949 static_cast<int>(type));
954 LOG_INFO(
"LayoutManager",
"Cleared all layout initialization flags");
std::string dump(int=-1, char=' ', bool=false, int=0) const
bool contains(const std::string &) const
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.
EditorType current_editor_type_
void BuildScreenLayout(ImGuiID dockspace_id)
ImGuiID last_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::string temp_session_imgui_layout_
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)
PanelManager * panel_manager_
std::string project_layout_key_
bool has_temp_session_layout_
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 ImVec2 GetLastDockspaceSize()
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
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
static DockSplitConfig ForEditor(EditorType type)