63 KiB
Editor UI Architecture Refactor Proposal
Executive Summary
This document proposes a refactoring of yaze's editor panel system to eliminate naming confusion, centralize lifecycle management, and reduce boilerplate. The key change is renaming "Card" terminology to "Panel"—a more precise term used by professional IDEs like VSCode, JetBrains, and Xcode.
Part 1: Terminology Decision
Why "Panel" Instead of "Card"?
| Term | Associations | Precision |
|---|---|---|
| Card | Material Design cards, Kanban boards, mobile UI | Vague—cards are typically static content containers |
| Panel | VSCode Side Panel, JetBrains Tool Windows, Xcode Inspectors | Precise—panels are dockable, resizable tool windows |
| Pane | Split views, editor areas | Typically refers to divisions within a window |
| Tool Window | JetBrains, Visual Studio | Verbose, but very precise |
Decision: Use "Panel" as the primary term:
gui::EditorCard→gui::PanelWindowCardInfo→PanelDescriptorEditorCardRegistry→PanelManager*Cardclasses →*Panelclasses
Part 2: Current Architecture Analysis
2.1 Key Components (As-Is)
| Component | Location | Current Name | Proposed Name |
|---|---|---|---|
| ImGui window wrapper | editor_layout.h |
gui::EditorCard |
gui::PanelWindow |
| Metadata struct | editor_card_registry.h |
CardInfo |
PanelDescriptor |
| Central registry | editor_card_registry.h |
EditorCardRegistry |
PanelManager |
| Sidebar UI | activity_bar.h |
ActivityBar |
ActivityBar (unchanged) |
| Layout builder | layout_manager.h |
LayoutManager |
LayoutManager (unchanged) |
| Presets | layout_presets.h |
LayoutPresets |
LayoutPresets (unchanged) |
2.2 Current Workflow (Dual Registration Problem)
┌─────────────────────────────────────────────────────────────────────────────┐
│ CURRENT WORKFLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Editor::Initialize() Editor::Update() │
│ ─────────────────── ───────────────── │
│ │
│ 1. Register CardInfo metadata 1. Create gui::EditorCard instance │
│ with registry (per-frame or static) │
│ │
│ card_registry->RegisterCard({ gui::EditorCard my_panel( │
│ .card_id = MakeCardId("x.foo"), MakeCardTitle("Foo"), │
│ .display_name = "Foo", ICON_MD_FOO); │
│ .window_title = " Foo", │
│ .visibility_flag = &show_foo_, if (my_panel.Begin(&show_foo_)) { │
│ ... DrawFooContent(); │
│ }); } │
│ my_panel.End(); │
│ │
│ PROBLEM: Two separate steps, titles can diverge, no centralized drawing │
└─────────────────────────────────────────────────────────────────────────────┘
2.3 Identified Issues
- Naming Collision:
gui::EditorCardvs conceptual "editor card" vs*Cardclasses - Dual Registration: Editors register metadata AND manually draw—no central control
- Title Mismatch Risk:
CardInfo.window_titlevsgui::EditorCardconstructor title - No Cross-Editor Panel Support: Panels are tied to their parent editor
- Inconsistent Instantiation: Static vs per-frame vs unique_ptr member patterns
Part 3: Complete Editor Inventory
3.1 Overworld Editor
Tool Panels (Static)
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
overworld.canvas |
Overworld Canvas | MAP | Main map editing canvas with toolset | ✅ |
overworld.tile16_selector |
Tile16 Selector | GRID_ON | Tile palette for painting | ✅ |
overworld.tile8_selector |
Tile8 Selector | GRID_3X3 | Low-level 8x8 tile editing | ❌ |
overworld.area_graphics |
Area Graphics | IMAGE | GFX sheet preview for current area | ✅ |
overworld.scratch |
Scratch Workspace | DRAW | Layout planning/clipboard area | ❌ |
overworld.gfx_groups |
GFX Groups | FOLDER | Graphics group configuration | ❌ |
overworld.usage_stats |
Usage Statistics | ANALYTICS | Tile usage analysis across all maps | ✅ |
overworld.v3_settings |
v3 Settings | SETTINGS | ZSCustomOverworld configuration | ❌ |
overworld.properties |
Map Properties | TUNE | Per-map settings (palette, GFX, etc.) | ✅ |
overworld.debug |
Debug Window | BUG_REPORT | Internal debug information | ❌ |
Resource Panels (Dynamic per-map)
| Panel ID Pattern | Display Name | Purpose |
|---|---|---|
overworld.map_{id} |
Map {id} ({world}) | Focused view of specific overworld map |
overworld.map_{id}.entities |
Map {id} Entities | Entity list (entrances, exits, items, sprites) |
Note: overworld.usage_stats is useful cross-editor—see Section 5.
3.2 Dungeon Editor (V2)
Tool Panels (Static)
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
dungeon.control_panel |
Dungeon Controls | CASTLE | Mode and tool selection | ✅ |
dungeon.room_selector |
Room Selector | LIST | Room list with search/filter (296 rooms) | ✅ |
dungeon.entrance_list |
Entrance List | DOOR_FRONT | Entrance list with search/filter | ✅ |
dungeon.room_matrix |
Room Matrix | GRID_VIEW | Visual 16x16 room layout | ✅ |
dungeon.entrances |
Entrance Properties | DOOR_SLIDING | Selected entrance property editor | ❌ |
dungeon.room_graphics |
Room Graphics | IMAGE | Room GFX sheet preview | ✅ |
dungeon.object_editor |
Object Editor | CONSTRUCTION | Object placement/editing | ✅ |
dungeon.palette_editor |
Palette Editor | PALETTE | Room palette selection | ❌ |
dungeon.debug_controls |
Debug Controls | BUG_REPORT | Debug tools and state inspection | ❌ |
dungeon.emulator_preview |
SNES Object Preview | MONITOR | Live emulator object preview | ❌ |
Resource Panels (Dynamic per-room)
| Panel ID Pattern | Display Name | Purpose |
|---|---|---|
dungeon.room_{id} |
Room {id} | Canvas for editing specific room (0-295) |
dungeon.room_{id}.objects |
Room {id} Objects | Object list for specific room |
Architecture: Dungeon Editor is pure panel-based—no "main canvas" concept. All panels are peers. Resource panels (room-specific) are created on-demand when a room is opened.
3.3 Graphics Editor
Tool Panels (Static)
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
graphics.sheet_browser_v2 |
Sheet Browser | VIEW_LIST | Navigate all 223 graphics sheets | ✅ |
graphics.pixel_editor |
Pixel Editor | DRAW | 8x8/16x16 tile pixel editing | ✅ |
graphics.palette_controls |
Palette Controls | PALETTE | Palette selection for editing | ✅ |
graphics.link_sprite_editor |
Link Sprite Editor | PERSON | Edit Link's sprite frames | ❌ |
graphics.polyhedral_editor |
3D Objects | VIEW_IN_AR | Edit rupees, crystals, triforce | ✅ |
graphics.sheet_editor |
Sheet Editor (Legacy) | EDIT | Older sheet editing interface | ❌ |
graphics.sheet_browser |
Asset Browser (Legacy) | VIEW_LIST | Older asset browsing | ❌ |
graphics.player_animations |
Player Animations | PERSON | View Link animation sequences | ❌ |
graphics.prototype_viewer |
Prototype Viewer | CONSTRUCTION | Experimental feature viewer | ❌ |
Resource Panels (Dynamic per-sheet)
| Panel ID Pattern | Display Name | Purpose |
|---|---|---|
graphics.sheet_{id} |
Sheet {id} | Dedicated editor for specific GFX sheet |
3.4 Palette Editor
TODO: Remove control panel and fold into activity bar for useful actions? Resource management
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
palette.control_panel |
Palette Controls | PALETTE | Group manager and quick actions | ✅ |
palette.ow_main |
Overworld Main | LANDSCAPE | 6 overworld area palettes | ✅ |
palette.ow_animated |
Overworld Animated | WATER | Water, lava animation palettes | ❌ |
palette.dungeon_main |
Dungeon Main | CASTLE | 20 dungeon room palettes | ✅ |
palette.sprites |
Global Sprite Palettes | PETS | Main sprite color sets | ✅ |
palette.sprites_aux1 |
Sprites Aux 1 | FILTER_1 | Auxiliary sprite colors 1 | ❌ |
palette.sprites_aux2 |
Sprites Aux 2 | FILTER_2 | Auxiliary sprite colors 2 | ❌ |
palette.sprites_aux3 |
Sprites Aux 3 | FILTER_3 | Auxiliary sprite colors 3 | ❌ |
palette.equipment |
Equipment Palettes | SHIELD | Link's tunic/equipment colors | ❌ |
palette.quick_access |
Quick Access | COLOR_LENS | Color harmony tools | ✅ |
palette.custom |
Custom Palette | BRUSH | Custom palette editing | ❌ |
3.5 Music Editor
Tool Panels (Static)
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
music.song_browser |
Song Browser | LIBRARY_MUSIC | Navigate all game songs | ✅ |
music.tracker |
Playback Control | PLAY_CIRCLE | Transport controls and BPM | ✅ |
music.piano_roll |
Piano Roll | PIANO | Visual note editing | ❌ |
music.instrument_editor |
Instrument Editor | SPEAKER | Edit instrument samples | ✅ |
music.sample_editor |
Sample Editor | WAVES | Edit BRR audio samples | ❌ |
music.assembly |
Assembly View | CODE | View music as 65816 assembly | ❌ |
music.help |
Help | HELP | Music editor documentation | ❌ |
Resource Panels (Dynamic per-song)
| Panel ID Pattern | Display Name | Purpose |
|---|---|---|
music.song_{index} |
Song: {name} | Full song editor for a specific track |
music.song_{index}.piano_roll |
{name} Piano Roll | Piano roll for specific song |
music.song_{index}.channels |
{name} Channels | Channel mixer for specific song |
Dynamic Panels: Music Editor creates per-song panels on demand. Multiple songs can be open simultaneously for comparison/copying.
3.6 Screen Editor
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
screen.dungeon_maps |
Dungeon Maps | MAP | Edit dungeon map screens | ✅ |
screen.inventory_menu |
Inventory Menu | INVENTORY | Edit inventory/pause menu | ✅ |
screen.overworld_map |
Overworld Map | PUBLIC | Edit overworld map screen | ❌ |
screen.title_screen |
Title Screen | TITLE | Edit title screen graphics | ✅ |
screen.naming_screen |
Naming Screen | EDIT | Edit save file naming screen | ❌ |
3.7 Sprite Editor
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
sprite.vanilla_editor |
Vanilla Sprites | SMART_TOY | View/edit built-in sprites | ✅ |
sprite.custom_editor |
Custom Sprites | ADD_CIRCLE | Import/edit custom ZSM sprites | ❌ |
3.8 Message Editor
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
message.message_list |
Message List | LIST | Navigate all game messages | ✅ |
message.message_editor |
Message Editor | EDIT | Edit message text and formatting | ✅ |
message.font_atlas |
Font Atlas | FONT_DOWNLOAD | View/edit font graphics | ✅ |
message.dictionary |
Dictionary | BOOK | Edit compression dictionary | ❌ |
3.9 Assembly Editor
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
assembly.editor |
Assembly Editor | CODE | 65816 assembly code editor | ✅ |
assembly.file_browser |
File Browser | FOLDER_OPEN | Navigate ASM project files | ✅ |
Note: Assembly Editor uses file browser integration for project navigation.
3.10 Agent Editor
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
agent.chat |
Agent Chat | CHAT | Main AI chat interface | ✅ |
agent.configuration |
AI Configuration | SETTINGS | API keys, model selection | ❌ |
agent.status |
Agent Status | INFO | Connection status, context info | ❌ |
agent.prompt_editor |
Prompt Editor | EDIT | Edit system prompts | ❌ |
agent.profiles |
Bot Profiles | FOLDER | Manage bot personalities | ❌ |
agent.history |
Chat History | HISTORY | View past conversations | ❌ |
agent.metrics |
Metrics Dashboard | ANALYTICS | Token usage, response times | ❌ |
agent.builder |
Agent Builder | AUTO_FIX_HIGH | Create custom agents | ❌ |
3.11 Emulator (Registered by EditorManager)
TODO: Consolidate some panels into PpuViewer nav as a canvas of sorts
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
emulator.cpu_debugger |
CPU Debugger | BUG_REPORT | CPU registers, stepping | ✅ |
emulator.ppu_viewer |
PPU Viewer | VIDEOGAME_ASSET | Graphics layer debugging | ✅ |
emulator.memory_viewer |
Memory Viewer | MEMORY | RAM/VRAM inspection | ❌ |
emulator.breakpoints |
Breakpoints | STOP | Breakpoint management | ❌ |
emulator.performance |
Performance | SPEED | Frame timing, FPS | ✅ |
emulator.ai_agent |
AI Agent | SMART_TOY | AI-assisted debugging | ❌ |
emulator.save_states |
Save States | SAVE | Save/load state management | ✅ |
emulator.keyboard_config |
Keyboard Config | KEYBOARD | Input configuration | ✅ |
emulator.virtual_controller |
Virtual Controller | SPORTS_ESPORTS | On-screen gamepad | ✅ |
emulator.apu_debugger |
APU Debugger | AUDIOTRACK | Audio processor debugging | ❌ |
emulator.audio_mixer |
Audio Mixer | AUDIO_FILE | Channel volume control | ❌ |
3.12 Memory (Registered by EditorManager)
TODO: Add more panels to help with hex editing operations?
| Panel ID | Display Name | Icon | Purpose | Default Visible |
|---|---|---|---|---|
memory.hex_editor |
Hex Editor | MEMORY | Raw ROM hex editing | ✅ |
Part 4: Cross-Editor Panel Visibility
4.1 The Problem
Currently, panels are tightly coupled to their parent editor. When switching editors, panels are hidden. But some panels are useful across editors:
| Panel | Useful When... |
|---|---|
overworld.usage_stats |
Editing dungeons to check tile availability |
palette.ow_main |
Editing overworld tiles to preview palette effects |
emulator.cpu_debugger |
Any editing to test changes in real-time |
graphics.pixel_editor |
Editing sprites, dungeons, or messages |
4.2 Panel Categories
We propose three panel categories based on lifecycle behavior:
| Category | Behavior | Examples |
|---|---|---|
| Editor-Bound | Hidden when switching away from parent editor | dungeon.room_selector, music.piano_roll |
| Persistent | Remains visible across editor switches | emulator.cpu_debugger, palette.quick_access |
| Cross-Editor | Can be pinned to stay visible (user choice) | overworld.usage_stats, graphics.pixel_editor |
4.3 Implementation: Pin-to-Persist
Add a "pin" button to panel headers:
class PanelWindow {
public:
// ... existing methods ...
/// If pinned, panel stays visible when switching editors
void SetPinned(bool pinned) { pinned_ = pinned; }
bool IsPinned() const { return pinned_; }
private:
bool pinned_ = false;
};
The PanelManager respects pinned state:
void PanelManager::OnEditorSwitch(EditorType from, EditorType to) {
// Hide non-pinned panels from the previous editor
for (auto& [id, panel] : panels_) {
if (panel->GetCategory() == GetCategoryForEditor(from) && !IsPinned(id)) {
HidePanel(id);
}
}
// Show default panels for new editor
auto defaults = LayoutPresets::GetDefaultPanels(to);
for (const auto& id : defaults) {
ShowPanel(id);
}
}
4.4 Related Panel Cascade (Optional)
For Editor-Bound panels only, we can define parent-child relationships:
struct PanelDescriptor {
// ... existing fields ...
/// If set, this panel closes when parent panel closes
std::string parent_panel_id;
/// If true, closing this panel also closes children
bool cascade_close = false;
};
Example: Closing dungeon.control_panel could cascade-close dungeon.object_editor if they're defined as related.
Documentation Requirement: Any cascade behavior MUST be documented in LayoutPresets comments.
Part 5: Resource Panels & Multi-Session Support
5.1 The Resource Panel Concept
Some panels represent specific resources within the ROM—not generic tools, but windows for editing a particular piece of data:
| Editor | Resource Type | Example Panel IDs |
|---|---|---|
| Dungeon | Rooms (0-295) | dungeon.room_42, dungeon.room_128 |
| Music | Songs (0-63) | music.song_5, music.song_12 |
| Overworld | Maps (0-159) | overworld.map_0, overworld.map_64 |
| Graphics | Sheets (0-222) | graphics.sheet_100, graphics.sheet_212 |
| Sprite | Sprites (0-255) | sprite.vanilla_42, sprite.custom_3 |
| Message | Messages (0-395) | message.text_42, message.text_100 |
5.2 Resource Panel Lifecycle
/// Base class for resource-specific panels
class ResourcePanel : public EditorPanel {
public:
/// The resource ID this panel edits (room_id, song_index, etc.)
virtual int GetResourceId() const = 0;
/// Resource type for grouping
virtual std::string GetResourceType() const = 0;
/// Can have multiple instances open simultaneously
virtual bool AllowMultipleInstances() const { return true; }
// Resource panels are always EditorBound by default
PanelCategory GetPanelCategory() const override {
return PanelCategory::EditorBound;
}
};
5.3 Resource Panel ID Format
{session}.{category}.{resource_type}_{resource_id}[.{subpanel}]
Examples:
s0.dungeon.room_42 -- Room 42 in session 0
s1.dungeon.room_42 -- Room 42 in session 1 (different ROM)
s0.music.song_5 -- Song 5 in session 0
s0.music.song_5.piano_roll -- Piano roll for song 5
s0.overworld.map_64.entities -- Entity list for map 64
5.4 Multi-Session Awareness
When multiple ROMs are loaded (multi-session editing), resource panels must be uniquely identified:
class PanelManager {
public:
/// Create a resource panel for the current session
std::string CreateResourcePanel(const std::string& category,
const std::string& resource_type,
int resource_id) {
std::string panel_id = MakeResourcePanelId(
active_session_, category, resource_type, resource_id);
// Check if already exists
if (panels_.contains(panel_id)) {
ShowPanel(panel_id); // Just bring to front
return panel_id;
}
// Create new resource panel
auto panel = CreateResourcePanelImpl(category, resource_type, resource_id);
RegisterPanel(std::move(panel));
ShowPanel(panel_id);
return panel_id;
}
/// Generate session-aware resource panel ID
std::string MakeResourcePanelId(size_t session_id,
const std::string& category,
const std::string& resource_type,
int resource_id) const {
if (session_count_ > 1) {
return absl::StrFormat("s%zu.%s.%s_%d",
session_id, category, resource_type, resource_id);
}
return absl::StrFormat("%s.%s_%d", category, resource_type, resource_id);
}
/// Get all resource panels for a session
std::vector<EditorPanel*> GetResourcePanelsInSession(size_t session_id);
/// Close all resource panels when a session closes
void CloseSessionResourcePanels(size_t session_id);
};
5.5 Multi-ROM Side-by-Side Editing
With session-aware IDs, users can:
- Open two ROMs (vanilla + hack)
- View same room side-by-side:
s0.dungeon.room_42(vanilla)s1.dungeon.room_42(hack)
- Compare and copy between them
┌─────────────────────────────────────────────────────────────────────────────┐
│ MULTI-SESSION RESOURCE PANELS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Session 0 (vanilla.sfc) Session 1 (myhack.sfc) │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ s0.dungeon.room_42 │ │ s1.dungeon.room_42 │ │
│ │ Room 42 │ │ Room 42 │ │
│ │ [Session 0] │ ◄─────► │ [Session 1] │ │
│ │ │ Compare │ │ │
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
│ │ │ Canvas │ │ │ │ Canvas │ │ │
│ │ │ (vanilla) │ │ │ │ (modified) │ │ │
│ │ └───────────────┘ │ │ └───────────────┘ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Window Title Format: "Room 42 [S0]" "Room 42 [S1]" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
5.6 Resource Panel Window Titles
std::string PanelManager::GetResourcePanelTitle(const std::string& panel_id) const {
auto* panel = GetPanel(panel_id);
if (!panel) return "";
auto* resource_panel = dynamic_cast<ResourcePanel*>(panel);
if (!resource_panel) {
return GetWindowTitle(panel_id); // Fallback to normal
}
std::string title = absl::StrFormat("%s %s %d",
resource_panel->GetIcon(),
resource_panel->GetResourceType(),
resource_panel->GetResourceId());
// Add session suffix for multi-session
if (session_count_ > 1) {
size_t session_id = GetSessionFromPanelId(panel_id);
title += absl::StrFormat(" [S%zu]", session_id);
}
return title;
}
5.7 Resource Panel Limits
To prevent memory bloat, enforce limits:
struct ResourcePanelLimits {
static constexpr size_t kMaxRoomPanels = 8; // Max open rooms
static constexpr size_t kMaxSongPanels = 4; // Max open songs
static constexpr size_t kMaxSheetPanels = 6; // Max open GFX sheets
static constexpr size_t kMaxTotalResourcePanels = 20;
};
When limit is reached:
- Option A: Auto-close oldest panel (LRU)
- Option B: Warn user and prevent new panel
- Option C: Prompt to close one (user choice)
Recommendation: Option A with undo (can reopen from sidebar).
Part 6: Proposed Architecture
6.1 New Terminology Mapping
| Current | Proposed | Description |
|---|---|---|
gui::EditorCard |
gui::PanelWindow |
Pure ImGui window wrapper |
CardInfo |
PanelDescriptor |
Panel metadata (immutable after registration) |
EditorCardRegistry |
PanelManager |
Owns panels, manages lifecycle, draws all |
*Card classes |
*Panel classes |
Logical component implementations |
6.2 Interface Definitions
gui::PanelWindow
namespace yaze::gui {
/// Pure ImGui window wrapper for dockable panels
class PanelWindow {
public:
enum class Position { Free, Left, Right, Bottom, Top, Floating };
PanelWindow(const char* title, const char* icon = nullptr);
// Configuration
void SetDefaultSize(float width, float height);
void SetPosition(Position pos);
void SetMinimizable(bool minimizable);
void SetClosable(bool closable);
void SetDockingAllowed(bool allowed);
void SetPinnable(bool pinnable); // NEW: Allow pin-to-persist
// Header buttons (drawn in title bar)
void AddHeaderButton(const char* icon, const char* tooltip,
std::function<void()> callback);
// ImGui lifecycle
bool Begin(bool* p_open = nullptr);
void End();
// State
void SetPinned(bool pinned);
bool IsPinned() const;
void Focus();
bool IsFocused() const;
const char* GetWindowName() const;
};
} // namespace yaze::gui
editor::EditorPanel (New Interface)
namespace yaze::editor {
/// Category for panel lifecycle behavior
enum class PanelCategory {
EditorBound, // Hidden on editor switch (default)
Persistent, // Always visible once shown
CrossEditor // User can pin to persist
};
/// Base interface for all logical panel components
class EditorPanel {
public:
virtual ~EditorPanel() = default;
// ========== Identity ==========
virtual std::string GetId() const = 0; // e.g., "dungeon.room_selector"
virtual std::string GetDisplayName() const = 0; // e.g., "Room Selector"
virtual std::string GetIcon() const = 0; // e.g., ICON_MD_LIST
virtual std::string GetEditorCategory() const = 0; // e.g., "Dungeon"
// ========== Drawing ==========
virtual void Draw(bool* p_open) = 0; // Called when visible
// ========== Lifecycle ==========
virtual void OnOpen() {} // Panel becomes visible
virtual void OnClose() {} // Panel becomes hidden
virtual void OnFocus() {} // Panel receives focus
// ========== Behavior ==========
virtual PanelCategory GetPanelCategory() const {
return PanelCategory::EditorBound;
}
virtual bool IsEnabled() const { return true; }
virtual std::string GetDisabledTooltip() const { return ""; }
virtual std::string GetShortcutHint() const { return ""; }
virtual int GetPriority() const { return 50; }
// ========== Relationships ==========
virtual std::string GetParentPanelId() const { return ""; }
virtual bool CascadeCloseChildren() const { return false; }
};
} // namespace yaze::editor
editor::PanelManager
namespace yaze::editor {
/// Central manager for all EditorPanel instances
class PanelManager {
public:
// ========== Registration ==========
void RegisterPanel(std::unique_ptr<EditorPanel> panel);
template <typename T, typename... Args>
T* EmplacePanel(Args&&... args);
void UnregisterPanel(const std::string& panel_id);
// ========== Visibility ==========
void ShowPanel(const std::string& panel_id);
void HidePanel(const std::string& panel_id);
void TogglePanel(const std::string& panel_id);
bool IsPanelVisible(const std::string& panel_id) const;
void ShowAllInCategory(const std::string& category);
void HideAllInCategory(const std::string& category);
// ========== Central Drawing ==========
void DrawAllVisiblePanels(); // Call once per frame
// ========== Editor Switching ==========
void OnEditorSwitch(EditorType from, EditorType to);
void SetActiveEditor(EditorType type);
// ========== Pin Management ==========
void SetPanelPinned(const std::string& panel_id, bool pinned);
bool IsPanelPinned(const std::string& panel_id) const;
std::vector<std::string> GetPinnedPanels() const;
// ========== Query ==========
EditorPanel* GetPanel(const std::string& panel_id);
std::vector<EditorPanel*> GetPanelsInCategory(const std::string& category);
std::vector<std::string> GetAllCategories() const;
// ========== Session Support ==========
void SetActiveSession(size_t session_id);
size_t GetActiveSession() const;
// ========== Persistence ==========
void SaveVisibilityState();
void LoadVisibilityState();
void SavePinnedState();
void LoadPinnedState();
private:
std::unordered_map<std::string, std::unique_ptr<EditorPanel>> panels_;
std::unordered_map<std::string, bool> visibility_;
std::unordered_map<std::string, bool> pinned_;
EditorType active_editor_ = EditorType::kUnknown;
size_t active_session_ = 0;
};
} // namespace yaze::editor
6.3 Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ PROPOSED ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ PanelManager │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ panels_: map<string, unique_ptr<EditorPanel>> │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ RoomList │ │ ObjectEditor │ │ UsageStats │ ... │ │ │
│ │ │ │ Panel │ │ Panel │ │ Panel │ │ │ │
│ │ │ │ [EditorBound]│ │ [EditorBound]│ │ [CrossEditor]│ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ DrawAllVisiblePanels() { │ │
│ │ for (auto& [id, panel] : panels_) { │ │
│ │ if (!IsVisible(id)) continue; │ │
│ │ │ │
│ │ gui::PanelWindow window(GetWindowTitle(id), panel->GetIcon()); │ │
│ │ if (IsPinnable(id)) window.SetPinnable(true); │ │
│ │ │ │
│ │ if (window.Begin(&visibility_[id])) { │ │
│ │ panel->Draw(&visibility_[id]); │ │
│ │ } │ │
│ │ window.End(); │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌───────────────┐ ┌───────────────────┐ │
│ │ ActivityBar │────▶│ PanelManager │◀────│ LayoutManager │ │
│ │ (Sidebar UI) │ │ (Ownership) │ │ (DockBuilder) │ │
│ └──────────────┘ └───────────────┘ └───────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │LayoutPresets │ │
│ │(Default vis) │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Part 7: Migration Plan & Current Status
Current Status (December 2024)
✅ PHASES 1-8 COMPLETE - Full panel system migration complete including resource panels and cross-editor features.
| Phase | Status | Description |
|---|---|---|
| Phase 1 | ✅ Complete | UI widget renamed (EditorCard → PanelWindow) |
| Phase 2 | ✅ Complete | Registry renamed (EditorCardRegistry deleted, PanelManager created) |
| Phase 3 | ✅ Complete | EditorPanel interface defined with central drawing |
| Phase 4 | ✅ Complete | Dungeon Editor migrated (9 panels) |
| Phase 5 | ✅ Complete | All remaining editors migrated (40 panels) |
| Phase 6 | ✅ Complete | Resource panels with LRU logic |
| Phase 7 | ✅ Complete | Cross-editor features (Pin-to-Persist, OnEditorSwitch) |
| Phase 8 | ✅ Complete | Multi-session verification and testing |
Phase 5 Completion Details
All editors now use the EditorPanel architecture:
| Editor | Static Panels | Resource Panels | Status | Pattern Used |
|---|---|---|---|---|
| Dungeon Editor | 10 | dungeon.room_{id} |
✅ Complete | Callback + Resource |
| Graphics Editor | 5 | — | ✅ Complete | Direct interface |
| Music Editor | 7 | music.song_{id}, music.piano_roll_{id} |
✅ Complete | Callback + Resource |
| Palette Editor | 11 | — | ✅ Complete | Callback pattern |
| Agent Editor | 8 | — | ✅ Complete | Callback pattern |
| Sprite Editor | 2 | — | ✅ Complete | Callback pattern |
| Screen Editor | 5 | — | ✅ Complete | Callback pattern |
| Message Editor | 4 | — | ✅ Complete | Callback pattern |
| Overworld Editor | 9 | — | ✅ Complete | Direct pointer pattern |
Total: 61 static EditorPanel implementations + dynamic resource panels
Overworld Editor Migration Details
The Overworld Editor migration was the most complex, using a direct pointer pattern with separate .cc files for maintainability:
Panels Created (9 total)
High-Priority Panels:
| Panel | ID | Purpose | File |
|---|---|---|---|
| AreaGraphicsPanel | overworld.area_graphics |
GFX sheet preview for current area | area_graphics_panel.h/.cc |
| Tile16SelectorPanel | overworld.tile16_selector |
Tile palette for painting | tile16_selector_panel.h/.cc |
| MapPropertiesPanel | overworld.properties |
Per-map settings editor | map_properties_panel.h/.cc |
Medium-Priority Panels:
| Panel | ID | Purpose | File |
|---|---|---|---|
| ScratchSpacePanel | overworld.scratch |
Layout planning workspace | scratch_space_panel.h/.cc |
| UsageStatisticsPanel | overworld.usage_stats |
Tile usage analysis | usage_statistics_panel.h/.cc |
Low-Priority Panels:
| Panel | ID | Purpose | File |
|---|---|---|---|
| Tile8SelectorPanel | overworld.tile8_selector |
8x8 tile editing | tile8_selector_panel.h/.cc |
| DebugWindowPanel | overworld.debug |
Debug information | debug_window_panel.h/.cc |
| GfxGroupsPanel | overworld.gfx_groups |
Graphics group configuration | gfx_groups_panel.h/.cc |
| V3SettingsPanel | overworld.v3_settings |
ZSCustomOverworld settings | v3_settings_panel.h/.cc |
Architecture Pattern
// Panel header (area_graphics_panel.h)
class AreaGraphicsPanel : public EditorPanel {
public:
explicit AreaGraphicsPanel(OverworldEditor* editor);
std::string GetId() const override { return "overworld.area_graphics"; }
std::string GetDisplayName() const override { return "Area Graphics"; }
std::string GetIcon() const override { return ICON_MD_IMAGE; }
std::string GetEditorCategory() const override { return "Overworld"; }
void Draw(bool* p_open) override;
private:
OverworldEditor* editor_;
};
// Registration in overworld_editor.cc
void OverworldEditor::Initialize() {
panel_manager->RegisterEditorPanel(
std::make_unique<AreaGraphicsPanel>(this));
// ... all 9 panels registered once
}
Git Commits (16 atomic commits)
1cd667a93f docs: update architecture documentation for EditorPanel system
a436321bcc refactor(wasm): update control API for panel system compatibility
28ca469cb5 fix(emu): improve audio timing and SPC700 cycle accuracy
604667e47f feat(editor): integrate EditorManager and Activity Bar with panel system
060857179c refactor(ui): update settings and coordinator systems for panel architecture
e7c65a1c46 feat(overworld-editor): migrate to EditorPanel system
7df3980222 feat(message-editor): migrate to EditorPanel system
a91f08928a feat(screen-editor): migrate to EditorPanel system
081af10c80 feat(sprite-editor): migrate to EditorPanel system
36c8bee040 feat(agent-editor): migrate to EditorPanel system
862a69965d feat(palette-editor): migrate to EditorPanel system
d4f0dccf10 feat(music-editor): migrate to EditorPanel system
195cf8ac5e feat(graphics-editor): migrate existing panels to EditorPanel interface
a5daccce8e feat(dungeon-editor): migrate to EditorPanel system
e9e1123a57 refactor(layout): update layout system to use PanelManager
b652636618 refactor(editor): remove deprecated EditorCardRegistry
6007a1086d feat(editor): add EditorPanel system for unified panel management
Benefits Achieved
- ✅ Centralized Management - All panels managed by
PanelManager - ✅ Activity Bar Integration - Panels automatically appear in sidebar
- ✅ Efficient Rendering - Panels created once in
Initialize(), not per-frame - ✅ Layout Persistence - Panel arrangements can be saved/restored
- ✅ Session Support - Proper scoping for multi-ROM editing
- ✅ Consistent Architecture - All editors follow same pattern
Phase 6-8 Completion Details
Phase 6: Resource Panels ✅ Complete
Implemented Features:
ResourcePanelbase class inresource_panel.h- Dynamic room panels (
DungeonRoomPanel) created on-demand when rooms selected - Dynamic song panels (
MusicSongPanel) and piano roll panels for music editor - Session-aware ID generation via
MakePanelId() - LRU eviction in
EnforceResourceLimits()respects pinned panels - Resource panels appear in Activity Bar under their editor category
- Room selector properly limited to 296 rooms (kNumberOfRooms)
Key Implementation Details:
// Resource panels use BASE IDs - PanelManager handles session prefixing
std::string card_id = absl::StrFormat("dungeon.room_%d", room_id);
panel_manager->RegisterPanel({.card_id = card_id, ...});
panel_manager->ShowPanel(card_id); // Sets visibility immediately
Important: Do NOT use MakeCardId() for resource panels - it causes double-prefixing since RegisterPanel() already calls MakePanelId() internally.
Phase 7: Cross-Editor Features ✅ Complete
Implemented Features:
- Pin-to-Persist UI - Pin button in Activity Bar sidebar (not title bar due to ImGui limitations)
OnEditorSwitch()- Hides non-pinned panels from previous category, shows defaults for new- Dashboard Category -
kDashboardCategorysuppresses all panels until editor selected - Category Filtering - Resource panels only visible when their category is active OR pinned
Key Implementation Details:
// In PanelManager::DrawAllVisiblePanels()
if (active_category_.empty() || active_category_ == kDashboardCategory) {
return; // Suppress panels when no editor selected
}
// Resource panels check category + pin status
if (panel->GetEditorCategory() == active_category_ ||
IsPanelPinned(panel_id) ||
panel->GetPanelCategory() == PanelCategory::Persistent) {
// Draw panel
}
Phase 8: Multi-Session Support ✅ Complete
Verified Behaviors:
- Session-aware panel IDs:
s0.dungeon.room_42vss1.dungeon.room_42 - Resource panels properly scoped to their session
- Fixed double-prefix bug (resource panels use base IDs, not
MakeCardId()) - Window titles include session suffix when multiple ROMs open
Dungeon Editor Panels (11 total):
| Panel | ID | Type |
|---|---|---|
| Control Panel | dungeon.control_panel |
Static |
| Room Selector | dungeon.room_selector |
Static |
| Entrance List | dungeon.entrance_list |
Static |
| Room Matrix | dungeon.room_matrix |
Static |
| Entrances Properties | dungeon.entrances |
Static |
| Room Graphics | dungeon.room_graphics |
Static |
| Object Editor | dungeon.object_editor |
Static |
| Debug Controls | dungeon.debug_controls |
Static |
| Room {id} | dungeon.room_{id} |
Resource (dynamic) |
Music Editor Resource Panels:
| Panel | ID Pattern | Type |
|---|---|---|
| Song Tracker | music.song_{id} |
Resource (dynamic) |
| Piano Roll | music.piano_roll_{id} |
Resource (dynamic) |
Remaining Work / Future Enhancements
The core panel system refactor is complete. The following are optional enhancements:
| Enhancement | Priority | Effort | Description |
|---|---|---|---|
| Overworld Resource Panels | Medium | 4-6h | Create overworld.map_{id} panels for per-map editing |
| Graphics Resource Panels | Medium | 4-6h | Create graphics.sheet_{id} panels for per-sheet editing |
| Sprite Resource Panels | Low | 2-4h | Create sprite.vanilla_{id} panels |
| Cascade Close | Low | 2-3h | Implement parent-child panel relationships |
| Panel State Persistence | Low | 2-3h | Save/restore pinned state and visibility to config |
| Keyboard Shortcuts | Low | 1-2h | Add configurable shortcuts for common panels |
Known Limitations:
- Pin button is in Activity Bar sidebar, not panel title bar (ImGui docking limitation)
- Resource panel limits are enforced but may need tuning based on user feedback
- Some editors (Assembly, Agent) have minimal panel integration
Troubleshooting: Common Panel Visibility Issues
When panels don't respect visibility or appear duplicated, check for these common issues:
Issue 1: Duplicate Panel Drawing (DUPLICATE DETECTED warnings)
Symptom: Console shows [PanelWindow] DUPLICATE DETECTED: 'Panel Name' Begin() called twice
Cause: Panel is being drawn by BOTH:
- Central
PanelManager::DrawAllVisiblePanels()(via EditorPanel) - Local
gui::PanelWindowcode in the editor'sUpdate()method
Fix: Remove the local drawing code. When using RegisterEditorPanel(), the central drawing handles everything:
// WRONG - draws twice
void MyEditor::Initialize() {
panel_manager->RegisterEditorPanel(std::make_unique<MyPanel>(...));
}
void MyEditor::Update() {
gui::PanelWindow panel("My Panel", ICON);
if (panel.Begin(&show_panel_)) { DrawContent(); }
panel.End();
}
// CORRECT - draws once via central system
void MyEditor::Initialize() {
panel_manager->RegisterEditorPanel(std::make_unique<MyPanel>([this]() {
DrawContent();
}));
}
void MyEditor::Update() {
// No local drawing - handled by PanelManager::DrawAllVisiblePanels()
return absl::OkStatus();
}
Issue 2: Duplicate Registration (RegisterPanel + RegisterEditorPanel)
Symptom: Panel appears twice in Activity Bar, or metadata conflicts
Cause: Both RegisterPanel() AND RegisterEditorPanel() called for same panel
Fix: Use only RegisterEditorPanel() - the EditorPanel class provides all metadata:
// WRONG - duplicate registration
panel_manager->RegisterPanel({.card_id = "editor.my_panel", ...});
panel_manager->RegisterEditorPanel(std::make_unique<MyPanel>(...));
// CORRECT - EditorPanel provides metadata via GetId(), GetDisplayName(), etc.
panel_manager->RegisterEditorPanel(std::make_unique<MyPanel>(...));
Issue 3: Resource Panel Double-Prefixing
Symptom: Resource panels (rooms, songs) don't appear or have wrong IDs like s0.s0.dungeon.room_42
Cause: Using MakeCardId() for resource panels when RegisterPanel() already adds session prefix
Fix: Use base IDs for resource panels - RegisterPanel() handles prefixing:
// WRONG - double prefix
std::string card_id = MakeCardId(absl::StrFormat("dungeon.room_%d", room_id));
panel_manager->RegisterPanel({.card_id = card_id, ...});
// CORRECT - let RegisterPanel handle prefixing
std::string card_id = absl::StrFormat("dungeon.room_%d", room_id);
panel_manager->RegisterPanel({.card_id = card_id, ...});
panel_manager->ShowPanel(card_id); // Uses same base ID
Issue 4: Context Menu Popups Don't Open
Symptom: Clicking context menu items doesn't open the expected popup (e.g., entity editor)
Cause: ImGui::OpenPopup() called from within another popup's callback doesn't work
Fix: Use deferred popup pattern - store request and process outside popup context:
// WRONG - OpenPopup inside context menu callback fails
entity_menu.callback = [this]() {
InsertEntity();
ImGui::OpenPopup("Entity Editor"); // Won't work!
};
// CORRECT - defer popup opening
void MyEditor::HandleEntityInsert(const std::string& type) {
pending_insert_type_ = type; // Store for later
}
void MyEditor::Update() {
ProcessPendingInsert(); // Called outside popup context
// ... draw popups here, OpenPopup() works now
}
Issue 5: Panels Visible Before Editor Selected
Symptom: Panels from other editors appear when on Dashboard
Cause: active_category_ not set to Dashboard, or missing category check
Fix: Ensure SetActiveCategory(kDashboardCategory) when ROM loaded but no editor selected:
void EditorManager::LoadRom() {
// After loading...
panel_manager_.SetActiveCategory(PanelManager::kDashboardCategory);
}
void EditorManager::SwitchToEditor(EditorType type) {
panel_manager_.SetActiveCategory(EditorRegistry::GetEditorCategory(type));
}
Issue 6: Resource Panels Always Visible (Act Like Pinned)
Symptom: Resource panels (rooms, songs) don't hide when switching editors
Cause: Missing category filtering in the editor's drawing loop
Fix: Add category + pin check before drawing resource panels:
// In editor's DrawLayout() for dynamic resource panels:
for (auto& resource : active_resources_) {
std::string card_id = absl::StrFormat("editor.resource_%d", resource.id);
// Skip if not in category AND not pinned
if (panel_manager->GetActiveCategory() != "MyEditor" &&
!panel_manager->IsPanelPinned(card_id)) {
continue;
}
// Draw the resource panel...
}
Checklist for Migrating an Editor to EditorPanel System
-
Create EditorPanel classes in
panels/subdirectory- Implement
GetId(),GetDisplayName(),GetIcon(),GetEditorCategory(),GetPriority() - Implement
Draw(bool* p_open)with content drawing
- Implement
-
Update Initialize()
- Call
RegisterEditorPanel()for each panel - Remove any
RegisterPanel()calls (EditorPanel provides metadata) - Call
ShowPanel()for default-visible panels
- Call
-
Update Update()
- Remove ALL local
gui::PanelWindowdrawing code - Central
PanelManager::DrawAllVisiblePanels()handles drawing - Keep only non-panel logic (popup modals, shortcuts, etc.)
- Remove ALL local
-
For Resource Panels (dynamic, per-resource)
- Use base IDs (no
MakeCardId()) - Add category filtering before drawing
- Register on-demand when resource opened
- Unregister when resource closed
- Use base IDs (no
-
Test
- Verify no DUPLICATE DETECTED warnings
- Verify panels appear/hide on editor switch
- Verify Activity Bar shows correct panels
- Verify pinning works across editor switches
Original Migration Plan (For Reference)
Phase 1: Rename UI Widget (2-3 hours) ✅ COMPLETE
- Rename
gui::EditorCard→gui::PanelWindow - Update all 24 files that use
gui::EditorCard - Update documentation
Phase 2: Rename Registry (3-4 hours) ✅ COMPLETE
- Rename
CardInfo→PanelDescriptor - Rename
EditorCardRegistry→PanelManager - Update all registration calls
- Update
LayoutPresetsconstants
Phase 3: Create EditorPanel Interface (4-6 hours) ✅ COMPLETE
- Define
EditorPanelbase class withPanelCategory - Add pin support to
PanelWindow - Implement central drawing in
PanelManager
Phase 4: Migrate Dungeon Editor (Proof of Concept, 8-12 hours) ✅ COMPLETE
- Convert all 9 Dungeon panels to
EditorPanelimplementations - Register with
PanelManager::EmplacePanel<> - Remove manual drawing from
DungeonEditorV2::Update() - Verify LayoutManager docking works
Phase 5: Migrate Remaining Editors (2-4 hours each) ✅ COMPLETE
Priority order based on panel count:
- Palette Editor (11 panels) ✅ - Has
PaletteGroupPanelhierarchy - Overworld Editor (9 panels) ✅ - Most complex, has main canvas
- Graphics Editor (5 panels) ✅ - Mix of legacy and new
- Agent Editor (8 panels) ✅ - Pure panel-based
- Music Editor (7 panels + dynamic) ✅ - Has dynamic song panels
- Screen Editor (5 panels) ✅ - Straightforward
- Message Editor (4 panels) ✅ - Straightforward
- Sprite Editor (2 panels) ✅ - Simple
- Assembly Editor (2 panels) - Deferred (project-based workflow)
Part 8: Summary Statistics & Default Visibility
8.1 Panel Counts by Category
| Category | Static Panels | Resource Panels | Default Visible | Notes |
|---|---|---|---|---|
| Overworld | 10 | per-map | 5 | Canvas, Tile16, Area GFX, Usage, Properties |
| Dungeon | 9 | per-room | 5 | Controls, Selector, Matrix, GFX, Object Editor |
| Graphics | 9 | per-sheet | 4 | Browser, Pixel, Palette, 3D Objects |
| Palette | 11 | — | 5 | Controls, OW Main, Dungeon, Sprites, Quick |
| Music | 7 | per-song | 3 | Browser, Tracker, Instruments |
| Screen | 5 | — | 3 | Dungeon Maps, Inventory, Title |
| Sprite | 2 | per-sprite | 1 | Vanilla Editor |
| Message | 4 | — | 3 | List, Editor, Font Atlas |
| Assembly | 2 | per-file | 1 | User-initiated only |
| Agent | 8 | — | 1 | User-initiated only |
| Emulator | 11 | — | 6 | CPU, PPU, Perf, States, Keys, Controller |
| Memory | 1 | — | 1 | User-initiated only |
| TOTAL | ~80 | dynamic | 35 | ~44% visible by default |
8.2 Default Visibility Rationale
Philosophy: Show panels that provide immediate value without overwhelming the user.
| Default ON | Reason |
|---|---|
| Main canvas/selector | Core editing workflow |
| Graphics preview | Visual feedback while editing |
| Object/entity editors | Primary editing task |
| Usage statistics | Optimization guidance |
| Playback controls | Audio editors need transport |
| Default OFF | Reason |
|---|---|
| Debug panels | Developer-focused |
| Legacy panels | Replaced by newer versions |
| Advanced tools | Power users enable as needed |
| Agent/AI panels | Requires configuration first |
| Assembly panels | Project-based workflow |
8.3 First-Time User Experience
When a ROM is first loaded, show a balanced workspace:
┌─────────────────────────────────────────────────────────────────────────────┐
│ RECOMMENDED DEFAULT LAYOUT │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌────────────────────────────────────┐ ┌──────────────────┐ │
│ │ Activity │ │ │ │ Properties │ │
│ │ Bar │ │ Main Canvas │ │ Panel │ │
│ │ │ │ (Overworld/Room) │ │ │ │
│ │ [OW] │ │ │ │ - Map Info │ │
│ │ [Dun] │ │ │ │ - Palette │ │
│ │ [Gfx] │ │ │ │ - GFX Group │ │
│ │ [Pal] │ │ │ │ │ │
│ │ [Mus] │ ├────────────────────────────────────┤ ├──────────────────┤ │
│ │ [Scr] │ │ Tile Selector │ Usage Stats │ │ Graphics │ │
│ │ [Spr] │ │ │ │ │ Preview │ │
│ │ [Msg] │ │ [Tile16 Grid] │ [Heat Map] │ │ │ │
│ │ [Asm] │ │ │ │ │ [Current Sheet] │ │
│ │ │ └──────────────────┴────────────────┘ └──────────────────┘ │
│ │ ──────── │ │
│ │ [Emu] │ Status Bar: ROM Name | Session | Unsaved Changes │
│ │ [Set] │ │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Appendix A: File Changes Summary
| File | Changes Required |
|---|---|
src/app/gui/app/editor_layout.h |
Rename class, add pin support |
src/app/gui/app/editor_layout.cc |
Update implementation |
src/app/editor/system/editor_card_registry.h |
Rename to panel_manager.h |
src/app/editor/system/editor_card_registry.cc |
Rename, add central drawing |
src/app/editor/ui/layout_presets.h |
Rename Card → Panel constants |
src/app/editor/menu/activity_bar.cc |
Update to use PanelManager |
src/app/editor/*/[editor].cc (24 files) |
Update registrations |
Appendix B: Naming Convention
Static Panel IDs
- Format:
{category}.{name}(lowercase, underscores) - Examples:
dungeon.room_selector,palette.ow_main
Resource Panel IDs
- Format:
[s{session}.]{category}.{resource}_{id}[.{subpanel}] - Session prefix only when
session_count_ > 1
| Pattern | Example | Description |
|---|---|---|
{cat}.{res}_{id} |
dungeon.room_42 |
Single session |
s{n}.{cat}.{res}_{id} |
s0.dungeon.room_42 |
Multi-session |
s{n}.{cat}.{res}_{id}.{sub} |
s1.music.song_5.piano_roll |
Subpanel |
Window Titles
- Static panels:
{icon} {Display Name} - Resource panels:
{icon} {Resource Type} {ID} - Session suffix:
[S{n}]when multi-session
| Panel Type | Single Session | Multi-Session |
|---|---|---|
| Static | 🏰 Room Selector |
🏰 Room Selector [S0] |
| Resource | 🚪 Room 42 |
🚪 Room 42 [S1] |
| Subpanel | 🎹 Song 5 Piano Roll |
🎹 Song 5 Piano Roll [S0] |
Categories (for ActivityBar grouping)
- Match
EditorType: Overworld, Dungeon, Graphics, Palette, Music, Screen, Sprite, Message, Assembly, Agent, Emulator, Memory
Resource Types (for resource panels)
| Category | Resource Types |
|---|---|
| Dungeon | room |
| Music | song, instrument, sample |
| Overworld | map |
| Graphics | sheet |
| Sprite | vanilla, custom |
| Message | text |
| Assembly | file |
Appendix C: ResourcePanel Interface
namespace yaze::editor {
/**
* @class ResourcePanel
* @brief Base class for panels that edit specific ROM resources
*
* A ResourcePanel represents a window for editing a specific piece of
* data within a ROM, such as a dungeon room, a song, or a graphics sheet.
*
* Key Features:
* - Session-aware: Can distinguish between same resource in different ROMs
* - Multi-instance: Multiple resources can be open simultaneously
* - LRU managed: Oldest panels auto-close when limit reached
*
* Subclasses:
* - DungeonRoomPanel: Edits a specific room (0-295)
* - MusicSongPanel: Edits a specific song with tracker/piano roll
* - GraphicsSheetPanel: Edits a specific GFX sheet
* - OverworldMapPanel: Edits a specific overworld map
*/
class ResourcePanel : public EditorPanel {
public:
// ========== Resource Identity ==========
/// The numeric ID of the resource (room_id, song_index, sheet_id, etc.)
virtual int GetResourceId() const = 0;
/// The resource type name (e.g., "room", "song", "sheet")
virtual std::string GetResourceType() const = 0;
/// Human-readable resource name (e.g., "Hyrule Castle Entrance")
virtual std::string GetResourceName() const {
return absl::StrFormat("%s %d", GetResourceType(), GetResourceId());
}
// ========== Panel Identity (from EditorPanel) ==========
std::string GetId() const override {
// Generated from resource type and ID
return absl::StrFormat("%s.%s_%d",
GetEditorCategory(), GetResourceType(), GetResourceId());
}
std::string GetDisplayName() const override {
return GetResourceName();
}
// ========== Behavior ==========
/// Resource panels are always editor-bound
PanelCategory GetPanelCategory() const override {
return PanelCategory::EditorBound;
}
/// Allow multiple resource panels of same type
virtual bool AllowMultipleInstances() const { return true; }
/// Get the session ID this resource belongs to
virtual size_t GetSessionId() const { return session_id_; }
// ========== Lifecycle ==========
/// Called when resource data changes externally
virtual void OnResourceModified() {}
/// Called when resource is deleted from ROM
virtual void OnResourceDeleted() {
// Default: close the panel
}
protected:
size_t session_id_ = 0;
};
/**
* @brief Example: Dungeon Room Panel
*/
class DungeonRoomPanel : public ResourcePanel {
public:
DungeonRoomPanel(size_t session_id, int room_id,
zelda3::Room* room, DungeonCanvasViewer* viewer)
: room_id_(room_id), room_(room), viewer_(viewer) {
session_id_ = session_id;
}
int GetResourceId() const override { return room_id_; }
std::string GetResourceType() const override { return "room"; }
std::string GetIcon() const override { return ICON_MD_DOOR_FRONT; }
std::string GetEditorCategory() const override { return "Dungeon"; }
void Draw(bool* p_open) override {
// Draw room canvas with objects, sprites, etc.
viewer_->DrawRoom(room_id_, p_open);
}
private:
int room_id_;
zelda3::Room* room_;
DungeonCanvasViewer* viewer_;
};
} // namespace yaze::editor
Appendix D: Recommended Default Panels by Editor
| Editor | Panels Visible by Default | Rationale |
|---|---|---|
| Overworld | Canvas, Tile16, Area GFX, Usage Stats, Properties | Full tile editing workflow |
| Dungeon | Controls, Selector, Matrix, Room GFX, Object Editor | Room editing + visual navigation |
| Graphics | Sheet Browser, Pixel Editor, Palette Controls, 3D Objects | Complete pixel art workflow |
| Palette | Controls, OW Main, Dungeon Main, Sprites, Quick Access | Color editing across contexts |
| Music | Song Browser, Tracker, Instrument Editor | Music composition workflow |
| Screen | Dungeon Maps, Inventory, Title Screen | Most commonly edited screens |
| Sprite | Vanilla Editor | View-only by default, editing opt-in |
| Message | Message List, Message Editor, Font Atlas | Text editing workflow |
| Assembly | None | Project-based, user-initiated |
| Agent | None | Requires API config first |
| Emulator | CPU Debugger, PPU, Performance, Save States, Keys, Controller | Debugging + playback |
| Memory | None | Power user feature |