feat(editor): introduce LayoutManager for enhanced editor layouts
- Added LayoutManager to manage ImGui DockBuilder layouts for various editor types, providing professional default layouts similar to VSCode. - Integrated layout initialization and persistence features, allowing users to save and load custom layouts. - Updated EditorManager to initialize LayoutManager and set default layouts upon editor activation. Benefits: - Improves user experience by offering tailored layouts for different editing tasks. - Enhances maintainability by centralizing layout management and initialization logic within the new LayoutManager class.
This commit is contained in:
@@ -48,6 +48,7 @@ set(
|
||||
app/editor/system/window_delegate.cc
|
||||
app/editor/system/shortcut_configurator.cc
|
||||
app/editor/ui/editor_selection_dialog.cc
|
||||
app/editor/ui/layout_manager.cc
|
||||
app/editor/ui/menu_builder.cc
|
||||
app/editor/ui/ui_coordinator.cc
|
||||
app/editor/ui/welcome_screen.cc
|
||||
|
||||
@@ -182,6 +182,9 @@ EditorManager::EditorManager()
|
||||
*session_coordinator_, window_delegate_, toast_manager_, *popup_manager_,
|
||||
shortcut_manager_);
|
||||
|
||||
// STEP 4.5: Initialize LayoutManager (DockBuilder layouts for editors)
|
||||
layout_manager_ = std::make_unique<LayoutManager>();
|
||||
|
||||
// STEP 5: ShortcutConfigurator created later in Initialize() method
|
||||
// It depends on all above coordinators being available
|
||||
}
|
||||
@@ -1954,6 +1957,12 @@ void EditorManager::SwitchToEditor(EditorType editor_type) {
|
||||
// Editor activated - set its category
|
||||
card_registry_.SetActiveCategory(
|
||||
EditorRegistry::GetEditorCategory(editor_type));
|
||||
|
||||
// Initialize default layout on first activation
|
||||
if (layout_manager_ && !layout_manager_->IsLayoutInitialized(editor_type)) {
|
||||
ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
|
||||
layout_manager_->InitializeEditorLayout(editor_type, dockspace_id);
|
||||
}
|
||||
} else {
|
||||
// Editor deactivated - switch to another active card-based editor
|
||||
for (auto* other : current_editor_set_->active_editors_) {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "app/editor/system/toast_manager.h"
|
||||
#include "app/editor/system/window_delegate.h"
|
||||
#include "app/editor/ui/editor_selection_dialog.h"
|
||||
#include "app/editor/ui/layout_manager.h"
|
||||
#include "app/editor/ui/menu_builder.h"
|
||||
#include "app/editor/ui/ui_coordinator.h"
|
||||
#include "app/editor/ui/welcome_screen.h"
|
||||
@@ -409,6 +410,7 @@ class EditorManager {
|
||||
std::unique_ptr<UICoordinator> ui_coordinator_;
|
||||
WindowDelegate window_delegate_;
|
||||
std::unique_ptr<SessionCoordinator> session_coordinator_;
|
||||
std::unique_ptr<LayoutManager> layout_manager_; // DockBuilder layout management
|
||||
|
||||
float autosave_timer_ = 0.0f;
|
||||
|
||||
|
||||
@@ -218,18 +218,19 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps,
|
||||
});
|
||||
|
||||
if (card_registry) {
|
||||
// Note: Using Ctrl+Alt for card shortcuts to avoid conflicts with Save As (Ctrl+Shift+S)
|
||||
RegisterIfValid(shortcut_manager, "Show Dungeon Cards",
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_D},
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_D},
|
||||
[card_registry]() {
|
||||
card_registry->ShowAllCardsInCategory("Dungeon");
|
||||
});
|
||||
RegisterIfValid(shortcut_manager, "Show Graphics Cards",
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_G},
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_G},
|
||||
[card_registry]() {
|
||||
card_registry->ShowAllCardsInCategory("Graphics");
|
||||
});
|
||||
RegisterIfValid(shortcut_manager, "Show Screen Cards",
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_S},
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_S},
|
||||
[card_registry]() {
|
||||
card_registry->ShowAllCardsInCategory("Screen");
|
||||
});
|
||||
@@ -320,8 +321,9 @@ void ConfigureMenuShortcuts(const ShortcutDependencies& deps,
|
||||
}
|
||||
});
|
||||
|
||||
// Note: Changed from Ctrl+Shift+R to Ctrl+Alt+R to avoid conflict with Proposal Drawer
|
||||
RegisterIfValid(shortcut_manager, "Reset Layout",
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_R},
|
||||
{ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_R},
|
||||
[workspace_manager]() {
|
||||
if (workspace_manager) {
|
||||
workspace_manager->ResetWorkspaceLayout();
|
||||
|
||||
413
src/app/editor/ui/layout_manager.cc
Normal file
413
src/app/editor/ui/layout_manager.cc
Normal file
@@ -0,0 +1,413 @@
|
||||
#include "app/editor/ui/layout_manager.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "imgui/imgui_internal.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
void LayoutManager::InitializeEditorLayout(EditorType type,
|
||||
ImGuiID dockspace_id) {
|
||||
// Don't reinitialize if already set up
|
||||
if (IsLayoutInitialized(type)) {
|
||||
LOG_INFO("LayoutManager",
|
||||
"Layout for editor type %d already initialized, skipping",
|
||||
static_cast<int>(type));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("LayoutManager", "Initializing layout for editor type %d",
|
||||
static_cast<int>(type));
|
||||
|
||||
// Clear existing layout for this dockspace
|
||||
ImGui::DockBuilderRemoveNode(dockspace_id);
|
||||
ImGui::DockBuilderAddNode(dockspace_id,
|
||||
ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size);
|
||||
|
||||
// Build layout based on editor type
|
||||
switch (type) {
|
||||
case EditorType::kOverworld:
|
||||
BuildOverworldLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kDungeon:
|
||||
BuildDungeonLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kGraphics:
|
||||
BuildGraphicsLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kPalette:
|
||||
BuildPaletteLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kScreen:
|
||||
BuildScreenLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kMusic:
|
||||
BuildMusicLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kSprite:
|
||||
BuildSpriteLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kMessage:
|
||||
BuildMessageLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kAssembly:
|
||||
BuildAssemblyLayout(dockspace_id);
|
||||
break;
|
||||
case EditorType::kSettings:
|
||||
BuildSettingsLayout(dockspace_id);
|
||||
break;
|
||||
default:
|
||||
LOG_WARN("LayoutManager", "No layout defined for editor type %d",
|
||||
static_cast<int>(type));
|
||||
break;
|
||||
}
|
||||
|
||||
// Finalize the layout
|
||||
ImGui::DockBuilderFinish(dockspace_id);
|
||||
|
||||
// Mark as initialized
|
||||
MarkLayoutInitialized(type);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildOverworldLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Overworld
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 25%: Tile16 Selector (top 50%) + Tile8 Selector (bottom 50%)
|
||||
// - Center 60%: Main Canvas (full height)
|
||||
// - Right 15%: Area Graphics (top 60%) + Scratch Pad (bottom 40%)
|
||||
//
|
||||
// Additional floating cards:
|
||||
// - Tile16 Editor (floating, 800x600)
|
||||
// - GFX Groups (floating, 700x550)
|
||||
// - Usage Stats (floating, 600x500)
|
||||
// - V3 Settings (floating, 500x600)
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_center_id = 0;
|
||||
ImGuiID dock_right_id = 0;
|
||||
|
||||
// Split dockspace: Left 25% | Center 60% | Right 15%
|
||||
dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f,
|
||||
nullptr, &dockspace_id);
|
||||
dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right,
|
||||
0.20f, nullptr, &dockspace_id);
|
||||
dock_center_id = dockspace_id; // Center is what remains
|
||||
|
||||
// Split left panel: Tile16 (top) and Tile8 (bottom)
|
||||
ImGuiID dock_left_top = 0;
|
||||
ImGuiID dock_left_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_left_id, ImGuiDir_Down, 0.50f, nullptr, &dock_left_top);
|
||||
|
||||
// Split right panel: Area Graphics (top) and Scratch Pad (bottom)
|
||||
ImGuiID dock_right_top = 0;
|
||||
ImGuiID dock_right_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_right_id, ImGuiDir_Down, 0.40f, nullptr, &dock_right_top);
|
||||
|
||||
// Dock windows to their designated nodes
|
||||
ImGui::DockBuilderDockWindow(" Overworld Canvas", dock_center_id);
|
||||
ImGui::DockBuilderDockWindow(" Tile16 Selector", dock_left_top);
|
||||
ImGui::DockBuilderDockWindow(" Tile8 Selector", dock_left_bottom);
|
||||
ImGui::DockBuilderDockWindow(" Area Graphics", dock_right_top);
|
||||
ImGui::DockBuilderDockWindow(" Scratch Pad", dock_right_bottom);
|
||||
|
||||
// Note: Floating windows (Tile16 Editor, GFX Groups, etc.) are not docked
|
||||
// They will appear as floating windows with their configured default positions
|
||||
}
|
||||
|
||||
void LayoutManager::BuildDungeonLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Dungeon
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 20%: Room Selector (top 60%) + Entrances (bottom 40%)
|
||||
// - Center 65%: Room Canvas + Tabs for multiple rooms
|
||||
// - Right 15%: Object Editor (top) + Palette Editor (bottom)
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_center_id = 0;
|
||||
ImGuiID dock_right_id = 0;
|
||||
|
||||
// Split dockspace: Left 20% | Center 65% | Right 15%
|
||||
dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.20f,
|
||||
nullptr, &dockspace_id);
|
||||
dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right,
|
||||
0.19f, nullptr, &dockspace_id);
|
||||
dock_center_id = dockspace_id;
|
||||
|
||||
// Split left panel: Room Selector (top 60%) and Entrances (bottom 40%)
|
||||
ImGuiID dock_left_top = 0;
|
||||
ImGuiID dock_left_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_left_id, ImGuiDir_Down, 0.40f, nullptr, &dock_left_top);
|
||||
|
||||
// Split right panel: Object Editor (top 50%) and Palette Editor (bottom 50%)
|
||||
ImGuiID dock_right_top = 0;
|
||||
ImGuiID dock_right_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_right_id, ImGuiDir_Down, 0.50f, nullptr, &dock_right_top);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Rooms List", dock_left_top);
|
||||
ImGui::DockBuilderDockWindow(" Entrances", dock_left_bottom);
|
||||
ImGui::DockBuilderDockWindow(" Object Editor", dock_right_top);
|
||||
ImGui::DockBuilderDockWindow(" Palette Editor", dock_right_bottom);
|
||||
|
||||
// Room tabs and Room Matrix are floating by default
|
||||
// Individual room windows (###RoomCard*) will dock together due to their
|
||||
// window class
|
||||
}
|
||||
|
||||
void LayoutManager::BuildGraphicsLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Graphics
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 30%: Sheet Browser
|
||||
// - Center 50%: Sheet Editor
|
||||
// - Right 20%: Animations (top) + Prototype (bottom)
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_center_id = 0;
|
||||
ImGuiID dock_right_id = 0;
|
||||
|
||||
// Split dockspace: Left 30% | Center 50% | Right 20%
|
||||
dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.30f,
|
||||
nullptr, &dockspace_id);
|
||||
dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right,
|
||||
0.29f, nullptr, &dockspace_id);
|
||||
dock_center_id = dockspace_id;
|
||||
|
||||
// Split right panel: Animations (top) and Prototype (bottom)
|
||||
ImGuiID dock_right_top = 0;
|
||||
ImGuiID dock_right_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_right_id, ImGuiDir_Down, 0.50f, nullptr, &dock_right_top);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" GFX Sheets", dock_left_id);
|
||||
ImGui::DockBuilderDockWindow(" Sheet Editor", dock_center_id);
|
||||
ImGui::DockBuilderDockWindow(" Animations", dock_right_top);
|
||||
ImGui::DockBuilderDockWindow(" Prototype", dock_right_bottom);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildPaletteLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Palette
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 25%: Group Manager (top) + ROM Palette Browser (bottom)
|
||||
// - Center 50%: Main Palette Editor
|
||||
// - Right 25%: SNES Palette (top) + Color Harmony Tools (bottom)
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_center_id = 0;
|
||||
ImGuiID dock_right_id = 0;
|
||||
|
||||
// Split dockspace: Left 25% | Center 50% | Right 25%
|
||||
dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f,
|
||||
nullptr, &dockspace_id);
|
||||
dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right,
|
||||
0.33f, nullptr, &dockspace_id);
|
||||
dock_center_id = dockspace_id;
|
||||
|
||||
// Split left panel: Group Manager (top) and ROM Browser (bottom)
|
||||
ImGuiID dock_left_top = 0;
|
||||
ImGuiID dock_left_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_left_id, ImGuiDir_Down, 0.50f, nullptr, &dock_left_top);
|
||||
|
||||
// Split right panel: SNES Palette (top) and Color Tools (bottom)
|
||||
ImGuiID dock_right_top = 0;
|
||||
ImGuiID dock_right_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_right_id, ImGuiDir_Down, 0.50f, nullptr, &dock_right_top);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Group Manager", dock_left_top);
|
||||
ImGui::DockBuilderDockWindow(" ROM Palette Browser", dock_left_bottom);
|
||||
ImGui::DockBuilderDockWindow(" Palette Editor", dock_center_id);
|
||||
ImGui::DockBuilderDockWindow(" SNES Palette", dock_right_top);
|
||||
ImGui::DockBuilderDockWindow(" Color Harmony", dock_right_bottom);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildScreenLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Screen
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Grid layout with Overworld Map in center (larger)
|
||||
// - Corners: Dungeon Maps, Title Screen, Inventory Menu, Naming Screen
|
||||
|
||||
ImGuiID dock_top = 0;
|
||||
ImGuiID dock_bottom = ImGui::DockBuilderSplitNode(
|
||||
dockspace_id, ImGuiDir_Down, 0.50f, nullptr, &dock_top);
|
||||
|
||||
// Split top: left and right
|
||||
ImGuiID dock_top_left = 0;
|
||||
ImGuiID dock_top_right = ImGui::DockBuilderSplitNode(
|
||||
dock_top, ImGuiDir_Right, 0.50f, nullptr, &dock_top_left);
|
||||
|
||||
// Split bottom: left and right
|
||||
ImGuiID dock_bottom_left = 0;
|
||||
ImGuiID dock_bottom_right = ImGui::DockBuilderSplitNode(
|
||||
dock_bottom, ImGuiDir_Right, 0.50f, nullptr, &dock_bottom_left);
|
||||
|
||||
// Dock windows in grid
|
||||
ImGui::DockBuilderDockWindow(" Dungeon Map Editor", dock_top_left);
|
||||
ImGui::DockBuilderDockWindow(" Title Screen", dock_top_right);
|
||||
ImGui::DockBuilderDockWindow(" Inventory Menu", dock_bottom_left);
|
||||
ImGui::DockBuilderDockWindow(" Naming Screen", dock_bottom_right);
|
||||
|
||||
// Overworld Map could be floating or in center - let user configure
|
||||
}
|
||||
|
||||
void LayoutManager::BuildMusicLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Music Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 30%: Music Tracker
|
||||
// - Center 45%: Instrument Editor
|
||||
// - Right 25%: Assembly/Export
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_center_id = 0;
|
||||
ImGuiID dock_right_id = 0;
|
||||
|
||||
// Split dockspace: Left 30% | Center 45% | Right 25%
|
||||
dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.30f,
|
||||
nullptr, &dockspace_id);
|
||||
dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right,
|
||||
0.36f, nullptr, &dockspace_id);
|
||||
dock_center_id = dockspace_id;
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Music Tracker", dock_left_id);
|
||||
ImGui::DockBuilderDockWindow(" Instrument Editor", dock_center_id);
|
||||
ImGui::DockBuilderDockWindow(" Music Assembly", dock_right_id);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildSpriteLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Sprite
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 50%: Vanilla Sprites
|
||||
// - Right 50%: Custom Sprites
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_right_id = ImGui::DockBuilderSplitNode(
|
||||
dockspace_id, ImGuiDir_Right, 0.50f, nullptr, &dock_left_id);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Vanilla Sprites", dock_left_id);
|
||||
ImGui::DockBuilderDockWindow(" Custom Sprites", dock_right_id);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildMessageLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Message
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 25%: Message List
|
||||
// - Center 50%: Message Editor
|
||||
// - Right 25%: Font Atlas (top) + Dictionary (bottom)
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_center_id = 0;
|
||||
ImGuiID dock_right_id = 0;
|
||||
|
||||
// Split dockspace: Left 25% | Center 50% | Right 25%
|
||||
dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f,
|
||||
nullptr, &dockspace_id);
|
||||
dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right,
|
||||
0.33f, nullptr, &dockspace_id);
|
||||
dock_center_id = dockspace_id;
|
||||
|
||||
// Split right panel: Font Atlas (top) and Dictionary (bottom)
|
||||
ImGuiID dock_right_top = 0;
|
||||
ImGuiID dock_right_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_right_id, ImGuiDir_Down, 0.50f, nullptr, &dock_right_top);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Message List", dock_left_id);
|
||||
ImGui::DockBuilderDockWindow(" Message Editor", dock_center_id);
|
||||
ImGui::DockBuilderDockWindow(" Font Atlas", dock_right_top);
|
||||
ImGui::DockBuilderDockWindow(" Dictionary", dock_right_bottom);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildAssemblyLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Assembly
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 60%: Code Editor
|
||||
// - Right 40%: Output/Errors (top) + Documentation (bottom)
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_right_id = ImGui::DockBuilderSplitNode(
|
||||
dockspace_id, ImGuiDir_Right, 0.40f, nullptr, &dock_left_id);
|
||||
|
||||
// Split right panel: Output (top) and Docs (bottom)
|
||||
ImGuiID dock_right_top = 0;
|
||||
ImGuiID dock_right_bottom = ImGui::DockBuilderSplitNode(
|
||||
dock_right_id, ImGuiDir_Down, 0.50f, nullptr, &dock_right_top);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Assembly Editor", dock_left_id);
|
||||
ImGui::DockBuilderDockWindow(" Assembly Output", dock_right_top);
|
||||
ImGui::DockBuilderDockWindow(" Assembly Docs", dock_right_bottom);
|
||||
}
|
||||
|
||||
void LayoutManager::BuildSettingsLayout(ImGuiID dockspace_id) {
|
||||
// TODO: [EditorManagerRefactor] Implement DockBuilder layout for Settings
|
||||
// Editor
|
||||
//
|
||||
// Desired layout:
|
||||
// - Left 25%: Category navigation (vertical list)
|
||||
// - Right 75%: Settings content for selected category
|
||||
|
||||
ImGuiID dock_left_id = 0;
|
||||
ImGuiID dock_right_id = ImGui::DockBuilderSplitNode(
|
||||
dockspace_id, ImGuiDir_Right, 0.75f, nullptr, &dock_left_id);
|
||||
|
||||
// Dock windows
|
||||
ImGui::DockBuilderDockWindow(" Settings Navigation", dock_left_id);
|
||||
ImGui::DockBuilderDockWindow(" Settings Content", dock_right_id);
|
||||
}
|
||||
|
||||
void LayoutManager::SaveCurrentLayout(const std::string& name) {
|
||||
// TODO: [EditorManagerRefactor] Implement layout saving to file
|
||||
// Use ImGui::SaveIniSettingsToMemory() and write to custom file
|
||||
LOG_INFO("LayoutManager", "Saving layout: %s", name.c_str());
|
||||
}
|
||||
|
||||
void LayoutManager::LoadLayout(const std::string& name) {
|
||||
// TODO: [EditorManagerRefactor] Implement layout loading from file
|
||||
// Use ImGui::LoadIniSettingsFromMemory() and read from custom file
|
||||
LOG_INFO("LayoutManager", "Loading layout: %s", name.c_str());
|
||||
}
|
||||
|
||||
void LayoutManager::ResetToDefaultLayout(EditorType type) {
|
||||
layouts_initialized_[type] = false;
|
||||
LOG_INFO("LayoutManager", "Reset layout for editor type %d",
|
||||
static_cast<int>(type));
|
||||
}
|
||||
|
||||
bool LayoutManager::IsLayoutInitialized(EditorType type) const {
|
||||
auto it = layouts_initialized_.find(type);
|
||||
return it != layouts_initialized_.end() && it->second;
|
||||
}
|
||||
|
||||
void LayoutManager::MarkLayoutInitialized(EditorType type) {
|
||||
layouts_initialized_[type] = true;
|
||||
LOG_INFO("LayoutManager", "Marked layout for editor type %d as initialized",
|
||||
static_cast<int>(type));
|
||||
}
|
||||
|
||||
void LayoutManager::ClearInitializationFlags() {
|
||||
layouts_initialized_.clear();
|
||||
LOG_INFO("LayoutManager", "Cleared all layout initialization flags");
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
96
src/app/editor/ui/layout_manager.h
Normal file
96
src/app/editor/ui/layout_manager.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef YAZE_APP_EDITOR_UI_LAYOUT_MANAGER_H_
|
||||
#define YAZE_APP_EDITOR_UI_LAYOUT_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "app/editor/editor.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
/**
|
||||
* @class LayoutManager
|
||||
* @brief Manages ImGui DockBuilder layouts for each editor type
|
||||
*
|
||||
* Provides professional default layouts using ImGui's DockBuilder API,
|
||||
* similar to VSCode's workspace layouts. Each editor type has a custom
|
||||
* layout optimized for its workflow.
|
||||
*
|
||||
* Features:
|
||||
* - Per-editor default layouts (Overworld, Dungeon, Graphics, etc.)
|
||||
* - Layout persistence and restoration
|
||||
* - Workspace presets (Developer, Designer, Modder)
|
||||
* - Dynamic layout initialization on first editor switch
|
||||
*/
|
||||
class LayoutManager {
|
||||
public:
|
||||
LayoutManager() = default;
|
||||
~LayoutManager() = default;
|
||||
|
||||
/**
|
||||
* @brief Initialize the default layout for a specific editor type
|
||||
* @param type The editor type to initialize
|
||||
* @param dockspace_id The ImGui dockspace ID to build the layout in
|
||||
*/
|
||||
void InitializeEditorLayout(EditorType type, ImGuiID dockspace_id);
|
||||
|
||||
/**
|
||||
* @brief Save the current layout with a custom name
|
||||
* @param name The name to save the layout under
|
||||
*/
|
||||
void SaveCurrentLayout(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief Load a saved layout by name
|
||||
* @param name The name of the layout to load
|
||||
*/
|
||||
void LoadLayout(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief Reset the layout for an editor to its default
|
||||
* @param type The editor type to reset
|
||||
*/
|
||||
void ResetToDefaultLayout(EditorType type);
|
||||
|
||||
/**
|
||||
* @brief Check if a layout has been initialized for an editor
|
||||
* @param type The editor type to check
|
||||
* @return True if layout is initialized
|
||||
*/
|
||||
bool IsLayoutInitialized(EditorType type) const;
|
||||
|
||||
/**
|
||||
* @brief Mark a layout as initialized
|
||||
* @param type The editor type to mark
|
||||
*/
|
||||
void MarkLayoutInitialized(EditorType type);
|
||||
|
||||
/**
|
||||
* @brief Clear all initialization flags (for testing)
|
||||
*/
|
||||
void ClearInitializationFlags();
|
||||
|
||||
private:
|
||||
// DockBuilder layout implementations for each editor type
|
||||
void BuildOverworldLayout(ImGuiID dockspace_id);
|
||||
void BuildDungeonLayout(ImGuiID dockspace_id);
|
||||
void BuildGraphicsLayout(ImGuiID dockspace_id);
|
||||
void BuildPaletteLayout(ImGuiID dockspace_id);
|
||||
void BuildScreenLayout(ImGuiID dockspace_id);
|
||||
void BuildMusicLayout(ImGuiID dockspace_id);
|
||||
void BuildSpriteLayout(ImGuiID dockspace_id);
|
||||
void BuildMessageLayout(ImGuiID dockspace_id);
|
||||
void BuildAssemblyLayout(ImGuiID dockspace_id);
|
||||
void BuildSettingsLayout(ImGuiID dockspace_id);
|
||||
|
||||
// Track which layouts have been initialized
|
||||
std::unordered_map<EditorType, bool> layouts_initialized_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_UI_LAYOUT_MANAGER_H_
|
||||
|
||||
@@ -107,6 +107,7 @@ void UICoordinator::DrawAllUI() {
|
||||
// Draw UI windows and dialogs
|
||||
// Session dialogs are drawn by SessionCoordinator separately to avoid duplication
|
||||
DrawCommandPalette(); // Ctrl+Shift+P
|
||||
DrawGlobalSearch(); // Ctrl+Shift+K
|
||||
DrawLayoutPresets(); // Layout preset dialogs
|
||||
DrawWelcomeScreen(); // Welcome screen
|
||||
DrawProjectHelp(); // Project help
|
||||
@@ -304,6 +305,10 @@ void UICoordinator::DrawWelcomeScreen() {
|
||||
// Draw the welcome screen
|
||||
LOG_INFO("UICoordinator", "Rendering welcome screen window");
|
||||
|
||||
// Reset first show flag to override ImGui ini state
|
||||
// This ensures the window appears even if imgui.ini has it hidden
|
||||
welcome_screen_->ResetFirstShow();
|
||||
|
||||
// Update recent projects before showing
|
||||
welcome_screen_->RefreshRecentProjects();
|
||||
|
||||
@@ -670,6 +675,153 @@ void UICoordinator::DrawCommandPalette() {
|
||||
}
|
||||
}
|
||||
|
||||
void UICoordinator::DrawGlobalSearch() {
|
||||
if (!show_global_search_) return;
|
||||
|
||||
using namespace ImGui;
|
||||
auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
||||
|
||||
SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
|
||||
|
||||
bool show_search = true;
|
||||
if (Begin(absl::StrFormat("%s Global Search", ICON_MD_SEARCH).c_str(),
|
||||
&show_search, ImGuiWindowFlags_NoCollapse)) {
|
||||
|
||||
// Search input with focus management
|
||||
SetNextItemWidth(-100);
|
||||
if (IsWindowAppearing()) {
|
||||
SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
bool input_changed = InputTextWithHint(
|
||||
"##global_search_query",
|
||||
absl::StrFormat("%s Search ROM data, cards, editors, resources...", ICON_MD_SEARCH).c_str(),
|
||||
global_search_query_, IM_ARRAYSIZE(global_search_query_));
|
||||
|
||||
SameLine();
|
||||
if (Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
|
||||
global_search_query_[0] = '\0';
|
||||
input_changed = true;
|
||||
}
|
||||
|
||||
Separator();
|
||||
|
||||
// Search results organized by category
|
||||
if (BeginTabBar("SearchCategories")) {
|
||||
// TODO: [EditorManagerRefactor] Implement actual ROM data searching
|
||||
// This should search through:
|
||||
// - Editor cards (all registered cards across all editors)
|
||||
// - ROM resources (palettes, graphics, sprites, etc.)
|
||||
// - Text strings (messages, dialogue)
|
||||
// - Map names, room names, sprite names
|
||||
// - Memory addresses and labels
|
||||
|
||||
if (BeginTabItem(absl::StrFormat("%s All Results", ICON_MD_LIST).c_str())) {
|
||||
if (global_search_query_[0] != '\0') {
|
||||
// Search through editor cards
|
||||
TextColored(gui::ConvertColorToImVec4(theme.info),
|
||||
"%s Editor Cards", ICON_MD_DASHBOARD);
|
||||
Separator();
|
||||
|
||||
// Get current session ID from editor manager
|
||||
size_t current_session_id = 0;
|
||||
if (editor_manager_) {
|
||||
current_session_id = editor_manager_->GetCurrentSessionId();
|
||||
}
|
||||
|
||||
// Get all cards in current session
|
||||
auto card_ids = card_registry_.GetCardsInSession(current_session_id);
|
||||
bool found_cards = false;
|
||||
|
||||
for (const auto& card_id : card_ids) {
|
||||
const auto* card_info = card_registry_.GetCardInfo(current_session_id, card_id);
|
||||
if (!card_info) continue;
|
||||
|
||||
std::string search_lower = global_search_query_;
|
||||
std::string card_lower = card_info->display_name;
|
||||
std::transform(search_lower.begin(), search_lower.end(),
|
||||
search_lower.begin(), ::tolower);
|
||||
std::transform(card_lower.begin(), card_lower.end(),
|
||||
card_lower.begin(), ::tolower);
|
||||
|
||||
if (card_lower.find(search_lower) != std::string::npos) {
|
||||
if (Selectable(absl::StrFormat("%s %s - %s",
|
||||
card_info->icon.c_str(),
|
||||
card_info->display_name.c_str(),
|
||||
card_info->category.c_str()).c_str())) {
|
||||
// Show the card when selected
|
||||
card_registry_.ShowCard(current_session_id, card_id);
|
||||
show_global_search_ = false;
|
||||
}
|
||||
if (IsItemHovered()) {
|
||||
BeginTooltip();
|
||||
Text("Category: %s", card_info->category.c_str());
|
||||
Text("Shortcut: %s", card_info->shortcut_hint.c_str());
|
||||
Text("Click to open");
|
||||
EndTooltip();
|
||||
}
|
||||
found_cards = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_cards) {
|
||||
PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(theme.text_disabled));
|
||||
Text("No cards found matching '%s'", global_search_query_);
|
||||
PopStyleColor();
|
||||
}
|
||||
|
||||
Spacing();
|
||||
Spacing();
|
||||
|
||||
// TODO: [EditorManagerRefactor] Add more search categories:
|
||||
// - ROM Resources (palettes, graphics, sprites)
|
||||
// - Text/Messages
|
||||
// - Map/Room names
|
||||
// - Memory addresses
|
||||
} else {
|
||||
PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(theme.text_disabled));
|
||||
Text("%s Enter search query to find ROM data, cards, and resources",
|
||||
ICON_MD_INFO);
|
||||
PopStyleColor();
|
||||
}
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem(absl::StrFormat("%s Cards", ICON_MD_DASHBOARD).c_str())) {
|
||||
Text("Card-specific search coming soon...");
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem(absl::StrFormat("%s ROM Data", ICON_MD_STORAGE).c_str())) {
|
||||
Text("ROM data search coming soon...");
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem(absl::StrFormat("%s Text", ICON_MD_TEXT_FIELDS).c_str())) {
|
||||
Text("Text search coming soon...");
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
EndTabBar();
|
||||
}
|
||||
|
||||
// Status bar
|
||||
Separator();
|
||||
Text("%s Global Search", ICON_MD_INFO);
|
||||
SameLine();
|
||||
PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(theme.text_disabled));
|
||||
Text("| Currently searches: Editor Cards | More categories coming soon");
|
||||
PopStyleColor();
|
||||
}
|
||||
End();
|
||||
|
||||
// Update visibility state
|
||||
if (!show_search) {
|
||||
show_global_search_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -160,6 +160,15 @@ bool WelcomeScreen::Show(bool* p_open) {
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
|
||||
|
||||
// CRITICAL: Override ImGui's saved window state from imgui.ini
|
||||
// Without this, ImGui will restore the last saved state (hidden/collapsed)
|
||||
// even when our logic says the window should be visible
|
||||
if (first_show_attempt_) {
|
||||
ImGui::SetNextWindowCollapsed(false); // Force window to be expanded
|
||||
ImGui::SetNextWindowFocus(); // Bring window to front
|
||||
first_show_attempt_ = false;
|
||||
}
|
||||
|
||||
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove;
|
||||
|
||||
@@ -79,6 +79,11 @@ class WelcomeScreen {
|
||||
*/
|
||||
void MarkManuallyClosed() { manually_closed_ = true; }
|
||||
|
||||
/**
|
||||
* @brief Reset first show flag (for testing/forcing display)
|
||||
*/
|
||||
void ResetFirstShow() { first_show_attempt_ = true; }
|
||||
|
||||
private:
|
||||
void DrawHeader();
|
||||
void DrawQuickActions();
|
||||
@@ -90,6 +95,7 @@ class WelcomeScreen {
|
||||
|
||||
std::vector<RecentProject> recent_projects_;
|
||||
bool manually_closed_ = false;
|
||||
bool first_show_attempt_ = true; // Override ImGui ini state on first display
|
||||
|
||||
// Callbacks
|
||||
std::function<void()> open_rom_callback_;
|
||||
|
||||
Reference in New Issue
Block a user