# G5 - GUI Consistency and Card-Based Architecture Guide This guide establishes standards for GUI consistency across all yaze editors, focusing on the modern card-based architecture, theming system, and layout patterns. ## Table of Contents 1. [Introduction](#1-introduction) 2. [Card-Based Architecture](#2-card-based-architecture) 3. [VSCode-Style Sidebar System](#3-vscode-style-sidebar-system) 4. [Toolset System](#4-toolset-system) 5. [GUI Library Architecture](#5-gui-library-architecture) 6. [Themed Widget System](#6-themed-widget-system) 7. [Begin/End Patterns](#7-beginend-patterns) 8. [Currently Integrated Editors](#8-currently-integrated-editors) 9. [Layout Helpers](#9-layout-helpers) 10. [Workspace Management](#10-workspace-management) 11. [Future Editor Improvements](#11-future-editor-improvements) 12. [Migration Checklist](#12-migration-checklist) 13. [Code Examples](#13-code-examples) 14. [Common Pitfalls](#14-common-pitfalls) ## 1. Introduction ### Purpose This guide establishes GUI consistency standards to ensure all editors in yaze provide a unified, modern user experience with maintainable code. The card-based architecture allows editors to present multiple independent windows that can be opened, closed, minimized, and managed independently. ### Benefits - **User Experience**: Consistent keyboard shortcuts, visual styling, and interaction patterns - **Maintainability**: Reusable components reduce duplication and bugs - **Modularity**: Independent cards can be developed and tested separately - **Flexibility**: Users can arrange their workspace as needed - **Discoverability**: Central EditorCardManager makes all features accessible ### Target Audience Contributors working on: - New editor implementations - Refactoring existing editors - Adding new UI features to editors - Improving user experience consistency ## 2. Card-Based Architecture ### Philosophy Modern yaze editors use **independent, modular windows** called "cards" rather than traditional tab-based or fixed-layout UIs. Each card: - Is a top-level ImGui window (not a child window) - Can be opened/closed independently via keyboard shortcuts - Can be minimized to a floating icon - Registers with `EditorCardManager` for centralized control - Has its own visibility flag synchronized with the manager ### Core Components #### EditorCardManager Central singleton registry for all editor cards across the application. **Key Features:** - Global card registration with metadata - Keyboard shortcut management - View menu integration - Workspace preset system - Programmatic card control **Registration Example:** ```cpp void MyEditor::Initialize() { auto& card_manager = gui::EditorCardManager::Get(); card_manager.RegisterCard({ .card_id = "myeditor.control_panel", .display_name = "My Editor Controls", .icon = ICON_MD_SETTINGS, .category = "MyEditor", .shortcut_hint = "Ctrl+Shift+M", .visibility_flag = &show_control_panel_, .priority = 10 }); // Register more cards... } ``` #### EditorCard Wrapper class for individual card windows with Begin/End pattern. **Key Features:** - Automatic positioning (Right, Left, Bottom, Floating, Free) - Default size management - Minimize/maximize support - Focus management - Docking control **Usage Pattern:** ```cpp void MyEditor::DrawMyCard() { gui::EditorCard card("Card Title", ICON_MD_ICON, &show_card_); card.SetDefaultSize(400, 300); card.SetPosition(gui::EditorCard::Position::Right); if (card.Begin(&show_card_)) { // Draw card content here ImGui::Text("Card content"); } card.End(); } ``` ### Centralized Visibility Pattern All editors now use the centralized visibility system where EditorCardManager owns and manages all visibility bools: ```cpp // Registration (in Initialize()): card_manager.RegisterCard({ .card_id = "music.tracker", .display_name = "Music Tracker", .icon = ICON_MD_MUSIC_NOTE, .category = "Music" }); // Usage (in Update()): static gui::EditorCard card("Music Tracker", ICON_MD_MUSIC_NOTE); if (card.Begin(card_manager.GetVisibilityFlag("music.tracker"))) { DrawContent(); card.End(); } ``` **Benefits:** - Single source of truth for all card visibility - No scattered bool members in editor classes - Automatic X button close functionality - Consistent behavior across all cards - Easy to query/modify from anywhere ### Reference Implementations **Best Examples:** - `src/app/editor/dungeon/dungeon_editor_v2.cc` - Gold standard implementation - `src/app/editor/palette/palette_editor.cc` - Recently refactored, clean patterns **Key Patterns from Dungeon Editor v2:** - Independent top-level cards (no parent wrapper) - Control panel with minimize-to-icon - Toolset integration - Proper card registration with shortcuts - Room cards in separate docking class ## 3. VSCode-Style Sidebar System ### Overview The VSCode-style sidebar provides a unified interface for managing editor cards. It's a fixed 48px sidebar on the left edge with icon-based card toggles. **Key Features:** - Fixed position on left edge (48px width) - Icon-based card toggles - Category switcher for multi-editor sessions - Card browser button (Ctrl+Shift+B) - Collapse button (Ctrl+B) - Theme-aware styling - Recent categories stack (last 5 used) ### Usage Each card-based editor simply calls: ```cpp void MyEditor::DrawToolset() { auto& card_manager = gui::EditorCardManager::Get(); card_manager.DrawSidebar("MyEditor"); } ``` The sidebar automatically reads from the existing card registry - no per-editor configuration needed. ### Card Browser Press `Ctrl+Shift+B` to open the card browser: - Search/filter cards by name - Category tabs - Visibility toggle for all cards - Statistics (total/visible cards) - Preset management - Batch operations (Show All, Hide All per category) ## 4. Toolset System ### Overview `gui::Toolset` provides an ultra-compact toolbar that merges mode buttons with inline settings. It's designed for minimal vertical space usage while maximizing functionality. **Design Philosophy:** - Single horizontal bar with everything inline - Small icon-only buttons for modes - Inline property editing (InputHex with scroll) - Vertical separators for visual grouping - No wasted space ### Basic Usage ```cpp void MyEditor::DrawToolset() { static gui::Toolset toolbar; toolbar.Begin(); // Add toggle buttons for cards if (toolbar.AddToggle(ICON_MD_LIST, &show_list_, "Show List (Ctrl+1)")) { // Optional: React to toggle } if (toolbar.AddToggle(ICON_MD_GRID_VIEW, &show_grid_, "Show Grid (Ctrl+2)")) { // Toggled } toolbar.AddSeparator(); // Add action buttons if (toolbar.AddAction(ICON_MD_SAVE, "Save All")) { SaveAllChanges(); } if (toolbar.AddAction(ICON_MD_REFRESH, "Reload")) { ReloadData(); } toolbar.End(); } ``` ### Advanced Features **Inline Property Editing:** ```cpp // Hex properties with scroll wheel support toolbar.AddProperty(ICON_MD_PALETTE, "Palette", &palette_id_, []() { OnPaletteChanged(); }); toolbar.AddProperty(ICON_MD_IMAGE, "GFX", &gfx_id_, []() { OnGfxChanged(); }); ``` **Mode Button Groups:** ```cpp toolbar.BeginModeGroup(); bool draw_mode = toolbar.ModeButton(ICON_MD_BRUSH, mode_ == Mode::Draw, "Draw"); bool erase_mode = toolbar.ModeButton(ICON_MD_DELETE, mode_ == Mode::Erase, "Erase"); bool select_mode = toolbar.ModeButton(ICON_MD_SELECT, mode_ == Mode::Select, "Select"); toolbar.EndModeGroup(); if (draw_mode) mode_ = Mode::Draw; if (erase_mode) mode_ = Mode::Erase; if (select_mode) mode_ = Mode::Select; ``` **Version Badges:** ```cpp // For ROM version indicators toolbar.AddRomBadge(rom_->asm_version(), []() { ShowUpgradeDialog(); }); toolbar.AddV3StatusBadge(rom_->asm_version(), []() { ShowV3Settings(); }); ``` ### Best Practices 1. **Keep it compact**: Only essential controls belong in the Toolset 2. **Use icons**: Prefer icon-only buttons with tooltips 3. **Group logically**: Use separators to group related controls 4. **Provide shortcuts**: Include keyboard shortcuts in tooltips 5. **Consistent ordering**: Toggles first, properties second, actions third ## 5. GUI Library Architecture ### Modular Library Structure The yaze GUI is organized into focused, layered libraries for improved build times and maintainability: **gui_core (Foundation)** - Theme management, colors, styling - Icons, input handling, layout helpers - Dependencies: yaze_util, ImGui, SDL2 **canvas (Core Widget)** - Canvas widget system - Canvas utilities, modals, context menus - Dependencies: gui_core, yaze_gfx **gui_widgets (Reusable Components)** - Themed widgets, palette widgets - Asset browser, text editor, tile selector - Dependencies: gui_core, yaze_gfx **gui_automation (Testing & AI)** - Widget ID registry, auto-registration - Widget state capture and measurement - Dependencies: gui_core **gui_app (Application-Specific UI)** - EditorCardManager, EditorLayout - Background renderer, collaboration panel - Dependencies: gui_core, gui_widgets, gui_automation **yaze_gui (Interface Library)** - Aggregates all sub-libraries - Single link target for executables ### Theme-Aware Sizing System All UI sizing respects the theme's `compact_factor` (0.8-1.2) for global density control: ```cpp #include "app/gui/layout_helpers.h" using gui::LayoutHelpers; // Standard widget sizing float widget_height = LayoutHelpers::GetStandardWidgetHeight(); float spacing = LayoutHelpers::GetStandardSpacing(); float toolbar_height = LayoutHelpers::GetToolbarHeight(); // All sizing respects: theme.compact_factor * multiplier * ImGui::GetFontSize() ``` **Layout Helpers API:** - `BeginTableWithTheming()` - Tables with automatic theme colors - `BeginCanvasPanel() / EndCanvasPanel()` - Canvas containers - `BeginPaddedPanel() / EndPaddedPanel()` - Consistent padding - `InputHexRow()` - Labeled hex inputs - `BeginPropertyGrid() / EndPropertyGrid()` - 2-column property tables - `PropertyRow()` - Label + widget in table row - `SectionHeader()` - Colored section headers - `HelpMarker()` - Tooltip help icons ## 6. Themed Widget System ### Philosophy **Never use hardcoded colors.** All UI elements must derive colors from the central theme system to ensure consistency and support for future dark/light theme switching. ### Themed Widget Prefixes All theme-aware widgets are prefixed with `Themed*`: **Available Widgets:** - `ThemedButton()` - Standard button with theme colors - `ThemedIconButton()` - Icon-only button - `PrimaryButton()` - Emphasized primary action (e.g., Save) - `DangerButton()` - Dangerous action (e.g., Delete, Discard) - `SectionHeader()` - Visual section divider with text ### Usage Examples ```cpp #include "app/gui/core/themed_widgets.h" using gui::ThemedButton; using gui::ThemedIconButton; using gui::PrimaryButton; using gui::DangerButton; using gui::SectionHeader; void MyCard::DrawContent() { SectionHeader("Settings"); if (PrimaryButton("Save Changes", ImVec2(-1, 0))) { SaveToRom(); } if (DangerButton("Discard All", ImVec2(-1, 0))) { DiscardChanges(); } ImGui::Separator(); SectionHeader("Quick Actions"); if (ThemedIconButton(ICON_MD_REFRESH, "Reload")) { Reload(); } ImGui::SameLine(); if (ThemedIconButton(ICON_MD_COPY, "Duplicate")) { Duplicate(); } } ``` ### Theme Colors Access theme colors via `AgentUITheme` (despite the name, it's used project-wide): ```cpp #include "app/editor/agent/agent_ui_theme.h" void DrawCustomUI() { const auto& theme = AgentUI::GetTheme(); // Use semantic colors ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color); ImGui::PushStyleColor(ImGuiCol_Text, theme.text_color); // Draw content ImGui::BeginChild("MyPanel"); ImGui::Text("Themed panel content"); ImGui::EndChild(); ImGui::PopStyleColor(2); } ``` **Common Theme Colors:** - `panel_bg_color` - Background for panels - `text_color` - Primary text - `text_dim_color` - Secondary/disabled text - `accent_color` - Highlights and accents - `status_success` - Success indicators (green) - `status_warning` - Warning indicators (yellow) - `status_error` - Error indicators (red) ### Migration from Hardcoded Colors **Before (Bad):** ```cpp ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.2f, 0.2f, 0.25f, 1.0f)); ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Error!"); ``` **After (Good):** ```cpp const auto& theme = AgentUI::GetTheme(); ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color); ImGui::TextColored(theme.status_error, "Error!"); ``` ### WhichKey Command System Spacemacs-style hierarchical command navigation: ```cpp // Press Space → Shows root menu with colored categories // Press category key (e.g., w) → Shows window submenu // Press command key → Executes command and closes // Press ESC → Go back or close ``` **Features:** - Fixed bottom bar (150px height) - Color-coded categories - Breadcrumb navigation ("Space > w") - Auto-close after 5 seconds of inactivity **Integration:** ```cpp command_manager_.RegisterPrefix("w", 'w', "Window", "Window management"); command_manager_.RegisterSubcommand("w", "w.s", 's', "Show All", "Show all windows", [this]() { workspace_manager_.ShowAllWindows(); }); ``` ## 7. Begin/End Patterns ### Philosophy All resource-managing UI elements use the Begin/End pattern for RAII-style cleanup. This prevents resource leaks and ensures proper ImGui state management. ### EditorCard Begin/End **Pattern:** ```cpp void DrawMyCard() { gui::EditorCard card("Title", ICON_MD_ICON, &show_flag_); card.SetDefaultSize(400, 300); // Begin returns false if window is collapsed/hidden if (card.Begin(&show_flag_)) { // Draw content only when visible DrawCardContent(); } // End MUST be called regardless of Begin result card.End(); } ``` **Critical Rules:** 1. Always call `End()` even if `Begin()` returns false 2. Put Begin/End calls in same scope for exception safety 3. Check Begin() return value before expensive drawing 4. Pass `p_open` to both constructor and Begin() for proper close button handling ### ImGui Native Begin/End **Window Pattern:** ```cpp void DrawWindow() { ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); if (ImGui::Begin("Window Title", &show_window_)) { // Draw window content ImGui::Text("Content"); } ImGui::End(); // ALWAYS call, even if Begin returns false } ``` **Table Pattern:** ```cpp if (ImGui::BeginTable("##MyTable", 3, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Column 1"); ImGui::TableSetupColumn("Column 2"); ImGui::TableSetupColumn("Column 3"); ImGui::TableHeadersRow(); for (int row = 0; row < 10; row++) { ImGui::TableNextRow(); for (int col = 0; col < 3; col++) { ImGui::TableNextColumn(); ImGui::Text("Cell %d,%d", row, col); } } ImGui::EndTable(); } ``` **Child Window Pattern:** ```cpp if (ImGui::BeginChild("##ScrollRegion", ImVec2(0, 200), true)) { // Scrollable content for (int i = 0; i < 100; i++) { ImGui::Text("Item %d", i); } } ImGui::EndChild(); ``` ### Toolset Begin/End ```cpp void DrawToolbar() { static gui::Toolset toolbar; toolbar.Begin(); // Add toolbar items toolbar.AddToggle(ICON_MD_ICON, &flag_, "Tooltip"); toolbar.End(); // Finalizes layout } ``` ### Error Handling **With Status Returns:** ```cpp absl::Status DrawEditor() { gui::EditorCard card("Editor", ICON_MD_EDIT); if (card.Begin()) { RETURN_IF_ERROR(DrawContent()); // Can early return } card.End(); // Still called via normal flow return absl::OkStatus(); } ``` **Exception Safety:** ```cpp // If exceptions are enabled, use RAII wrappers struct ScopedCard { gui::EditorCard& card; explicit ScopedCard(gui::EditorCard& c) : card(c) { card.Begin(); } ~ScopedCard() { card.End(); } }; ``` ## 8. Currently Integrated Editors The card system is integrated across 11 of 13 editors: | Editor | Cards | Status | |--------|-------|--------| | **DungeonEditorV2** | Room selector, canvas, object selector, object editor, entrance editor, tile painter, sprite placer | Complete | | **PaletteEditor** | Group editor, animation editor, color picker | Complete | | **GraphicsEditor** | Sheet editor, browser, player animations, prototype viewer | Complete | | **ScreenEditor** | Dungeon maps, inventory, overworld map, title screen, naming screen | Complete | | **SpriteEditor** | Vanilla sprites, custom sprites | Complete | | **OverworldEditor** | Canvas, tile16/tile8 selectors, area graphics, scratch workspace, GFX groups, usage stats, properties, exits, items, sprites, settings | Complete | | **MessageEditor** | Message list, editor, font atlas, dictionary | Complete | | **HexEditor** | Hex editor with comparison | Complete | | **AssemblyEditor** | Assembly editor, file browser | Complete | | **MusicEditor** | Music tracker, instrument editor, assembly view | Complete | | **Emulator** | CPU debugger, PPU viewer, memory viewer, breakpoints, performance, AI agent, save states, keyboard config, APU debugger, audio mixer | Complete | **Not Yet Ported:** - **SettingsEditor** - Monolithic settings window, low usage frequency - **AgentEditor** - Complex AI agent UI, under active development ## 9. Layout Helpers ### Overview `app/gui/layout_helpers.h` provides utilities for consistent spacing, sizing, and layout across all editors. ### Standard Input Widths ```cpp #include "app/gui/core/layout_helpers.h" using gui::LayoutHelpers; void DrawSettings() { ImGui::Text("Property:"); ImGui::SameLine(); // Standard width for input fields (120px) ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth()); ImGui::InputInt("##value", &my_value_); } ``` ### Help Markers ```cpp ImGui::Text("Complex Setting"); ImGui::SameLine(); LayoutHelpers::HelpMarker( "This is a detailed explanation of what this setting does. " "It appears as a tooltip when hovering the (?) icon." ); ``` ### Spacing Utilities ```cpp // Vertical spacing LayoutHelpers::VerticalSpacing(10.0f); // 10px vertical space // Horizontal spacing ImGui::SameLine(); LayoutHelpers::HorizontalSpacing(20.0f); // 20px horizontal space // Separator with text LayoutHelpers::SeparatorText("Section Name"); ``` ### Responsive Layout ```cpp // Get available width float available_width = ImGui::GetContentRegionAvail().x; // Calculate dynamic widths float button_width = available_width * 0.5f; // 50% of available if (ImGui::Button("Full Width", ImVec2(-1, 0))) { // -1 = fill available width } if (ImGui::Button("Half Width", ImVec2(button_width, 0))) { // Fixed to 50% } ``` ### Grid Layouts ```cpp // Two-column grid if (ImGui::BeginTable("##Grid", 2, ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Label 1:"); ImGui::TableNextColumn(); ImGui::InputText("##input1", buffer1, sizeof(buffer1)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Label 2:"); ImGui::TableNextColumn(); ImGui::InputText("##input2", buffer2, sizeof(buffer2)); ImGui::EndTable(); } ``` ## 10. Workspace Management The workspace manager provides comprehensive window and layout operations: **Window Management:** - `ShowAllWindows() / HideAllWindows()` - via EditorCardManager - `MaximizeCurrentWindow()` - Dock to central node - `RestoreAllWindows()` - Reset window sizes - `CloseAllFloatingWindows()` - Close undocked windows **Window Navigation:** - `FocusNextWindow() / FocusPreviousWindow()` - Window cycling - `SplitWindowHorizontal() / SplitWindowVertical()` - Split current window - `CloseCurrentWindow()` - Close focused window **Command Integration:** ```cpp workspace_manager_.ExecuteWorkspaceCommand(command_id); // Supports: w.s (show all), w.h (hide all), l.s (save layout), etc. ``` ## 11. Future Editor Improvements This section outlines remaining improvements for editors not yet fully integrated. ### SettingsEditor **Current State:** Monolithic settings window **Potential Improvements:** 1. Split into categorized cards 2. Register with EditorCardManager 3. Add search/filter functionality ### AgentEditor **Current State:** Complex AI agent UI, under active development **Potential Improvements:** 1. Consider card-based refactoring when stable 2. Integrate with EditorCardManager 3. Add keyboard shortcuts for common operations ## 12. Migration Checklist Use this checklist when converting an editor to the card-based architecture: ### Planning Phase - [ ] Identify all major UI components that should become cards - [ ] Design keyboard shortcut scheme (Ctrl+Alt+[1-9] for cards) - [ ] Plan `Toolset` contents (toggles, actions, properties) - [ ] List all hardcoded colors to be replaced ### Implementation Phase - Core Structure - [ ] Add visibility flags for all cards (e.g., `bool show_my_card_ = false;`) - [ ] Create `Initialize()` method if not present - [ ] Register all cards with `EditorCardManager` in `Initialize()` - [ ] Add card priority values (10, 20, 30, etc.) - [ ] Include shortcut hints in registration ### Implementation Phase - Toolset - [ ] Create `DrawToolset()` method - [ ] Add toggle buttons for each card - [ ] Include keyboard shortcut hints in tooltips - [ ] Add separators between logical groups - [ ] Add action buttons for common operations ### Implementation Phase - Control Panel - [ ] Create `DrawControlPanel()` method - [ ] Call `DrawToolset()` at top of control panel - [ ] Add checkbox grid for quick toggles - [ ] Add minimize-to-icon button at bottom - [ ] Include modified status indicators if applicable - [ ] Add "Save All" / "Discard All" buttons if applicable ### Implementation Phase - Cards - [ ] Create card classes or Draw methods - [ ] Use `gui::EditorCard` wrapper with Begin/End - [ ] Set default size and position for each card - [ ] Pass visibility flag to both constructor and Begin() - [ ] Implement proper card content ### Implementation Phase - Update Method - [ ] Update `Update()` to draw control panel (if visible) - [ ] Update `Update()` to draw minimize-to-icon (if minimized) - [ ] Add visibility flag synchronization for each card: ```cpp if (show_card_ && card_instance_) { if (!card_instance_->IsVisible()) card_instance_->Show(); card_instance_->Draw(); if (!card_instance_->IsVisible()) show_card_ = false; } ``` ### Implementation Phase - Theming - [ ] Replace all `ImVec4` color literals with theme colors - [ ] Use `ThemedButton()` instead of `ImGui::Button()` where appropriate - [ ] Use `PrimaryButton()` for save/apply actions - [ ] Use `DangerButton()` for delete/discard actions - [ ] Use `SectionHeader()` for visual hierarchy - [ ] Use `ThemedIconButton()` for icon-only buttons ### Testing Phase - [ ] Test opening each card via control panel checkbox - [ ] Test opening each card via keyboard shortcut - [ ] Test closing cards with X button - [ ] Test minimize-to-icon on control panel - [ ] Test reopening from icon - [ ] Verify EditorCardManager shows all cards in View menu - [ ] Test that closing control panel doesn't affect other cards - [ ] Verify visibility flags sync properly - [ ] Test docking behavior (if enabled) - [ ] Verify all themed widgets render correctly ### Documentation Phase - [ ] Document keyboard shortcuts in header comment - [ ] Update E2-development-guide.md editor status if applicable - [ ] Add example to this guide if pattern is novel - [ ] Update CLAUDE.md if editor behavior changed significantly ## 13. Code Examples ### Complete Editor Implementation This example shows a minimal but complete editor implementation using all the patterns: ```cpp // my_editor.h #ifndef YAZE_APP_EDITOR_MY_EDITOR_H #define YAZE_APP_EDITOR_MY_EDITOR_H #include "app/editor/editor.h" #include "app/rom.h" namespace yaze { namespace editor { class MyEditor : public Editor { public: explicit MyEditor(Rom* rom = nullptr) : rom_(rom) { type_ = EditorType::kMyEditor; } void Initialize() override; absl::Status Load() override; absl::Status Update() override; void set_rom(Rom* rom) { rom_ = rom; } Rom* rom() const { return rom_; } private: void DrawToolset(); void DrawControlPanel(); void DrawListCard(); void DrawPropertiesCard(); Rom* rom_; // Card visibility flags bool show_control_panel_ = true; bool show_list_card_ = false; bool show_properties_card_ = false; bool control_panel_minimized_ = false; // Data int selected_item_ = -1; }; } // namespace editor } // namespace yaze #endif // YAZE_APP_EDITOR_MY_EDITOR_H ``` ```cpp // my_editor.cc #include "my_editor.h" #include "app/gui/app/editor_card_manager.h" #include "app/gui/app/editor_layout.h" #include "app/gui/core/icons.h" #include "app/gui/core/themed_widgets.h" #include "imgui/imgui.h" namespace yaze { namespace editor { void MyEditor::Initialize() { auto& card_manager = gui::EditorCardManager::Get(); card_manager.RegisterCard({ .card_id = "myeditor.control_panel", .display_name = "My Editor Controls", .icon = ICON_MD_SETTINGS, .category = "MyEditor", .shortcut_hint = "Ctrl+Shift+M", .visibility_flag = &show_control_panel_, .priority = 10 }); card_manager.RegisterCard({ .card_id = "myeditor.list", .display_name = "Item List", .icon = ICON_MD_LIST, .category = "MyEditor", .shortcut_hint = "Ctrl+Alt+1", .visibility_flag = &show_list_card_, .priority = 20 }); card_manager.RegisterCard({ .card_id = "myeditor.properties", .display_name = "Properties", .icon = ICON_MD_TUNE, .category = "MyEditor", .shortcut_hint = "Ctrl+Alt+2", .visibility_flag = &show_properties_card_, .priority = 30 }); } absl::Status MyEditor::Load() { if (!rom_ || !rom_->is_loaded()) { return absl::NotFoundError("ROM not loaded"); } // Load data from ROM // ... return absl::OkStatus(); } absl::Status MyEditor::Update() { if (!rom_ || !rom_->is_loaded()) { gui::EditorCard loading_card("My Editor Loading", ICON_MD_SETTINGS); loading_card.SetDefaultSize(400, 200); if (loading_card.Begin()) { ImGui::Text("Waiting for ROM to load..."); } loading_card.End(); return absl::OkStatus(); } // Control panel (can be hidden/minimized) if (show_control_panel_) { DrawControlPanel(); } else if (control_panel_minimized_) { // Minimize-to-icon 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("##MyEditorControlIcon", nullptr, icon_flags)) { if (ImGui::Button(ICON_MD_SETTINGS, ImVec2(40, 40))) { show_control_panel_ = true; control_panel_minimized_ = false; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open My Editor Controls"); } } ImGui::End(); } // Independent cards if (show_list_card_) { DrawListCard(); } if (show_properties_card_) { DrawPropertiesCard(); } return absl::OkStatus(); } void MyEditor::DrawToolset() { static gui::Toolset toolbar; toolbar.Begin(); if (toolbar.AddToggle(ICON_MD_LIST, &show_list_card_, "Item List (Ctrl+Alt+1)")) { // Toggled } if (toolbar.AddToggle(ICON_MD_TUNE, &show_properties_card_, "Properties (Ctrl+Alt+2)")) { // Toggled } toolbar.AddSeparator(); if (toolbar.AddAction(ICON_MD_REFRESH, "Reload")) { Load(); } toolbar.End(); } void MyEditor::DrawControlPanel() { using gui::PrimaryButton; using gui::DangerButton; ImGui::SetNextWindowSize(ImVec2(280, 220), ImGuiCond_FirstUseEver); ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver); if (ImGui::Begin(ICON_MD_SETTINGS " My Editor Controls", &show_control_panel_)) { DrawToolset(); ImGui::Separator(); ImGui::Text("Quick Toggles:"); if (ImGui::BeginTable("##QuickToggles", 2, ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Checkbox("List", &show_list_card_); ImGui::TableNextColumn(); ImGui::Checkbox("Properties", &show_properties_card_); ImGui::EndTable(); } ImGui::Separator(); if (ImGui::SmallButton(ICON_MD_MINIMIZE " Minimize to Icon")) { control_panel_minimized_ = true; show_control_panel_ = false; } } ImGui::End(); } void MyEditor::DrawListCard() { gui::EditorCard card("Item List", ICON_MD_LIST, &show_list_card_); card.SetDefaultSize(300, 500); card.SetPosition(gui::EditorCard::Position::Left); if (card.Begin(&show_list_card_)) { ImGui::Text("Item List Content"); if (ImGui::BeginChild("##ItemListScroll", ImVec2(0, 0), true)) { for (int i = 0; i < 50; i++) { bool is_selected = (selected_item_ == i); if (ImGui::Selectable(absl::StrFormat("Item %d", i).c_str(), is_selected)) { selected_item_ = i; } } } ImGui::EndChild(); } card.End(); } void MyEditor::DrawPropertiesCard() { using gui::ThemedIconButton; using gui::SectionHeader; using gui::PrimaryButton; gui::EditorCard card("Properties", ICON_MD_TUNE, &show_properties_card_); card.SetDefaultSize(350, 400); card.SetPosition(gui::EditorCard::Position::Right); if (card.Begin(&show_properties_card_)) { if (selected_item_ < 0) { ImGui::TextDisabled("No item selected"); } else { SectionHeader("Item Properties"); ImGui::Text("Item: %d", selected_item_); static char name_buffer[64] = "Item Name"; ImGui::InputText("Name", name_buffer, sizeof(name_buffer)); static int value = 100; ImGui::InputInt("Value", &value); ImGui::Separator(); if (PrimaryButton("Save Changes", ImVec2(-1, 0))) { // Save to ROM } if (ThemedIconButton(ICON_MD_REFRESH, "Reset to defaults")) { // Reset } } } card.End(); } } // namespace editor } // namespace yaze ``` ## 14. Common Pitfalls ### 1. Forgetting Bidirectional Visibility Sync **Problem:** Cards don't reopen after being closed with X button. **Cause:** Not syncing the visibility flag back when the card is closed. **Solution:** ```cpp // WRONG if (show_my_card_) { my_card_->Draw(); } // CORRECT if (show_my_card_ && my_card_) { if (!my_card_->IsVisible()) my_card_->Show(); my_card_->Draw(); if (!my_card_->IsVisible()) show_my_card_ = false; // Sync back! } ``` ### 2. Using Hardcoded Colors **Problem:** UI looks inconsistent, doesn't respect theme. **Cause:** Using `ImVec4` literals instead of theme colors. **Solution:** ```cpp // WRONG ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Error!"); // CORRECT const auto& theme = AgentUI::GetTheme(); ImGui::TextColored(theme.status_error, "Error!"); ``` ### 3. Not Calling Show() Before Draw() **Problem:** Newly opened cards don't appear. **Cause:** The card's internal `show_` flag isn't set when visibility flag changes. **Solution:** ```cpp // WRONG if (show_my_card_) { my_card_->Draw(); // Card won't appear if internally hidden } // CORRECT if (show_my_card_) { if (!my_card_->IsVisible()) my_card_->Show(); // Ensure visible my_card_->Draw(); } ``` ### 4. Missing EditorCardManager Registration **Problem:** Cards don't appear in View menu, shortcuts don't work. **Cause:** Forgot to register cards in `Initialize()`. **Solution:** ```cpp void MyEditor::Initialize() { auto& card_manager = gui::EditorCardManager::Get(); // Register ALL cards card_manager.RegisterCard({ .card_id = "myeditor.my_card", .display_name = "My Card", .icon = ICON_MD_ICON, .category = "MyEditor", .shortcut_hint = "Ctrl+Alt+1", .visibility_flag = &show_my_card_, .priority = 20 }); } ``` ### 5. Improper Begin/End Pairing **Problem:** ImGui asserts, UI state corruption. **Cause:** Not calling `End()` when `Begin()` returns false, or early returns. **Solution:** ```cpp // WRONG if (card.Begin()) { DrawContent(); card.End(); // Only called if Begin succeeded } // CORRECT if (card.Begin()) { DrawContent(); } card.End(); // ALWAYS called ``` ### 6. Not Testing Minimize-to-Icon **Problem:** Control panel can't be reopened after minimizing. **Cause:** Forgot to implement the minimize-to-icon floating button. **Solution:** ```cpp if (show_control_panel_) { DrawControlPanel(); } else if (control_panel_minimized_) { // Draw floating icon button ImGui::SetNextWindowPos(ImVec2(10, 100)); ImGui::SetNextWindowSize(ImVec2(50, 50)); ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking; if (ImGui::Begin("##ControlIcon", nullptr, flags)) { if (ImGui::Button(ICON_MD_SETTINGS, ImVec2(40, 40))) { show_control_panel_ = true; control_panel_minimized_ = false; } } ImGui::End(); } ``` ### 7. Wrong Card Position Enum **Problem:** Card appears in unexpected location. **Cause:** Using wrong `Position` enum value. **Solution:** ```cpp // Card positions card.SetPosition(gui::EditorCard::Position::Right); // Dock to right side card.SetPosition(gui::EditorCard::Position::Left); // Dock to left side card.SetPosition(gui::EditorCard::Position::Bottom); // Dock to bottom card.SetPosition(gui::EditorCard::Position::Floating);// Save position card.SetPosition(gui::EditorCard::Position::Free); // No positioning ``` ### 8. Not Handling Null Rom **Problem:** Editor crashes when ROM isn't loaded. **Cause:** Not checking `rom_` before access. **Solution:** ```cpp absl::Status MyEditor::Update() { if (!rom_ || !rom_->is_loaded()) { // Show loading card gui::EditorCard loading_card("Editor Loading", ICON_MD_ICON); loading_card.SetDefaultSize(400, 200); if (loading_card.Begin()) { ImGui::Text("Waiting for ROM..."); } loading_card.End(); return absl::OkStatus(); } // Safe to use rom_ now DrawEditor(); return absl::OkStatus(); } ``` ### 9. Forgetting Toolset Begin/End **Problem:** Toolset items don't render or layout is broken. **Cause:** Missing `Begin()` or `End()` calls. **Solution:** ```cpp void DrawToolset() { static gui::Toolset toolbar; toolbar.Begin(); // REQUIRED toolbar.AddToggle(ICON_MD_ICON, &flag_, "Tooltip"); toolbar.AddSeparator(); toolbar.AddAction(ICON_MD_SAVE, "Save"); toolbar.End(); // REQUIRED } ``` ### 10. Hardcoded Shortcuts in Tooltips **Problem:** Shortcuts shown in tooltips don't match actual keybinds. **Cause:** Tooltip string doesn't match `shortcut_hint` in registration. **Solution:** ```cpp // In Registration card_manager.RegisterCard({ .shortcut_hint = "Ctrl+Alt+1", // Define once // ... }); // In Toolset toolbar.AddToggle(ICON_MD_LIST, &show_list_, "Item List (Ctrl+Alt+1)"); // Match exactly ``` --- ## Summary Following this guide ensures: - **Consistency**: All editors use the same patterns and components - **Maintainability**: Reusable components reduce code duplication - **User Experience**: Predictable keyboard shortcuts and visual styling - **Flexibility**: Independent cards allow custom workspace arrangements - **Discoverability**: EditorCardManager makes all features accessible When adding new editors or refactoring existing ones, refer to: 1. **Dungeon Editor v2** (`dungeon_editor_v2.cc`) - Gold standard implementation 2. **Palette Editor** (`palette_editor.cc`) - Recently refactored, clean patterns 3. **This Guide** - Comprehensive reference for all patterns For questions or suggestions about GUI consistency, please open an issue on GitHub or discuss in the development chat. --- **Last Updated**: October 13, 2025