From 94651959561d3c963d30742e82b21a2436c71b98 Mon Sep 17 00:00:00 2001 From: scawful Date: Thu, 9 Oct 2025 09:08:17 -0400 Subject: [PATCH] feat: Introduce card management system for dungeon editor - Added EditorCardManager to handle registration, visibility, and management of editor cards. - Integrated card shortcuts and dynamic menu sections for improved user experience in the dungeon editor. - Registered multiple dungeon-related cards with associated shortcuts for quick access. - Enhanced DungeonEditorV2 to utilize the new card management system, allowing independent control of editor cards. - Updated UI components to support card-based editors, improving layout and usability. --- src/app/editor/dungeon/dungeon_editor.cc | 30 - src/app/editor/dungeon/dungeon_editor.h | 2 - src/app/editor/dungeon/dungeon_editor_v2.cc | 194 ++++++- src/app/editor/dungeon/dungeon_editor_v2.h | 4 + src/app/editor/editor_manager.cc | 126 +++-- src/app/editor/editor_manager.h | 1 + src/app/gui/editor_card_manager.cc | 577 ++++++++++++++++++++ src/app/gui/editor_card_manager.h | 198 +++++++ src/app/gui/editor_layout.cc | 57 +- src/app/gui/editor_layout.h | 12 + src/app/gui/gui_library.cmake | 1 + 11 files changed, 1116 insertions(+), 86 deletions(-) create mode 100644 src/app/gui/editor_card_manager.cc create mode 100644 src/app/gui/editor_card_manager.h diff --git a/src/app/editor/dungeon/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc index 12301762..fc1e42a2 100644 --- a/src/app/editor/dungeon/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -755,36 +755,6 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200); } - // TEMPORARY: Render all objects as primitives until proper rendering is fixed - if (current_palette_id_ < current_palette_group_.size()) { - auto room_palette = current_palette_group_[current_palette_id_]; - - // Render regular objects with primitive fallback - for (const auto& object : room.GetTileObjects()) { - renderer_.RenderObjectInCanvas(object, room_palette); - } - - // Test manual rendering for debugging - if (room.GetTileObjects().size() > 0) { - const auto& test_object = room.GetTileObjects()[0]; - int canvas_x = test_object.x_ * 8; - int canvas_y = test_object.y_ * 8; - - printf("[DungeonEditor] Testing manual render for object 0x%04X at (%d,%d)\n", - test_object.id_, canvas_x, canvas_y); - printf("[DungeonEditor] Object tiles count: %zu\n", test_object.tiles().size()); - - manual_renderer_.RenderSimpleBlock(test_object.id_, canvas_x, canvas_y, room_palette); - - // Debug graphics sheets - manual_renderer_.DebugGraphicsSheet(0); - manual_renderer_.DebugGraphicsSheet(1); - } - - // Test palette rendering - manual_renderer_.TestPaletteRendering(400, 100); - } - // Render sprites as simple 16x16 squares with labels // (Sprites are not part of the background buffers) renderer_.RenderSprites(rooms_[room_id]); diff --git a/src/app/editor/dungeon/dungeon_editor.h b/src/app/editor/dungeon/dungeon_editor.h index f8810a4a..8ac084f4 100644 --- a/src/app/editor/dungeon/dungeon_editor.h +++ b/src/app/editor/dungeon/dungeon_editor.h @@ -35,7 +35,6 @@ #include "dungeon_renderer.h" #include "dungeon_room_loader.h" #include "dungeon_usage_tracker.h" -#include "manual_object_renderer.h" namespace yaze { namespace editor { @@ -201,7 +200,6 @@ class DungeonEditor : public Editor { DungeonRenderer renderer_; DungeonRoomLoader room_loader_; DungeonUsageTracker usage_tracker_; - ManualObjectRenderer manual_renderer_; absl::Status status_; diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index 2771b893..0b9c11ae 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -72,6 +72,81 @@ absl::Status DungeonEditorV2::Load() { &canvas_viewer_.canvas(), rom_); printf("[DungeonEditorV2] Manual renderer initialized for debugging\n"); + // Register all cards with the card manager for unified control + auto& card_manager = gui::EditorCardManager::Get(); + + card_manager.RegisterCard({ + .card_id = "dungeon.control_panel", + .display_name = "Dungeon Controls", + .icon = ICON_MD_CASTLE, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+D", + .visibility_flag = &show_control_panel_, + .priority = 10 + }); + + card_manager.RegisterCard({ + .card_id = "dungeon.room_selector", + .display_name = "Room Selector", + .icon = ICON_MD_LIST, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+R", + .visibility_flag = &show_room_selector_, + .priority = 20 + }); + + card_manager.RegisterCard({ + .card_id = "dungeon.room_matrix", + .display_name = "Room Matrix", + .icon = ICON_MD_GRID_VIEW, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+M", + .visibility_flag = &show_room_matrix_, + .priority = 30 + }); + + card_manager.RegisterCard({ + .card_id = "dungeon.entrances", + .display_name = "Entrances", + .icon = ICON_MD_DOOR_FRONT, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+E", + .visibility_flag = &show_entrances_list_, + .priority = 40 + }); + + card_manager.RegisterCard({ + .card_id = "dungeon.room_graphics", + .display_name = "Room Graphics", + .icon = ICON_MD_IMAGE, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+G", + .visibility_flag = &show_room_graphics_, + .priority = 50 + }); + + card_manager.RegisterCard({ + .card_id = "dungeon.object_editor", + .display_name = "Object Editor", + .icon = ICON_MD_CONSTRUCTION, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+O", + .visibility_flag = &show_object_editor_, + .priority = 60 + }); + + card_manager.RegisterCard({ + .card_id = "dungeon.palette_editor", + .display_name = "Palette Editor", + .icon = ICON_MD_PALETTE, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+P", + .visibility_flag = &show_palette_editor_, + .priority = 70 + }); + + printf("[DungeonEditorV2] Registered 7 cards with EditorCardManager\n"); + // Wire palette changes to trigger room re-renders palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) { // Re-render all active rooms when palette changes @@ -89,25 +164,46 @@ absl::Status DungeonEditorV2::Load() { absl::Status DungeonEditorV2::Update() { if (!is_loaded_) { - // Show minimal loading message in parent window - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Dungeon Editor Loading..."); - ImGui::TextWrapped("Independent editor cards will appear once ROM data is loaded."); + // CARD-BASED EDITOR: Create a minimal loading card + gui::EditorCard loading_card("Dungeon Editor Loading", ICON_MD_CASTLE); + loading_card.SetDefaultSize(400, 200); + if (loading_card.Begin()) { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Loading dungeon data..."); + ImGui::TextWrapped("Independent editor cards will appear once ROM data is loaded."); + } + loading_card.End(); return absl::OkStatus(); } - // Minimize parent window content - just show a toolbar - DrawToolset(); + // CARD-BASED EDITOR: All windows are independent top-level cards + // No parent wrapper - this allows closing control panel without affecting rooms - ImGui::Separator(); - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "Editor cards are independent windows - dock them anywhere!"); - ImGui::TextWrapped( - "Room Selector, Object Selector, and Room cards can be freely arranged. " - "This parent window can be minimized or closed."); + // Optional control panel (can be hidden/minimized) + if (show_control_panel_) { + DrawControlPanel(); + } else if (control_panel_minimized_) { + // Draw floating icon button to reopen + ImGui::SetNextWindowPos(ImVec2(10, 100)); + ImGui::SetNextWindowSize(ImVec2(50, 50)); + ImGuiWindowFlags icon_flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoDocking; + + if (ImGui::Begin("##DungeonControlIcon", nullptr, icon_flags)) { + if (ImGui::Button(ICON_MD_CASTLE, ImVec2(40, 40))) { + show_control_panel_ = true; + control_panel_minimized_ = false; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open Dungeon Controls"); + } + } + ImGui::End(); + } - // Render all independent cards (these create their own top-level windows) - // NOTE: Emulator preview is now integrated into ObjectEditorCard - // object_emulator_preview_.Render(); // Removed - causing performance issues + // Render all independent cards (these are ALL top-level windows now) DrawLayout(); return absl::OkStatus(); @@ -169,35 +265,94 @@ void DungeonEditorV2::DrawToolset() { toolbar.End(); } +void DungeonEditorV2::DrawControlPanel() { + // Small, collapsible control panel for dungeon editor + ImGui::SetNextWindowSize(ImVec2(250, 200), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver); + + ImGuiWindowFlags flags = ImGuiWindowFlags_None; + + if (ImGui::Begin(ICON_MD_CASTLE " Dungeon Controls", &show_control_panel_, flags)) { + DrawToolset(); + + ImGui::Separator(); + ImGui::Text("Quick Toggles:"); + + // Checkbox grid for quick toggles + if (ImGui::BeginTable("##QuickToggles", 2, ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Rooms", &show_room_selector_); + + ImGui::TableNextColumn(); + ImGui::Checkbox("Matrix", &show_room_matrix_); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Entrances", &show_entrances_list_); + + ImGui::TableNextColumn(); + ImGui::Checkbox("Graphics", &show_room_graphics_); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("Objects", &show_object_editor_); + + ImGui::TableNextColumn(); + ImGui::Checkbox("Palette", &show_palette_editor_); + + ImGui::EndTable(); + } + + ImGui::Separator(); + + // Minimize button + if (ImGui::SmallButton(ICON_MD_MINIMIZE " Minimize to Icon")) { + control_panel_minimized_ = true; + show_control_panel_ = false; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Collapse to floating icon. Rooms stay open."); + } + } + ImGui::End(); +} + void DungeonEditorV2::DrawLayout() { // NO TABLE LAYOUT - All independent dockable EditorCards + // All cards check their visibility flags and can be closed with X button // 1. Room Selector Card (independent, dockable) if (show_room_selector_) { DrawRoomsListCard(); + // Card handles its own closing via &show_room_selector_ in constructor } // 2. Room Matrix Card (visual navigation) if (show_room_matrix_) { DrawRoomMatrixCard(); + // Card handles its own closing via &show_room_matrix_ in constructor } // 3. Entrances List Card if (show_entrances_list_) { DrawEntrancesListCard(); + // Card handles its own closing via &show_entrances_list_ in constructor } - // 3b. Room Graphics Card + // 4. Room Graphics Card if (show_room_graphics_) { DrawRoomGraphicsCard(); + // Card handles its own closing via &show_room_graphics_ in constructor } - // 4. Unified Object Editor Card + // 5. Unified Object Editor Card if (show_object_editor_ && object_editor_card_) { object_editor_card_->Draw(&show_object_editor_); + // ObjectEditorCard handles closing via p_open parameter } - // 5. Palette Editor Card (independent, dockable) + // 6. Palette Editor Card (independent, dockable) if (show_palette_editor_) { gui::EditorCard palette_card( MakeCardTitle("Palette Editor").c_str(), @@ -206,6 +361,7 @@ void DungeonEditorV2::DrawLayout() { palette_editor_.Draw(); } palette_card.End(); + // Card handles its own closing via &show_palette_editor_ in constructor } // 6. Active Room Cards (independent, dockable, tracked for jump-to) @@ -238,9 +394,11 @@ void DungeonEditorV2::DrawLayout() { auto& room_card = room_cards_[room_id]; - // CRITICAL: Use docking class BEFORE Begin() to make rooms tab together + // CRITICAL: Use docking class BEFORE Begin() to make rooms dock together + // This creates a separate docking space for all room cards ImGui::SetNextWindowClass(&room_window_class_); + // Make room cards fully dockable and independent if (room_card->Begin(&open)) { DrawRoomTab(room_id); } diff --git a/src/app/editor/dungeon/dungeon_editor_v2.h b/src/app/editor/dungeon/dungeon_editor_v2.h index 0ccaaa33..eb2878b9 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.h +++ b/src/app/editor/dungeon/dungeon_editor_v2.h @@ -15,6 +15,7 @@ #include "dungeon_room_loader.h" #include "object_editor_card.h" #include "manual_object_renderer.h" +#include "app/gui/editor_card_manager.h" #include "app/zelda3/dungeon/room.h" #include "app/zelda3/dungeon/room_entrance.h" #include "app/gui/editor_layout.h" @@ -97,6 +98,7 @@ class DungeonEditorV2 : public Editor { void DrawRoomsListCard(); void DrawEntrancesListCard(); void DrawRoomGraphicsCard(); + void DrawControlPanel(); // Texture processing (critical for rendering) void ProcessDeferredTextures(); @@ -125,6 +127,8 @@ class DungeonEditorV2 : public Editor { bool show_room_graphics_ = false; // Room graphics card bool show_object_editor_ = true; // Unified object editor card bool show_palette_editor_ = true; + bool show_control_panel_ = true; // Optional control panel + bool control_panel_minimized_ = false; // Palette management gfx::SnesPalette current_palette_; diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 9867bec7..1fb9cb84 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -610,6 +610,34 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file context_.shortcut_manager.RegisterShortcut( "Editor Selection", {ImGuiKey_E, ImGuiMod_Ctrl}, [this]() { show_editor_selection_ = true; }); + + // Card Browser shortcut + context_.shortcut_manager.RegisterShortcut( + "Card Browser", {ImGuiKey_B, ImGuiMod_Ctrl, ImGuiMod_Shift}, + [this]() { show_card_browser_ = true; }); + + // Dungeon Card shortcuts + context_.shortcut_manager.RegisterShortcut( + "Toggle Dungeon Controls", {ImGuiKey_D, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.control_panel"); }); + context_.shortcut_manager.RegisterShortcut( + "Toggle Room Selector", {ImGuiKey_R, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.room_selector"); }); + context_.shortcut_manager.RegisterShortcut( + "Toggle Room Matrix", {ImGuiKey_M, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.room_matrix"); }); + context_.shortcut_manager.RegisterShortcut( + "Toggle Dungeon Entrances", {ImGuiKey_E, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.entrances"); }); + context_.shortcut_manager.RegisterShortcut( + "Toggle Room Graphics", {ImGuiKey_G, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.room_graphics"); }); + context_.shortcut_manager.RegisterShortcut( + "Toggle Object Editor", {ImGuiKey_O, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.object_editor"); }); + context_.shortcut_manager.RegisterShortcut( + "Toggle Dungeon Palette", {ImGuiKey_P, ImGuiMod_Ctrl, ImGuiMod_Shift}, + []() { gui::EditorCardManager::Get().ToggleCard("dungeon.palette_editor"); }); #ifdef YAZE_WITH_GRPC // Agent Editor shortcut @@ -672,6 +700,11 @@ absl::Status EditorManager::Update() { if (show_editor_selection_) { editor_selection_dialog_.Show(&show_editor_selection_); } + + // Draw card browser + if (show_card_browser_) { + gui::EditorCardManager::Get().DrawCardBrowser(&show_card_browser_); + } #ifdef YAZE_WITH_GRPC // Update agent editor dashboard @@ -771,20 +804,13 @@ absl::Status EditorManager::Update() { } } - // Generate unique window titles and IDs for multi-session support - std::string window_title = - GenerateUniqueEditorTitle(editor->type(), session_idx); + // CARD-BASED EDITORS: Don't wrap in Begin/End, they manage own windows + bool is_card_based_editor = (editor->type() == EditorType::kDungeon); + // TODO: Add EditorType::kGraphics, EditorType::kPalette when converted - // Note: PushID removed - window title provides sufficient uniqueness - // and PushID was causing ID stack corruption issues - - // Set window to maximize on first open (use FirstUseEver instead of IsWindowAppearing check) - ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize, ImGuiCond_FirstUseEver); - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->WorkPos, ImGuiCond_FirstUseEver); - - if (ImGui::Begin(window_title.c_str(), editor->active(), - ImGuiWindowFlags_None)) { // Allow full docking - // Temporarily switch context for this editor's update + if (is_card_based_editor) { + // Card-based editors create their own top-level windows + // No parent wrapper needed - this allows independent docking Rom* prev_rom = current_rom_; EditorSet* prev_editor_set = current_editor_set_; size_t prev_session_id = context_.session_id; @@ -792,29 +818,13 @@ absl::Status EditorManager::Update() { current_rom_ = &session.rom; current_editor_set_ = &session.editors; current_editor_ = editor; - context_.session_id = session_idx; // Set session ID for child panels + context_.session_id = session_idx; status_ = editor->Update(); // Route editor errors to toast manager if (!status_.ok()) { - std::string editor_name = - "Editor"; // Get actual editor name if available - if (editor == &session.editors.overworld_editor_) - editor_name = "Overworld Editor"; - else if (editor == &session.editors.dungeon_editor_) - editor_name = "Dungeon Editor"; - else if (editor == &session.editors.sprite_editor_) - editor_name = "Sprite Editor"; - else if (editor == &session.editors.graphics_editor_) - editor_name = "Graphics Editor"; - else if (editor == &session.editors.music_editor_) - editor_name = "Music Editor"; - else if (editor == &session.editors.palette_editor_) - editor_name = "Palette Editor"; - else if (editor == &session.editors.screen_editor_) - editor_name = "Screen Editor"; - + std::string editor_name = GetEditorName(editor->type()); toast_manager_.Show( absl::StrFormat("%s Error: %s", editor_name, status_.message()), editor::ToastType::kError, 8.0f); @@ -823,10 +833,46 @@ absl::Status EditorManager::Update() { // Restore context current_rom_ = prev_rom; current_editor_set_ = prev_editor_set; - context_.session_id = prev_session_id; // Restore previous session ID + context_.session_id = prev_session_id; + + } else { + // TRADITIONAL EDITORS: Wrap in Begin/End + std::string window_title = + GenerateUniqueEditorTitle(editor->type(), session_idx); + + // Set window to maximize on first open + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->WorkPos, ImGuiCond_FirstUseEver); + + if (ImGui::Begin(window_title.c_str(), editor->active(), + ImGuiWindowFlags_None)) { // Allow full docking + // Temporarily switch context for this editor's update + Rom* prev_rom = current_rom_; + EditorSet* prev_editor_set = current_editor_set_; + size_t prev_session_id = context_.session_id; + + current_rom_ = &session.rom; + current_editor_set_ = &session.editors; + current_editor_ = editor; + context_.session_id = session_idx; + + status_ = editor->Update(); + + // Route editor errors to toast manager + if (!status_.ok()) { + std::string editor_name = GetEditorName(editor->type()); + toast_manager_.Show( + absl::StrFormat("%s Error: %s", editor_name, status_.message()), + editor::ToastType::kError, 8.0f); + } + + // Restore context + current_rom_ = prev_rom; + current_editor_set_ = prev_editor_set; + context_.session_id = prev_session_id; + } + ImGui::End(); } - ImGui::End(); - // PopID removed to match PushID removal above } } } @@ -962,7 +1008,7 @@ void EditorManager::BuildModernMenu() { [this]() { show_global_search_ = true; }, "Ctrl+Shift+F") .EndMenu(); - // View Menu - editors only + // View Menu - editors and cards menu_builder_.BeginMenu("View") .Item("Editor Selection", ICON_MD_DASHBOARD, [this]() { show_editor_selection_ = true; }, "Ctrl+E") @@ -993,6 +1039,16 @@ void EditorManager::BuildModernMenu() { .Item("Proposal Drawer", ICON_MD_PREVIEW, [this]() { proposal_drawer_.Toggle(); }, "Ctrl+P") #endif + .Separator(); + + // Dynamic card menu sections (from EditorCardManager) + auto& card_manager = gui::EditorCardManager::Get(); + card_manager.DrawViewMenuAll(); + + menu_builder_ + .Separator() + .Item("Card Browser", ICON_MD_DASHBOARD, + [this]() { show_card_browser_ = true; }, "Ctrl+Shift+B") .Separator() .Item("Welcome Screen", ICON_MD_HOME, [this]() { show_welcome_screen_ = true; }) diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index c5ce9d35..5406915b 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -189,6 +189,7 @@ class EditorManager { bool show_session_rename_dialog_ = false; bool show_welcome_screen_ = false; bool welcome_screen_manually_closed_ = false; + bool show_card_browser_ = false; size_t session_to_rename_ = 0; char session_rename_buffer_[256] = {}; diff --git a/src/app/gui/editor_card_manager.cc b/src/app/gui/editor_card_manager.cc new file mode 100644 index 00000000..d93e996f --- /dev/null +++ b/src/app/gui/editor_card_manager.cc @@ -0,0 +1,577 @@ +#include "editor_card_manager.h" + +#include +#include + +#include "absl/strings/str_format.h" +#include "app/gui/icons.h" +#include "imgui/imgui.h" +#include "util/file_util.h" + +namespace yaze { +namespace gui { + +EditorCardManager& EditorCardManager::Get() { + static EditorCardManager instance; + return instance; +} + +void EditorCardManager::RegisterCard(const CardInfo& info) { + if (info.card_id.empty()) { + printf("[EditorCardManager] Warning: Attempted to register card with empty ID\n"); + return; + } + + cards_[info.card_id] = info; + printf("[EditorCardManager] Registered card: %s (%s)\n", + info.card_id.c_str(), info.display_name.c_str()); +} + +void EditorCardManager::UnregisterCard(const std::string& card_id) { + auto it = cards_.find(card_id); + if (it != cards_.end()) { + printf("[EditorCardManager] Unregistered card: %s\n", card_id.c_str()); + cards_.erase(it); + } +} + +void EditorCardManager::ClearAllCards() { + printf("[EditorCardManager] Clearing all %zu registered cards\n", cards_.size()); + cards_.clear(); +} + +bool EditorCardManager::ShowCard(const std::string& card_id) { + auto it = cards_.find(card_id); + if (it == cards_.end()) { + return false; + } + + if (it->second.visibility_flag) { + *it->second.visibility_flag = true; + + if (it->second.on_show) { + it->second.on_show(); + } + + return true; + } + + return false; +} + +bool EditorCardManager::HideCard(const std::string& card_id) { + auto it = cards_.find(card_id); + if (it == cards_.end()) { + return false; + } + + if (it->second.visibility_flag) { + *it->second.visibility_flag = false; + + if (it->second.on_hide) { + it->second.on_hide(); + } + + return true; + } + + return false; +} + +bool EditorCardManager::ToggleCard(const std::string& card_id) { + auto it = cards_.find(card_id); + if (it == cards_.end()) { + return false; + } + + if (it->second.visibility_flag) { + bool new_state = !(*it->second.visibility_flag); + *it->second.visibility_flag = new_state; + + if (new_state && it->second.on_show) { + it->second.on_show(); + } else if (!new_state && it->second.on_hide) { + it->second.on_hide(); + } + + return true; + } + + return false; +} + +bool EditorCardManager::IsCardVisible(const std::string& card_id) const { + auto it = cards_.find(card_id); + if (it != cards_.end() && it->second.visibility_flag) { + return *it->second.visibility_flag; + } + return false; +} + +void EditorCardManager::ShowAllCardsInCategory(const std::string& category) { + for (auto& [id, info] : cards_) { + if (info.category == category && info.visibility_flag) { + *info.visibility_flag = true; + if (info.on_show) info.on_show(); + } + } +} + +void EditorCardManager::HideAllCardsInCategory(const std::string& category) { + for (auto& [id, info] : cards_) { + if (info.category == category && info.visibility_flag) { + *info.visibility_flag = false; + if (info.on_hide) info.on_hide(); + } + } +} + +void EditorCardManager::ShowOnlyCard(const std::string& card_id) { + auto target = cards_.find(card_id); + if (target == cards_.end()) { + return; + } + + std::string category = target->second.category; + + // Hide all cards in the same category + for (auto& [id, info] : cards_) { + if (info.category == category && info.visibility_flag) { + *info.visibility_flag = (id == card_id); + + if (id == card_id && info.on_show) { + info.on_show(); + } else if (id != card_id && info.on_hide) { + info.on_hide(); + } + } + } +} + +std::vector EditorCardManager::GetCardsInCategory(const std::string& category) const { + std::vector result; + + for (const auto& [id, info] : cards_) { + if (info.category == category) { + result.push_back(info); + } + } + + // Sort by priority + std::sort(result.begin(), result.end(), + [](const CardInfo& a, const CardInfo& b) { + return a.priority < b.priority; + }); + + return result; +} + +std::vector EditorCardManager::GetAllCategories() const { + std::vector categories; + + for (const auto& [id, info] : cards_) { + if (std::find(categories.begin(), categories.end(), info.category) == categories.end()) { + categories.push_back(info.category); + } + } + + std::sort(categories.begin(), categories.end()); + return categories; +} + +const CardInfo* EditorCardManager::GetCardInfo(const std::string& card_id) const { + auto it = cards_.find(card_id); + return (it != cards_.end()) ? &it->second : nullptr; +} + +void EditorCardManager::DrawViewMenuSection(const std::string& category) { + auto cards_in_category = GetCardsInCategory(category); + + if (cards_in_category.empty()) { + ImGui::MenuItem("(No cards registered)", nullptr, false, false); + return; + } + + for (const auto& info : cards_in_category) { + if (!info.visibility_flag) continue; + + std::string label = info.icon.empty() + ? info.display_name + : info.icon + " " + info.display_name; + + bool visible = *info.visibility_flag; + + if (ImGui::MenuItem(label.c_str(), + info.shortcut_hint.empty() ? nullptr : info.shortcut_hint.c_str(), + visible)) { + ToggleCard(info.card_id); + } + } +} + +void EditorCardManager::DrawViewMenuAll() { + auto categories = GetAllCategories(); + + if (categories.empty()) { + ImGui::TextDisabled("No cards registered"); + return; + } + + for (const auto& category : categories) { + if (ImGui::BeginMenu(category.c_str())) { + DrawViewMenuSection(category); + ImGui::Separator(); + + // Category-level actions + if (ImGui::MenuItem(absl::StrFormat("%s Show All", ICON_MD_VISIBILITY).c_str())) { + ShowAllCardsInCategory(category); + } + + if (ImGui::MenuItem(absl::StrFormat("%s Hide All", ICON_MD_VISIBILITY_OFF).c_str())) { + HideAllCardsInCategory(category); + } + + ImGui::EndMenu(); + } + } + + ImGui::Separator(); + + // Global actions + if (ImGui::MenuItem(absl::StrFormat("%s Show All Cards", ICON_MD_VISIBILITY).c_str())) { + ShowAll(); + } + + if (ImGui::MenuItem(absl::StrFormat("%s Hide All Cards", ICON_MD_VISIBILITY_OFF).c_str())) { + HideAll(); + } + + ImGui::Separator(); + + if (ImGui::MenuItem(absl::StrFormat("%s Card Browser", ICON_MD_DASHBOARD).c_str(), + "Ctrl+Shift+B")) { + // This will be shown by EditorManager + } +} + +void EditorCardManager::DrawCardBrowser(bool* p_open) { + if (!p_open || !*p_open) return; + + ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (!ImGui::Begin(absl::StrFormat("%s Card Browser", ICON_MD_DASHBOARD).c_str(), + p_open)) { + ImGui::End(); + return; + } + + // Search filter + static char search_filter[256] = ""; + ImGui::SetNextItemWidth(-100); + ImGui::InputTextWithHint("##CardSearch", + absl::StrFormat("%s Search cards...", ICON_MD_SEARCH).c_str(), + search_filter, sizeof(search_filter)); + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) { + search_filter[0] = '\0'; + } + + ImGui::Separator(); + + // Statistics + ImGui::Text("%s Total Cards: %zu | Visible: %zu", + ICON_MD_INFO, GetCardCount(), GetVisibleCardCount()); + + ImGui::Separator(); + + // Category tabs + if (ImGui::BeginTabBar("CardBrowserTabs")) { + + // All Cards tab + if (ImGui::BeginTabItem(absl::StrFormat("%s All", ICON_MD_APPS).c_str())) { + DrawCardBrowserTable(search_filter, ""); + ImGui::EndTabItem(); + } + + // Category tabs + for (const auto& category : GetAllCategories()) { + std::string tab_label = category; + if (ImGui::BeginTabItem(tab_label.c_str())) { + DrawCardBrowserTable(search_filter, category); + ImGui::EndTabItem(); + } + } + + // Presets tab + if (ImGui::BeginTabItem(absl::StrFormat("%s Presets", ICON_MD_BOOKMARK).c_str())) { + DrawPresetsTab(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::End(); +} + +void EditorCardManager::DrawCardBrowserTable(const char* search_filter, + const std::string& category_filter) { + if (ImGui::BeginTable("CardBrowserTable", 4, + ImGuiTableFlags_Borders | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | + ImGuiTableFlags_ScrollY)) { + + ImGui::TableSetupColumn("Card", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Visible", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableHeadersRow(); + + // Collect and sort cards + std::vector display_cards; + for (const auto& [id, info] : cards_) { + // Apply filters + if (!category_filter.empty() && info.category != category_filter) { + continue; + } + + if (search_filter && search_filter[0] != '\0') { + std::string search_lower = search_filter; + std::transform(search_lower.begin(), search_lower.end(), + search_lower.begin(), ::tolower); + + std::string name_lower = info.display_name; + std::transform(name_lower.begin(), name_lower.end(), + name_lower.begin(), ::tolower); + + if (name_lower.find(search_lower) == std::string::npos) { + continue; + } + } + + display_cards.push_back(info); + } + + // Sort by category then priority + std::sort(display_cards.begin(), display_cards.end(), + [](const CardInfo& a, const CardInfo& b) { + if (a.category != b.category) return a.category < b.category; + return a.priority < b.priority; + }); + + // Draw rows + for (const auto& info : display_cards) { + ImGui::PushID(info.card_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + // Card name with icon + std::string label = info.icon.empty() + ? info.display_name + : info.icon + " " + info.display_name; + ImGui::Text("%s", label.c_str()); + + ImGui::TableNextColumn(); + ImGui::TextDisabled("%s", info.category.c_str()); + + ImGui::TableNextColumn(); + if (!info.shortcut_hint.empty()) { + ImGui::TextDisabled("%s", info.shortcut_hint.c_str()); + } + + ImGui::TableNextColumn(); + if (info.visibility_flag) { + bool visible = *info.visibility_flag; + if (ImGui::Checkbox("##Visible", &visible)) { + *info.visibility_flag = visible; + if (visible && info.on_show) { + info.on_show(); + } else if (!visible && info.on_hide) { + info.on_hide(); + } + } + } + + ImGui::PopID(); + } + + ImGui::EndTable(); + } +} + +void EditorCardManager::DrawPresetsTab() { + ImGui::Text("%s Workspace Presets", ICON_MD_BOOKMARK); + ImGui::Separator(); + + // Save current as preset + static char preset_name[256] = ""; + static char preset_desc[512] = ""; + + ImGui::Text("Save Current Layout:"); + ImGui::InputText("Name", preset_name, sizeof(preset_name)); + ImGui::InputText("Description", preset_desc, sizeof(preset_desc)); + + if (ImGui::Button(absl::StrFormat("%s Save Preset", ICON_MD_SAVE).c_str())) { + if (preset_name[0] != '\0') { + SavePreset(preset_name, preset_desc); + preset_name[0] = '\0'; + preset_desc[0] = '\0'; + } + } + + ImGui::Separator(); + ImGui::Text("Saved Presets:"); + + // List presets + auto presets = GetPresets(); + if (presets.empty()) { + ImGui::TextDisabled("No presets saved"); + } else { + for (const auto& preset : presets) { + ImGui::PushID(preset.name.c_str()); + + if (ImGui::Button(absl::StrFormat("%s Load", ICON_MD_FOLDER_OPEN).c_str())) { + LoadPreset(preset.name); + } + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Delete", ICON_MD_DELETE).c_str())) { + DeletePreset(preset.name); + } + + ImGui::SameLine(); + ImGui::Text("%s %s", ICON_MD_BOOKMARK, preset.name.c_str()); + + if (!preset.description.empty()) { + ImGui::SameLine(); + ImGui::TextDisabled("- %s", preset.description.c_str()); + } + + ImGui::SameLine(); + ImGui::TextDisabled("(%zu cards)", preset.visible_cards.size()); + + ImGui::PopID(); + } + } +} + +void EditorCardManager::SavePreset(const std::string& name, const std::string& description) { + WorkspacePreset preset; + preset.name = name; + preset.description = description; + + // Save currently visible cards + for (const auto& [id, info] : cards_) { + if (info.visibility_flag && *info.visibility_flag) { + preset.visible_cards.push_back(id); + } + } + + presets_[name] = preset; + SavePresetsToFile(); + + printf("[EditorCardManager] Saved preset '%s' with %zu cards\n", + name.c_str(), preset.visible_cards.size()); +} + +bool EditorCardManager::LoadPreset(const std::string& name) { + auto it = presets_.find(name); + if (it == presets_.end()) { + return false; + } + + // Hide all cards first + HideAll(); + + // Show cards in preset + for (const auto& card_id : it->second.visible_cards) { + ShowCard(card_id); + } + + printf("[EditorCardManager] Loaded preset '%s' with %zu cards\n", + name.c_str(), it->second.visible_cards.size()); + + return true; +} + +void EditorCardManager::DeletePreset(const std::string& name) { + auto it = presets_.find(name); + if (it != presets_.end()) { + presets_.erase(it); + SavePresetsToFile(); + printf("[EditorCardManager] Deleted preset '%s'\n", name.c_str()); + } +} + +std::vector EditorCardManager::GetPresets() const { + std::vector result; + for (const auto& [name, preset] : presets_) { + result.push_back(preset); + } + return result; +} + +void EditorCardManager::ShowAll() { + for (auto& [id, info] : cards_) { + if (info.visibility_flag) { + *info.visibility_flag = true; + if (info.on_show) info.on_show(); + } + } +} + +void EditorCardManager::HideAll() { + for (auto& [id, info] : cards_) { + if (info.visibility_flag) { + *info.visibility_flag = false; + if (info.on_hide) info.on_hide(); + } + } +} + +void EditorCardManager::ResetToDefaults() { + // Default visibility based on priority + for (auto& [id, info] : cards_) { + if (info.visibility_flag) { + // Show high-priority cards (priority < 50) + *info.visibility_flag = (info.priority < 50); + + if (*info.visibility_flag && info.on_show) { + info.on_show(); + } else if (!*info.visibility_flag && info.on_hide) { + info.on_hide(); + } + } + } +} + +size_t EditorCardManager::GetVisibleCardCount() const { + size_t count = 0; + for (const auto& [id, info] : cards_) { + if (info.visibility_flag && *info.visibility_flag) { + count++; + } + } + return count; +} + +void EditorCardManager::SavePresetsToFile() { + // Save presets to JSON or simple format + // TODO: Implement file I/O + printf("[EditorCardManager] Saving %zu presets to file\n", presets_.size()); +} + +void EditorCardManager::LoadPresetsFromFile() { + // Load presets from file + // TODO: Implement file I/O + printf("[EditorCardManager] Loading presets from file\n"); +} + +} // namespace gui +} // namespace yaze + diff --git a/src/app/gui/editor_card_manager.h b/src/app/gui/editor_card_manager.h new file mode 100644 index 00000000..a6f9f84f --- /dev/null +++ b/src/app/gui/editor_card_manager.h @@ -0,0 +1,198 @@ +#ifndef YAZE_APP_GUI_EDITOR_CARD_MANAGER_H +#define YAZE_APP_GUI_EDITOR_CARD_MANAGER_H + +#include +#include +#include +#include +#include + +#include "imgui/imgui.h" + +namespace yaze { +namespace gui { + +class EditorCard; // Forward declaration + +/** + * @brief Metadata for an editor card + */ +struct CardInfo { + std::string card_id; // Unique identifier (e.g., "dungeon.room_selector") + std::string display_name; // Human-readable name (e.g., "Room Selector") + std::string icon; // Material icon + std::string category; // Category (e.g., "Dungeon", "Graphics", "Palette") + std::string shortcut_hint; // Display hint (e.g., "Ctrl+Shift+R") + bool* visibility_flag; // Pointer to bool controlling visibility + EditorCard* card_instance; // Pointer to actual card (optional) + std::function on_show; // Callback when card is shown + std::function on_hide; // Callback when card is hidden + int priority; // Display priority for menus (lower = higher) +}; + +/** + * @brief Central registry and manager for all editor cards + * + * This singleton provides: + * - Global card registration across all editors + * - View menu integration + * - Keyboard shortcut management + * - Workspace preset system + * - Quick search/filter + * - Programmatic card control without GUI coupling + * + * Design Philosophy: + * - Cards register themselves on creation + * - Manager provides unified interface for visibility control + * - No direct GUI dependency in card logic + * - Supports dynamic card creation/destruction + * + * Usage: + * ```cpp + * // In editor initialization: + * auto& manager = EditorCardManager::Get(); + * manager.RegisterCard({ + * .card_id = "dungeon.room_selector", + * .display_name = "Room Selector", + * .icon = ICON_MD_LIST, + * .category = "Dungeon", + * .visibility_flag = &show_room_selector_, + * .on_show = []() { printf("Room selector opened\n"); } + * }); + * + * // Programmatic control: + * manager.ShowCard("dungeon.room_selector"); + * manager.HideCard("dungeon.room_selector"); + * manager.ToggleCard("dungeon.room_selector"); + * + * // In View menu: + * manager.DrawViewMenuSection("Dungeon"); + * ``` + */ +class EditorCardManager { + public: + static EditorCardManager& Get(); + + // Registration + void RegisterCard(const CardInfo& info); + void UnregisterCard(const std::string& card_id); + void ClearAllCards(); + + // Card control (programmatic, no GUI) + bool ShowCard(const std::string& card_id); + bool HideCard(const std::string& card_id); + bool ToggleCard(const std::string& card_id); + bool IsCardVisible(const std::string& card_id) const; + + // Batch operations + void ShowAllCardsInCategory(const std::string& category); + void HideAllCardsInCategory(const std::string& category); + void ShowOnlyCard(const std::string& card_id); // Hide all others in category + + // Query + std::vector GetCardsInCategory(const std::string& category) const; + std::vector GetAllCategories() const; + const CardInfo* GetCardInfo(const std::string& card_id) const; + + // View menu integration + void DrawViewMenuSection(const std::string& category); + void DrawViewMenuAll(); // Draw all categories as submenus + + // Card browser UI + void DrawCardBrowser(bool* p_open); // Visual card browser/toggler + void DrawCardBrowserTable(const char* search_filter, const std::string& category_filter); + void DrawPresetsTab(); + + // Workspace presets + struct WorkspacePreset { + std::string name; + std::vector visible_cards; // Card IDs + std::string description; + }; + + void SavePreset(const std::string& name, const std::string& description = ""); + bool LoadPreset(const std::string& name); + void DeletePreset(const std::string& name); + std::vector GetPresets() const; + + // Quick actions + void ShowAll(); // Show all registered cards + void HideAll(); // Hide all registered cards + void ResetToDefaults(); // Reset to default visibility state + + // Statistics + size_t GetCardCount() const { return cards_.size(); } + size_t GetVisibleCardCount() const; + + private: + EditorCardManager() = default; + ~EditorCardManager() = default; + EditorCardManager(const EditorCardManager&) = delete; + EditorCardManager& operator=(const EditorCardManager&) = delete; + + std::unordered_map cards_; + std::unordered_map presets_; + + // Helper methods + void SavePresetsToFile(); + void LoadPresetsFromFile(); +}; + +/** + * @brief RAII helper for auto-registering cards + * + * Usage: + * ```cpp + * class MyEditor { + * CardRegistration room_selector_reg_; + * + * MyEditor() { + * room_selector_reg_ = RegisterCard({ + * .card_id = "myeditor.room_selector", + * .display_name = "Room Selector", + * .visibility_flag = &show_room_selector_ + * }); + * } + * }; + * ``` + */ +class CardRegistration { + public: + CardRegistration() = default; + explicit CardRegistration(const std::string& card_id) : card_id_(card_id) {} + + ~CardRegistration() { + if (!card_id_.empty()) { + EditorCardManager::Get().UnregisterCard(card_id_); + } + } + + // No copy, allow move + CardRegistration(const CardRegistration&) = delete; + CardRegistration& operator=(const CardRegistration&) = delete; + CardRegistration(CardRegistration&& other) noexcept : card_id_(std::move(other.card_id_)) { + other.card_id_.clear(); + } + CardRegistration& operator=(CardRegistration&& other) noexcept { + if (this != &other) { + card_id_ = std::move(other.card_id_); + other.card_id_.clear(); + } + return *this; + } + + private: + std::string card_id_; +}; + +// Convenience function for registration +inline CardRegistration RegisterCard(const CardInfo& info) { + EditorCardManager::Get().RegisterCard(info); + return CardRegistration(info.card_id); +} + +} // namespace gui +} // namespace yaze + +#endif // YAZE_APP_GUI_EDITOR_CARD_MANAGER_H + diff --git a/src/app/gui/editor_layout.cc b/src/app/gui/editor_layout.cc index 403c912b..9453e5db 100644 --- a/src/app/gui/editor_layout.cc +++ b/src/app/gui/editor_layout.cc @@ -248,8 +248,25 @@ void EditorCard::SetPosition(Position pos) { } bool EditorCard::Begin(bool* p_open) { + // Handle icon-collapsed state + if (icon_collapsible_ && collapsed_to_icon_) { + DrawFloatingIconButton(); + return false; + } + ImGuiWindowFlags flags = ImGuiWindowFlags_None; + // Apply headless mode + if (headless_) { + flags |= ImGuiWindowFlags_NoTitleBar; + flags |= ImGuiWindowFlags_NoCollapse; + } + + // Control docking + if (!docking_allowed_) { + flags |= ImGuiWindowFlags_NoDocking; + } + // Set initial position based on position enum if (first_draw_) { float display_width = ImGui::GetIO().DisplaySize.x; @@ -290,7 +307,13 @@ bool EditorCard::Begin(bool* p_open) { ImGui::PushStyleColor(ImGuiCol_TitleBg, GetThemeColor(ImGuiCol_TitleBg)); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, GetAccentColor()); - bool visible = ImGui::Begin(window_title.c_str(), p_open, flags); + // Use p_open parameter if provided, otherwise use stored p_open_ + bool* actual_p_open = p_open ? p_open : p_open_; + + // If closable is false, don't pass p_open (removes X button) + bool visible = ImGui::Begin(window_title.c_str(), + closable_ ? actual_p_open : nullptr, + flags); // Register card window for test automation if (ImGui::GetCurrentWindow() && ImGui::GetCurrentWindow()->ID != 0) { @@ -318,6 +341,38 @@ void EditorCard::Focus() { focused_ = true; } +void EditorCard::DrawFloatingIconButton() { + // Draw a small floating button with the icon + ImGui::SetNextWindowPos(saved_icon_pos_, ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(50, 50)); + + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoCollapse; + + std::string icon_window_name = window_name_ + "##IconCollapsed"; + + if (ImGui::Begin(icon_window_name.c_str(), nullptr, flags)) { + // Draw icon button + if (ImGui::Button(icon_.c_str(), ImVec2(40, 40))) { + collapsed_to_icon_ = false; // Expand back to full window + } + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Expand %s", title_.c_str()); + } + + // Allow dragging the icon + if (ImGui::IsWindowHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; + saved_icon_pos_.x += mouse_delta.x; + saved_icon_pos_.y += mouse_delta.y; + } + } + ImGui::End(); +} + // ============================================================================ // EditorLayout Implementation // ============================================================================ diff --git a/src/app/gui/editor_layout.h b/src/app/gui/editor_layout.h index f322a995..c5362df1 100644 --- a/src/app/gui/editor_layout.h +++ b/src/app/gui/editor_layout.h @@ -121,6 +121,9 @@ class EditorCard { void SetPosition(Position pos); void SetMinimizable(bool minimizable) { minimizable_ = minimizable; } void SetClosable(bool closable) { closable_ = closable; } + void SetHeadless(bool headless) { headless_ = headless; } + void SetDockingAllowed(bool allowed) { docking_allowed_ = allowed; } + void SetIconCollapsible(bool collapsible) { icon_collapsible_ = collapsible; } // Begin drawing the card bool Begin(bool* p_open = nullptr); @@ -151,6 +154,15 @@ class EditorCard { bool first_draw_ = true; bool focused_ = false; bool* p_open_ = nullptr; + + // UX enhancements + bool headless_ = false; // Minimal chrome, no title bar + bool docking_allowed_ = true; // Allow docking + bool icon_collapsible_ = false; // Can collapse to floating icon + bool collapsed_to_icon_ = false; // Currently collapsed + ImVec2 saved_icon_pos_ = ImVec2(10, 100); // Position when collapsed to icon + + void DrawFloatingIconButton(); }; /** diff --git a/src/app/gui/gui_library.cmake b/src/app/gui/gui_library.cmake index 44234d6d..c87944b5 100644 --- a/src/app/gui/gui_library.cmake +++ b/src/app/gui/gui_library.cmake @@ -19,6 +19,7 @@ set( app/gui/widgets/widget_state_capture.cc app/gui/ui_helpers.cc app/gui/editor_layout.cc + app/gui/editor_card_manager.cc # Canvas system components app/gui/canvas/canvas_modals.cc app/gui/canvas/canvas_context_menu.cc