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
- Introduction
- Card-Based Architecture
- VSCode-Style Sidebar System
- Toolset System
- GUI Library Architecture
- Themed Widget System
- Begin/End Patterns
- Currently Integrated Editors
- Layout Helpers
- Workspace Management
- Future Editor Improvements
- Migration Checklist
- Code Examples
- 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:
void MyEditor::Initialize() {
auto& card_manager = gui::EditorCardManager::Get();
card_manager.RegisterCard({
.card_id = "myeditor.control_panel",
.display_name = "My Editor Controls",
.category = "MyEditor",
.shortcut_hint = "Ctrl+Shift+M",
.visibility_flag = &show_control_panel_,
.priority = 10
});
}
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:
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_)) {
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:
card_manager.RegisterCard({
.card_id = "music.tracker",
.display_name = "Music Tracker",
.category = "Music"
});
if (card.Begin(card_manager.GetVisibilityFlag("music.tracker"))) {
DrawContent();
card.End();
}
#define ICON_MD_MUSIC_NOTE
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:
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:
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
void MyEditor::DrawToolset() {
static gui::Toolset toolbar;
toolbar.Begin();
if (toolbar.AddToggle(
ICON_MD_LIST, &show_list_,
"Show List (Ctrl+1)")) {
}
}
toolbar.AddSeparator();
SaveAllChanges();
}
ReloadData();
}
toolbar.End();
}
#define ICON_MD_GRID_VIEW
Advanced Features
Inline Property Editing:
[]() { OnPaletteChanged(); });
[]() { OnGfxChanged(); });
Mode Button Groups:
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:
toolbar.AddRomBadge(rom_->asm_version(), []() {
ShowUpgradeDialog();
});
toolbar.AddV3StatusBadge(rom_->asm_version(), []() {
ShowV3Settings();
});
Best Practices
- Keep it compact: Only essential controls belong in the Toolset
- Use icons: Prefer icon-only buttons with tooltips
- Group logically: Use separators to group related controls
- Provide shortcuts: Include keyboard shortcuts in tooltips
- 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:
#include "app/gui/layout_helpers.h"
using gui::LayoutHelpers;
float widget_height = LayoutHelpers::GetStandardWidgetHeight();
float spacing = LayoutHelpers::GetStandardSpacing();
float toolbar_height = LayoutHelpers::GetToolbarHeight();
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
#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();
Reload();
}
ImGui::SameLine();
Duplicate();
}
}
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
bool ThemedIconButton(const char *icon, const char *tooltip)
Themed button with icon (Material Design Icons)
Theme Colors
Access theme colors via AgentUITheme (despite the name, it's used project-wide):
void DrawCustomUI() {
const auto& theme = AgentUI::GetTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color);
ImGui::PushStyleColor(ImGuiCol_Text, theme.text_color);
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):
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):
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:
Features:
- Fixed bottom bar (150px height)
- Color-coded categories
- Breadcrumb navigation ("Space > w")
- Auto-close after 5 seconds of inactivity
Integration:
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:
void DrawMyCard() {
gui::EditorCard card("Title", ICON_MD_ICON, &show_flag_);
card.SetDefaultSize(400, 300);
if (card.Begin(&show_flag_)) {
DrawCardContent();
}
card.End();
}
Critical Rules:
- Always call
End() even if Begin() returns false
- Put Begin/End calls in same scope for exception safety
- Check Begin() return value before expensive drawing
- Pass
p_open to both constructor and Begin() for proper close button handling
ImGui Native Begin/End
Window Pattern:
void DrawWindow() {
ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Window Title", &show_window_)) {
ImGui::Text("Content");
}
ImGui::End();
}
Table Pattern:
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:
if (ImGui::BeginChild("##ScrollRegion", ImVec2(0, 200), true)) {
for (int i = 0; i < 100; i++) {
ImGui::Text("Item %d", i);
}
}
ImGui::EndChild();
Toolset Begin/End
void DrawToolbar() {
static gui::Toolset toolbar;
toolbar.Begin();
toolbar.AddToggle(ICON_MD_ICON, &flag_, "Tooltip");
toolbar.End();
}
Error Handling
With Status Returns:
absl::Status DrawEditor() {
if (card.Begin()) {
}
card.End();
return absl::OkStatus();
}
#define RETURN_IF_ERROR(expression)
Exception Safety:
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
using gui::LayoutHelpers;
void DrawSettings() {
ImGui::Text("Property:");
ImGui::SameLine();
ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
ImGui::InputInt("##value", &my_value_);
}
Help Markers
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
LayoutHelpers::VerticalSpacing(10.0f);
ImGui::SameLine();
LayoutHelpers::HorizontalSpacing(20.0f);
LayoutHelpers::SeparatorText("Section Name");
Responsive Layout
float available_width = ImGui::GetContentRegionAvail().x;
float button_width = available_width * 0.5f;
if (ImGui::Button("Full Width", ImVec2(-1, 0))) {
}
if (ImGui::Button("Half Width", ImVec2(button_width, 0))) {
}
Grid Layouts
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:
workspace_manager_.ExecuteWorkspaceCommand(command_id);
11. Future Editor Improvements
This section outlines remaining improvements for editors not yet fully integrated.
SettingsEditor
Current State: Monolithic settings window
Potential Improvements:
- Split into categorized cards
- Register with EditorCardManager
- Add search/filter functionality
AgentEditor
Current State: Complex AI agent UI, under active development
Potential Improvements:
- Consider card-based refactoring when stable
- Integrate with EditorCardManager
- 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:
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:
#ifndef YAZE_APP_EDITOR_MY_EDITOR_H
#define YAZE_APP_EDITOR_MY_EDITOR_H
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_;
bool show_control_panel_ = true;
bool show_list_card_ = false;
bool show_properties_card_ = false;
bool control_panel_minimized_ = false;
int selected_item_ = -1;
};
}
}
#endif
Main namespace for the application.
#include "my_editor.h"
#include "app/gui/app/editor_card_manager.h"
#include "app/gui/core/themed_widgets.h"
#include "imgui/imgui.h"
namespace editor {
void MyEditor::Initialize() {
auto& card_manager = gui::EditorCardManager::Get();
card_manager.RegisterCard({
.card_id = "myeditor.control_panel",
.display_name = "My Editor Controls",
.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",
.category = "MyEditor",
.shortcut_hint = "Ctrl+Alt+1",
.visibility_flag = &show_list_card_,
.priority = 20
});
card_manager.RegisterCard({
.card_id = "myeditor.properties",
.display_name = "Properties",
.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");
}
return absl::OkStatus();
}
absl::Status MyEditor::Update() {
if (!rom_ || !rom_->is_loaded()) {
loading_card.SetDefaultSize(400, 200);
if (loading_card.Begin()) {
ImGui::Text("Waiting for ROM to load...");
}
loading_card.End();
return absl::OkStatus();
}
if (show_control_panel_) {
DrawControlPanel();
} else if (control_panel_minimized_) {
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)) {
show_control_panel_ = true;
control_panel_minimized_ = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Open My Editor Controls");
}
}
ImGui::End();
}
if (show_list_card_) {
DrawListCard();
}
if (show_properties_card_) {
DrawPropertiesCard();
}
return absl::OkStatus();
}
void MyEditor::DrawToolset() {
static gui::Toolset toolbar;
toolbar.Begin();
"Item List (Ctrl+Alt+1)")) {
}
"Properties (Ctrl+Alt+2)")) {
}
toolbar.AddSeparator();
Load();
}
toolbar.End();
}
void MyEditor::DrawControlPanel() {
ImGui::SetNextWindowSize(ImVec2(280, 220), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver);
&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();
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);
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() {
gui::EditorCard card(
"Properties",
ICON_MD_TUNE, &show_properties_card_);
card.SetDefaultSize(350, 400);
if (card.Begin(&show_properties_card_)) {
if (selected_item_ < 0) {
ImGui::TextDisabled("No item selected");
} else {
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();
}
}
}
}
card.End();
}
}
}
bool DangerButton(const char *label, const ImVec2 &size)
Danger/destructive action button (uses error color)
bool PrimaryButton(const char *label, const ImVec2 &size)
Primary action button (uses accent color)
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:
if (show_my_card_) {
my_card_->Draw();
}
if (show_my_card_ && my_card_) {
if (!my_card_->IsVisible()) my_card_->Show();
my_card_->Draw();
if (!my_card_->IsVisible()) show_my_card_ = false;
}
2. Using Hardcoded Colors
Problem: UI looks inconsistent, doesn't respect theme.
Cause: Using ImVec4 literals instead of theme colors.
Solution:
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Error!");
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:
if (show_my_card_) {
my_card_->Draw();
}
if (show_my_card_) {
if (!my_card_->IsVisible()) my_card_->Show();
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:
void MyEditor::Initialize() {
auto& card_manager = gui::EditorCardManager::Get();
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:
if (card.Begin()) {
DrawContent();
card.End();
}
if (card.Begin()) {
DrawContent();
}
card.End();
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:
if (show_control_panel_) {
DrawControlPanel();
} else if (control_panel_minimized_) {
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)) {
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:
card.SetPosition(gui::EditorCard::Position::Right);
card.SetPosition(gui::EditorCard::Position::Left);
card.SetPosition(gui::EditorCard::Position::Bottom);
card.SetPosition(gui::EditorCard::Position::Floating);
card.SetPosition(gui::EditorCard::Position::Free);
8. Not Handling Null Rom
Problem: Editor crashes when ROM isn't loaded.
Cause: Not checking rom_ before access.
Solution:
absl::Status MyEditor::Update() {
if (!rom_ || !rom_->is_loaded()) {
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();
}
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:
void DrawToolset() {
static gui::Toolset toolbar;
toolbar.Begin();
toolbar.AddToggle(ICON_MD_ICON, &flag_, "Tooltip");
toolbar.AddSeparator();
toolbar.End();
}
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:
card_manager.RegisterCard({
.shortcut_hint = "Ctrl+Alt+1",
});
"Item List (Ctrl+Alt+1)");
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:
- Dungeon Editor v2 (
dungeon_editor_v2.cc) - Gold standard implementation
- Palette Editor (
palette_editor.cc) - Recently refactored, clean patterns
- 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