24 KiB
UI Panel System Architecture
Status: FUNCTIONAL - UX Polish Needed
Owner: ui-architect
Created: 2025-01-25
Last Reviewed: 2025-01-25
Next Review: 2025-02-08
✅ UPDATE (2025-01-25): Core rendering issues RESOLVED. Sidebars and panels now render correctly. Remaining work is UX consistency polish for the right panel system (notifications, proposals, settings panels).
Overview
This document describes the UI sidebar, menu, and panel system implemented for YAZE. The system provides a VSCode-inspired layout with:
- Left Sidebar - Editor card toggles (existing
EditorCardRegistry) - Right Panel System - Sliding panels for Agent Chat, Proposals, Settings (new
RightPanelManager) - Menu Bar System - Reorganized menus with ROM-dependent item states
- Status Cluster - Right-aligned menu bar elements with panel toggles
+------------------------------------------------------------------+
| [≡] File Edit View Tools Window Help [v0.x][●][S][P][🔔] |
+--------+---------------------------------------------+-----------+
| | | |
| LEFT | | RIGHT |
| SIDE | MAIN DOCKING SPACE | PANEL |
| BAR | (adjusts with both sidebars) | |
| (cards)| | - Agent |
| | | - Props |
| | | - Settings|
+--------+---------------------------------------------+-----------+
Component Architecture
1. RightPanelManager (src/app/editor/ui/right_panel_manager.h/cc)
Purpose: Manages right-side sliding panels for ancillary functionality.
Key Types:
enum class PanelType {
kNone = 0, // No panel open
kAgentChat, // AI Agent conversation
kProposals, // Agent proposal review
kSettings, // Application settings
kHelp // Help & documentation
};
Key Methods:
| Method | Description |
|---|---|
TogglePanel(PanelType) |
Toggle specific panel on/off |
OpenPanel(PanelType) |
Open specific panel (closes any active) |
ClosePanel() |
Close currently active panel |
IsPanelExpanded() |
Check if any panel is open |
GetPanelWidth() |
Get current panel width for layout offset |
Draw() |
Render the panel and its contents |
DrawPanelToggleButtons() |
Render toggle buttons for status cluster |
Panel Widths:
- Agent Chat: 380px
- Proposals: 420px
- Settings: 480px
- Help: 350px
Integration Points:
// In EditorManager constructor:
right_panel_manager_ = std::make_unique<RightPanelManager>();
right_panel_manager_->SetToastManager(&toast_manager_);
right_panel_manager_->SetProposalDrawer(&proposal_drawer_);
#ifdef YAZE_WITH_GRPC
right_panel_manager_->SetAgentChatWidget(agent_editor_.GetChatWidget());
#endif
Drawing Flow:
EditorManager::Update()callsright_panel_manager_->Draw()after sidebarUICoordinator::DrawMenuBarExtras()callsDrawPanelToggleButtons()- Panel positions itself at
(viewport_width - panel_width, menu_bar_height)
2. EditorCardRegistry (Sidebar)
File: src/app/editor/system/editor_card_registry.h/cc
Key Constants:
static constexpr float GetSidebarWidth() { return 48.0f; }
static constexpr float GetCollapsedSidebarWidth() { return 16.0f; }
Sidebar State:
bool sidebar_collapsed_ = false;
bool IsSidebarCollapsed() const;
void SetSidebarCollapsed(bool collapsed);
void ToggleSidebarCollapsed();
Sidebar Toggle Button (in EditorManager::DrawMenuBar()):
// Always visible, icon changes based on state
const char* sidebar_icon = card_registry_.IsSidebarCollapsed()
? ICON_MD_MENU
: ICON_MD_MENU_OPEN;
if (ImGui::SmallButton(sidebar_icon)) {
card_registry_.ToggleSidebarCollapsed();
}
3. Dockspace Layout Adjustment
File: src/app/controller.cc
The main dockspace adjusts its position and size based on sidebar/panel state:
// In Controller::OnLoad()
const float left_offset = editor_manager_.GetLeftLayoutOffset();
const float right_offset = editor_manager_.GetRightLayoutOffset();
ImVec2 dockspace_pos = viewport->WorkPos;
ImVec2 dockspace_size = viewport->WorkSize;
dockspace_pos.x += left_offset;
dockspace_size.x -= (left_offset + right_offset);
ImGui::SetNextWindowPos(dockspace_pos);
ImGui::SetNextWindowSize(dockspace_size);
EditorManager Layout Offset Methods:
// Returns sidebar width when visible and expanded
float GetLeftLayoutOffset() const {
if (!ui_coordinator_ || !ui_coordinator_->IsCardSidebarVisible()) {
return 0.0f;
}
return card_registry_.IsSidebarCollapsed() ? 0.0f
: EditorCardRegistry::GetSidebarWidth();
}
// Returns right panel width when expanded
float GetRightLayoutOffset() const {
return right_panel_manager_ ? right_panel_manager_->GetPanelWidth() : 0.0f;
}
4. Menu Bar System
File: src/app/editor/system/menu_orchestrator.cc
Menu Structure:
File - ROM/Project operations
Edit - Undo/Redo/Cut/Copy/Paste
View - Editor shortcuts (ROM-dependent), Display settings, Welcome screen
Tools - Search, Performance, ImGui debug
Window - Sessions, Layouts, Cards, Panels, Workspace presets
Help - Documentation links
Key Changes:
- Cards submenu moved from View → Window menu
- Panels submenu added to Window menu
- ROM-dependent items disabled when no ROM loaded
ROM-Dependent Item Pattern:
menu_builder_
.Item(
"Overworld", ICON_MD_MAP,
[this]() { OnSwitchToEditor(EditorType::kOverworld); }, "Ctrl+1",
[this]() { return HasActiveRom(); }) // Enable condition
Cards Submenu (conditional):
if (HasActiveRom()) {
AddCardsSubmenu();
} else {
if (ImGui::BeginMenu("Cards")) {
ImGui::MenuItem("(No ROM loaded)", nullptr, false, false);
ImGui::EndMenu();
}
}
Panels Submenu:
void MenuOrchestrator::AddPanelsSubmenu() {
if (ImGui::BeginMenu("Panels")) {
#ifdef YAZE_WITH_GRPC
if (ImGui::MenuItem("AI Agent", "Ctrl+Shift+A")) {
OnShowAIAgent();
}
#endif
if (ImGui::MenuItem("Proposals", "Ctrl+Shift+R")) {
OnShowProposalDrawer();
}
if (ImGui::MenuItem("Settings")) {
OnShowSettings();
}
// ...
ImGui::EndMenu();
}
}
5. Status Cluster
File: src/app/editor/ui/ui_coordinator.cc
Order (left to right):
[version] [●dirty] [📄session] [🤖agent] [📋proposals] [⚙settings] [🔔bell] [⛶fullscreen]
Implementation:
void UICoordinator::DrawMenuBarExtras() {
// Calculate cluster width for right alignment
float cluster_width = 280.0f;
ImGui::SameLine(ImGui::GetWindowWidth() - cluster_width);
// 1. Version (leftmost)
ImGui::Text("v%s", version.c_str());
// 2. Dirty badge (when ROM has unsaved changes)
if (current_rom && current_rom->dirty()) {
ImGui::Text(ICON_MD_FIBER_MANUAL_RECORD); // Orange dot
}
// 3. Session button (when 2+ sessions)
if (session_coordinator_.HasMultipleSessions()) {
DrawSessionButton();
}
// 4. Panel toggle buttons
editor_manager_->right_panel_manager()->DrawPanelToggleButtons();
// 5. Notification bell (rightmost)
DrawNotificationBell();
#ifdef __EMSCRIPTEN__
// 6. Menu bar hide button (WASM only)
if (ImGui::SmallButton(ICON_MD_FULLSCREEN)) {
show_menu_bar_ = false;
}
#endif
}
6. WASM Menu Bar Toggle
Files: ui_coordinator.h/cc, controller.cc
Purpose: Allow hiding the menu bar for a cleaner web UI experience.
State:
bool show_menu_bar_ = true; // Default visible
bool IsMenuBarVisible() const;
void SetMenuBarVisible(bool visible);
void ToggleMenuBar();
Controller Integration:
// In OnLoad()
bool show_menu_bar = true;
if (editor_manager_.ui_coordinator()) {
show_menu_bar = editor_manager_.ui_coordinator()->IsMenuBarVisible();
}
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
if (show_menu_bar) {
window_flags |= ImGuiWindowFlags_MenuBar;
}
// ...
if (show_menu_bar) {
editor_manager_.DrawMenuBar();
}
// Draw restore button when hidden
if (!show_menu_bar && editor_manager_.ui_coordinator()) {
editor_manager_.ui_coordinator()->DrawMenuBarRestoreButton();
}
Restore Button:
- Small floating button in top-left corner
- Also responds to Alt key press
7. Dropdown Popup Positioning
Pattern for right-anchored popups:
// Store button position before drawing
ImVec2 button_min = ImGui::GetCursorScreenPos();
if (ImGui::SmallButton(icon)) {
ImGui::OpenPopup("##PopupId");
}
ImVec2 button_max = ImGui::GetItemRectMax();
// Calculate position to prevent overflow
const float popup_width = 320.0f;
const float screen_width = ImGui::GetIO().DisplaySize.x;
const float popup_x = std::min(button_min.x, screen_width - popup_width - 10.0f);
ImGui::SetNextWindowPos(ImVec2(popup_x, button_max.y + 2.0f), ImGuiCond_Appearing);
if (ImGui::BeginPopup("##PopupId")) {
// Popup contents...
ImGui::EndPopup();
}
Data Flow Diagram
┌─────────────────┐
│ Controller │
│ OnLoad() │
└────────┬────────┘
│ GetLeftLayoutOffset()
│ GetRightLayoutOffset()
▼
┌─────────────────┐ ┌──────────────────┐
│ EditorManager │────▶│ EditorCardRegistry│
│ │ │ (Left Sidebar) │
│ DrawMenuBar() │ └──────────────────┘
│ Update() │
└────────┬────────┘
│
├──────────────────────┐
▼ ▼
┌─────────────────┐ ┌───────────────────┐
│ MenuOrchestrator│ │ RightPanelManager │
│ BuildMainMenu() │ │ Draw() │
└────────┬────────┘ └───────────────────┘
│
▼
┌─────────────────┐
│ UICoordinator │
│DrawMenuBarExtras│
│DrawPanelToggles │
└─────────────────┘
Future Improvement Ideas
High Priority
1. Panel Animation
Currently panels appear/disappear instantly. Add smooth slide-in/out animation:
// In RightPanelManager
float target_width_; // Target panel width
float current_width_ = 0.0f; // Animated current width
float animation_speed_ = 8.0f;
void UpdateAnimation(float delta_time) {
float target = (active_panel_ != PanelType::kNone) ? GetPanelWidth() : 0.0f;
current_width_ = ImLerp(current_width_, target, animation_speed_ * delta_time);
}
float GetAnimatedWidth() const {
return current_width_;
}
2. Panel Resizing
Allow users to drag panel edges to resize:
void DrawResizeHandle() {
ImVec2 handle_pos(panel_x - 4.0f, menu_bar_height);
ImVec2 handle_size(8.0f, viewport_height);
if (ImGui::InvisibleButton("##PanelResize", handle_size)) {
resizing_ = true;
}
if (resizing_ && ImGui::IsMouseDown(0)) {
float delta = ImGui::GetIO().MouseDelta.x;
SetPanelWidth(active_panel_, GetPanelWidth() - delta);
}
}
3. Panel Memory/Persistence
Save panel states to user settings:
// In UserSettings
struct PanelSettings {
bool agent_panel_open = false;
float agent_panel_width = 380.0f;
bool proposals_panel_open = false;
float proposals_panel_width = 420.0f;
// ...
};
// On startup
void RightPanelManager::LoadFromSettings(const PanelSettings& settings);
// On panel state change
void RightPanelManager::SaveToSettings(PanelSettings& settings);
4. Multiple Simultaneous Panels
Allow multiple panels to be open side-by-side:
// Replace single active_panel_ with:
std::vector<PanelType> open_panels_;
void TogglePanel(PanelType type) {
auto it = std::find(open_panels_.begin(), open_panels_.end(), type);
if (it != open_panels_.end()) {
open_panels_.erase(it);
} else {
open_panels_.push_back(type);
}
}
float GetTotalPanelWidth() const {
float total = 0.0f;
for (auto panel : open_panels_) {
total += GetPanelWidth(panel);
}
return total;
}
Medium Priority
5. Keyboard Shortcuts for Panels
Add shortcuts to toggle panels directly:
// In ShortcutConfigurator
shortcut_manager_.RegisterShortcut({
.id = "toggle_agent_panel",
.keys = {ImGuiMod_Ctrl | ImGuiMod_Shift, ImGuiKey_A},
.callback = [this]() {
editor_manager_->right_panel_manager()->TogglePanel(
RightPanelManager::PanelType::kAgentChat);
}
});
6. Panel Tab Bar
When multiple panels open, show tabs at top:
void DrawPanelTabBar() {
if (ImGui::BeginTabBar("##PanelTabs")) {
for (auto panel : open_panels_) {
if (ImGui::BeginTabItem(GetPanelTypeName(panel))) {
active_tab_ = panel;
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
}
7. Sidebar Categories Icon Bar
Add VSCode-style icon bar on left edge showing editor categories:
void DrawSidebarIconBar() {
// Vertical strip of category icons
for (const auto& category : GetActiveCategories()) {
bool is_current = (category == current_category_);
if (DrawIconButton(GetCategoryIcon(category), is_current)) {
SetCurrentCategory(category);
}
}
}
8. Floating Panel Mode
Allow panels to be undocked and float as separate windows:
enum class PanelDockMode {
kDocked, // Fixed to right edge
kFloating, // Separate movable window
kMinimized // Collapsed to icon
};
void DrawAsFloatingWindow() {
ImGui::SetNextWindowSize(ImVec2(panel_width_, 400.0f), ImGuiCond_FirstUseEver);
if (ImGui::Begin(GetPanelTypeName(active_panel_), &visible_)) {
DrawPanelContents();
}
ImGui::End();
}
Low Priority / Experimental
9. Panel Presets
Save/load panel configurations:
struct PanelPreset {
std::string name;
std::vector<PanelType> open_panels;
std::map<PanelType, float> panel_widths;
bool sidebar_visible;
};
void SavePreset(const std::string& name);
void LoadPreset(const std::string& name);
10. Context-Sensitive Panel Suggestions
Show relevant panels based on current editor:
void SuggestPanelsForEditor(EditorType type) {
switch (type) {
case EditorType::kOverworld:
ShowPanelHint(PanelType::kSettings, "Tip: Configure overworld flags");
break;
case EditorType::kDungeon:
ShowPanelHint(PanelType::kProposals, "Tip: Review agent room suggestions");
break;
}
}
11. Split Panel View
Allow splitting a panel horizontally to show two contents:
void DrawSplitView() {
float split_pos = viewport_height * split_ratio_;
ImGui::BeginChild("##TopPanel", ImVec2(0, split_pos));
DrawAgentChatPanel();
ImGui::EndChild();
DrawSplitter(&split_ratio_);
ImGui::BeginChild("##BottomPanel");
DrawProposalsPanel();
ImGui::EndChild();
}
12. Mini-Map in Sidebar
Show a visual mini-map of the current editor content:
void DrawMiniMap() {
// Render scaled-down preview of current editor
auto* editor = editor_manager_->GetCurrentEditor();
if (editor && editor->type() == EditorType::kOverworld) {
RenderOverworldMiniMap(sidebar_width_ - 8, 100);
}
}
Testing Checklist
Sidebar Tests
- Toggle button always visible in menu bar
- Icon changes based on collapsed state
- Dockspace adjusts when sidebar expands/collapses
- Cards display correctly in sidebar
- Category switching works
Panel Tests
- Each panel type opens correctly
- Only one panel open at a time
- Panel toggle buttons highlight active panel
- Dockspace adjusts when panel opens/closes
- Close button in panel header works
- Panel respects screen bounds
Menu Tests
- Cards submenu in Window menu
- Panels submenu in Window menu
- Editor shortcuts disabled without ROM
- Card Browser disabled without ROM
- Cards submenu shows placeholder without ROM
Status Cluster Tests
- Order: version, dirty, session, panels, bell
- Panel toggle buttons visible
- Popups anchor to right edge
- Session popup doesn't overflow screen
- Notification popup doesn't overflow screen
WASM Tests
- Menu bar hide button visible
- Menu bar hides when clicked
- Restore button appears when hidden
- Alt key restores menu bar
Known Issues
RESOLVED
- Sidebars Not Rendering (RESOLVED 2025-01-25)
- Status: Fixed
- Root Cause: The sidebar and right panel drawing code was placed AFTER an early return in
EditorManager::Update()that triggered when no ROM was loaded. This meant the sidebar code was never reached. - Fix Applied: Moved sidebar drawing (lines 754-816) and right panel drawing (lines 819-821) to BEFORE the early return at line 721-723. The sidebar now correctly draws:
- No ROM loaded:
DrawPlaceholderSidebar()shows "Open ROM" hint - ROM loaded: Full sidebar with category buttons and card toggles
- No ROM loaded:
- Files Modified:
src/app/editor/editor_manager.cc- restructuredUpdate()method
HIGH PRIORITY - UX Consistency Issues
-
Right Panel System Visual Consistency
- Status: Open - needs design pass
- Symptoms: The three right panel types (Notifications, Proposals, Settings) have inconsistent styling
- Issues:
- Panel headers vary in style and spacing
- Background colors may not perfectly match theme in all cases
- Close button positioning inconsistent
- Panel content padding varies
- Location:
src/app/editor/ui/right_panel_manager.cc - Fix needed:
- Standardize panel header style (icon + title + close button layout)
- Ensure all panels use
theme.surfaceconsistently - Add consistent padding/margins across all panel types
- Consider adding subtle panel title bar with drag handle aesthetic
-
Notification Bell/Panel Integration
- Status: Open - needs review
- Symptoms: Notification dropdown may not align well with right panel system
- Issues:
- Notification popup positioning may conflict with right panels
- Notification styling may differ from panel styling
- Location:
src/app/editor/ui/ui_coordinator.cc-DrawNotificationBell() - Fix needed:
- Consider making notifications a panel type instead of dropdown
- Or ensure dropdown anchors correctly regardless of panel state
-
Proposal Registry Panel
- Status: Open - needs consistency pass
- Symptoms: Proposal drawer may have different UX patterns than other panels
- Location:
src/app/editor/system/proposal_drawer.cc - Fix needed:
- Align proposal UI with other panel styling
- Ensure consistent header, padding, and interaction patterns
MEDIUM PRIORITY
-
Panel Content Not Scrollable: Some panel contents may overflow without scrollbars. Need to wrap content in
ImGui::BeginChild()with scroll flags. -
Settings Panel Integration: The
SettingsEditoris called directly but may need its own layout adaptation for panel context. -
Agent Chat State: When panel closes, the chat widget's
active_state should be managed to pause updates. -
Layout Persistence: Panel states are not persisted across sessions yet.
LOW PRIORITY
- Status Cluster Notification Positioning: The
cluster_widthcalculation (220px) works but could be dynamically calculated for better responsiveness.
Debugging Guide
Sidebar Visibility Issues (RESOLVED)
The sidebar visibility issue has been resolved. The root cause was that sidebar drawing code was placed after an early return in EditorManager::Update(). If similar issues occur in the future:
- Check execution order: Ensure UI drawing code executes BEFORE any early returns
- Use ImGui Metrics Window: Enable via View → ImGui Metrics to verify windows exist
- Check
GetLeftLayoutOffset(): Verify it returns the correct width for dockspace adjustment - Verify visibility flags: Check
IsCardSidebarVisible()andIsSidebarCollapsed()states
To Debug Status Cluster Positioning
- Visualize element bounds:
// After each element in DrawMenuBarExtras():
ImGui::GetForegroundDrawList()->AddRect(
ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
IM_COL32(255, 0, 0, 255));
- Log actual widths:
float actual_width = ImGui::GetCursorPosX() - start_x;
LOG_INFO("StatusCluster", "Actual width used: %f", actual_width);
- Check popup positioning:
// Before ImGui::SetNextWindowPos():
LOG_INFO("Popup", "popup_x=%f, button_max.y=%f", popup_x, button_max.y);
Recent Updates (2025-01-25)
Session 2: Menu Bar & Theme Fixes
Menu Bar Cleanup:
- Removed raw
ImGui::BeginMenu()calls fromAddWindowMenuItems()that were creating root-level "Cards" and "Panels" menus - These were appearing alongside File/Edit/View instead of inside the Window menu
- Cards are now accessible via sidebar; Panels via toggle buttons on right
Theme Integration:
- Updated
DrawPlaceholderSidebar()to usetheme.surfaceandtheme.text_disabled - Updated
EditorCardRegistry::DrawSidebar()to use theme colors - Updated
RightPanelManager::Draw()to use theme colors - Sidebars now match the current application theme
Status Cluster Improvements:
- Restored panel toggle buttons (Agent, Proposals, Settings) on right side
- Reduced item spacing to 2px for more compact layout
- Reduced cluster width to 220px
Session 1: Sidebar/Panel Rendering Fix (RESOLVED)
Root Cause: Sidebar and right panel drawing code was placed AFTER an early return in EditorManager::Update() at line 721-723. When no ROM was loaded, Update() returned early and the sidebar drawing code was never executed.
Fix Applied: Restructured EditorManager::Update() to draw sidebar and right panel BEFORE the early return.
Additional Fixes:
- Sidebars now fill full viewport height (y=0 to bottom)
- Welcome screen centers within dockspace region (accounts for sidebar offsets)
- Added
SetLayoutOffsets()to WelcomeScreen for proper centering
Sidebar Visibility States (NOW WORKING)
The sidebar now correctly shows in three states:
- No ROM: Placeholder sidebar with "Open ROM" hint
- ROM + Editor: Full card sidebar with editor cards
- Collapsed: No sidebar (toggle button shows hamburger icon)
Files Modified
| File | Changes |
|---|---|
src/app/editor/ui/right_panel_manager.h |
NEW - Panel manager header |
src/app/editor/ui/right_panel_manager.cc |
NEW - Panel manager implementation, theme colors |
src/app/editor/editor_library.cmake |
Added right_panel_manager.cc |
src/app/editor/editor_manager.h |
Added RightPanelManager member, layout offset methods |
src/app/editor/editor_manager.cc |
Initialize RightPanelManager, sidebar toggle, draw panel, theme colors, viewport positioning |
src/app/editor/system/menu_orchestrator.h |
Added AddPanelsSubmenu declaration |
src/app/editor/system/menu_orchestrator.cc |
Removed root-level Cards/Panels menus, cleaned up Window menu |
src/app/controller.cc |
Layout offset calculation, menu bar visibility |
src/app/editor/ui/ui_coordinator.h |
Menu bar visibility state |
src/app/editor/ui/ui_coordinator.cc |
Reordered status cluster, panel buttons, compact spacing |
src/app/editor/system/editor_card_registry.cc |
Theme colors for sidebar, viewport positioning |
src/app/editor/ui/welcome_screen.h |
Added SetLayoutOffsets() method |
src/app/editor/ui/welcome_screen.cc |
Dockspace-aware centering with sidebar offsets |
Related Documents
- Sidebar-MenuBar-Sessions Architecture - Original architecture overview
- EditorManager Architecture - Full EditorManager documentation
- Agent Protocol - Agent coordination rules
Contact
For questions about this system, review the coordination board or consult the ui-architect agent persona.