994 lines
29 KiB
Markdown
994 lines
29 KiB
Markdown
# UI/UX Improvement Plan: IDE-like Interface
|
|
|
|
> Terminology update: ongoing refactor renames Card → Panel. References to
|
|
> `CardInfo`/`EditorCardRegistry` should be read as
|
|
> `PanelDescriptor`/`PanelManager` going forward.
|
|
|
|
**Created**: 2025-01-25
|
|
**Status**: Design Phase
|
|
**Priority**: High
|
|
**Target**: Make yaze feel like VSCode/JetBrains IDEs
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines comprehensive UI/UX improvements to transform yaze into a professional, IDE-like application with consistent theming, responsive layouts, proper disabled states, and Material Design principles throughout.
|
|
|
|
**Key Improvements**:
|
|
1. Enhanced sidebar system with responsive sizing, badges, and disabled states
|
|
2. Improved menu bar with proper precondition checks and consistent spacing
|
|
3. Responsive panel system that adapts to window size
|
|
4. Extended AgentUITheme for application-wide consistency
|
|
5. Status cluster enhancements for better visual feedback
|
|
|
|
---
|
|
|
|
## 1. Sidebar System Improvements
|
|
|
|
### Current Issues
|
|
- No visual feedback for disabled cards (when ROM not loaded)
|
|
- Fixed sizing doesn't respond to window height changes
|
|
- No badge indicators for notifications/counts
|
|
- Hover effects are basic (only tooltip)
|
|
- Missing selection state visual feedback
|
|
- No category headers/separators for large card lists
|
|
|
|
### Proposed Solutions
|
|
|
|
#### 1.1 Disabled State System
|
|
|
|
**File**: `src/app/editor/system/editor_card_registry.h`
|
|
|
|
```cpp
|
|
struct CardInfo {
|
|
// ... existing fields ...
|
|
std::function<bool()> enabled_condition; // NEW: Card enable condition
|
|
int notification_count = 0; // NEW: Badge count (0 = no badge)
|
|
};
|
|
```
|
|
|
|
**File**: `src/app/editor/system/editor_card_registry.cc` (DrawSidebar)
|
|
|
|
```cpp
|
|
// In the card drawing loop:
|
|
bool is_active = card.visibility_flag && *card.visibility_flag;
|
|
bool is_enabled = card.enabled_condition ? card.enabled_condition() : true;
|
|
|
|
if (!is_enabled) {
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.4f); // Dim disabled cards
|
|
}
|
|
|
|
// ... draw button ...
|
|
|
|
if (!is_enabled) {
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
if (!is_enabled) {
|
|
ImGui::SetTooltip("%s\nRequires ROM to be loaded", card.display_name.c_str());
|
|
} else {
|
|
// Normal tooltip
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 1.2 Notification Badges
|
|
|
|
**New Helper Function**: `src/app/editor/system/editor_card_registry.cc`
|
|
|
|
```cpp
|
|
void EditorCardRegistry::DrawCardWithBadge(const CardInfo& card, bool is_active) {
|
|
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
|
|
|
// Draw the main button
|
|
// ... existing button code ...
|
|
|
|
// Draw notification badge if count > 0
|
|
if (card.notification_count > 0) {
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
ImVec2 button_max = ImGui::GetItemRectMax();
|
|
|
|
// Badge position (top-right corner of button)
|
|
float badge_radius = 8.0f;
|
|
ImVec2 badge_pos(button_max.x - badge_radius, button_max.y - 32.0f + badge_radius);
|
|
|
|
// Draw badge circle
|
|
ImVec4 error_color = gui::ConvertColorToImVec4(theme.error);
|
|
draw_list->AddCircleFilled(badge_pos, badge_radius, ImGui::GetColorU32(error_color));
|
|
|
|
// Draw count text
|
|
if (card.notification_count < 100) {
|
|
char badge_text[4];
|
|
snprintf(badge_text, sizeof(badge_text), "%d", card.notification_count);
|
|
|
|
ImVec2 text_size = ImGui::CalcTextSize(badge_text);
|
|
ImVec2 text_pos(badge_pos.x - text_size.x * 0.5f,
|
|
badge_pos.y - text_size.y * 0.5f);
|
|
|
|
draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), badge_text);
|
|
} else {
|
|
draw_list->AddText(
|
|
ImVec2(badge_pos.x - 6, badge_pos.y - 6),
|
|
IM_COL32(255, 255, 255, 255), "!");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 1.3 Enhanced Hover Effects
|
|
|
|
**Style Improvements**:
|
|
|
|
```cpp
|
|
// In DrawSidebar, replace existing button style:
|
|
if (is_active) {
|
|
ImGui::PushStyleColor(ImGuiCol_Button,
|
|
ImVec4(accent_color.x, accent_color.y, accent_color.z, 0.6f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
|
ImVec4(accent_color.x, accent_color.y, accent_color.z, 0.8f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, accent_color);
|
|
|
|
// Add glow effect
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
|
|
} else {
|
|
ImGui::PushStyleColor(ImGuiCol_Button, button_bg);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
|
ImVec4(accent_color.x, accent_color.y, accent_color.z, 0.3f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
|
gui::ConvertColorToImVec4(theme.button_active));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);
|
|
}
|
|
```
|
|
|
|
#### 1.4 Responsive Sizing
|
|
|
|
**Current code calculates fixed heights**. Improve with dynamic calculations:
|
|
|
|
```cpp
|
|
// Replace fixed utility_section_height calculation:
|
|
const float utility_section_height = 4 * 40.0f + 80.0f;
|
|
|
|
// With dynamic calculation:
|
|
const float button_height = 40.0f * theme.widget_height_multiplier;
|
|
const float spacing = ImGui::GetStyle().ItemSpacing.y;
|
|
const float utility_button_count = 4;
|
|
const float utility_section_height =
|
|
(utility_button_count * (button_height + spacing)) +
|
|
80.0f * theme.panel_padding_multiplier;
|
|
|
|
const float current_y = ImGui::GetCursorPosY();
|
|
const float available_height = viewport_height - current_y - utility_section_height;
|
|
```
|
|
|
|
#### 1.5 Category Headers/Separators
|
|
|
|
**For editors with many cards**, add collapsible sections:
|
|
|
|
```cpp
|
|
void EditorCardRegistry::DrawSidebarWithSections(
|
|
size_t session_id,
|
|
const std::string& category,
|
|
const std::map<std::string, std::vector<CardInfo>>& card_sections) {
|
|
|
|
for (const auto& [section_name, cards] : card_sections) {
|
|
if (ImGui::CollapsingHeader(section_name.c_str(),
|
|
ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
for (const auto& card : cards) {
|
|
DrawCardWithBadge(card, /* is_active */ ...);
|
|
}
|
|
}
|
|
ImGui::Spacing();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Menu Bar System Improvements
|
|
|
|
### Current Issues
|
|
- Many menu items don't gray out when preconditions not met
|
|
- Inconsistent spacing/padding between menu items
|
|
- Status cluster feels cramped
|
|
- No visual separation between menu groups
|
|
|
|
### Proposed Solutions
|
|
|
|
#### 2.1 Comprehensive Enabled Conditions
|
|
|
|
**File**: `src/app/editor/system/menu_orchestrator.cc`
|
|
|
|
Add helper methods:
|
|
|
|
```cpp
|
|
// Existing helpers (keep these):
|
|
bool MenuOrchestrator::HasActiveRom() const;
|
|
bool MenuOrchestrator::CanSaveRom() const;
|
|
bool MenuOrchestrator::HasCurrentEditor() const;
|
|
bool MenuOrchestrator::HasMultipleSessions() const;
|
|
|
|
// NEW helpers:
|
|
bool MenuOrchestrator::HasActiveSelection() const {
|
|
// Check if current editor has selected entities/objects
|
|
// Delegate to EditorManager to query active editor
|
|
}
|
|
|
|
bool MenuOrchestrator::CanUndo() const {
|
|
// Check if undo stack has entries
|
|
return HasCurrentEditor() && editor_manager_->CanUndo();
|
|
}
|
|
|
|
bool MenuOrchestrator::CanRedo() const {
|
|
return HasCurrentEditor() && editor_manager_->CanRedo();
|
|
}
|
|
|
|
bool MenuOrchestrator::HasClipboardData() const {
|
|
// Check if clipboard has paste-able data
|
|
return HasCurrentEditor() && editor_manager_->HasClipboardData();
|
|
}
|
|
|
|
bool MenuOrchestrator::IsRomModified() const {
|
|
return HasActiveRom() && rom_manager_.GetCurrentRom()->dirty();
|
|
}
|
|
```
|
|
|
|
#### 2.2 Apply Conditions to All Menu Items
|
|
|
|
**File**: `src/app/editor/system/menu_orchestrator.cc`
|
|
|
|
```cpp
|
|
void MenuOrchestrator::BuildFileMenu() {
|
|
menu_builder_.BeginMenu("File", ICON_MD_FOLDER);
|
|
|
|
menu_builder_.Item(
|
|
"Open ROM", ICON_MD_FILE_OPEN,
|
|
[this]() { OnOpenRom(); },
|
|
"Ctrl+O",
|
|
[]() { return true; } // Always enabled
|
|
);
|
|
|
|
menu_builder_.Item(
|
|
"Save ROM", ICON_MD_SAVE,
|
|
[this]() { OnSaveRom(); },
|
|
"Ctrl+S",
|
|
[this]() { return CanSaveRom(); } // EXISTING
|
|
);
|
|
|
|
menu_builder_.Item(
|
|
"Save ROM As...", ICON_MD_SAVE_AS,
|
|
[this]() { OnSaveRomAs(); },
|
|
"Ctrl+Shift+S",
|
|
[this]() { return HasActiveRom(); } // NEW
|
|
);
|
|
|
|
menu_builder_.Separator();
|
|
|
|
menu_builder_.Item(
|
|
"Create Project", ICON_MD_CREATE_NEW_FOLDER,
|
|
[this]() { OnCreateProject(); },
|
|
"Ctrl+Shift+N",
|
|
[this]() { return HasActiveRom(); } // NEW - requires ROM first
|
|
);
|
|
|
|
// ... continue for all items
|
|
|
|
menu_builder_.EndMenu();
|
|
}
|
|
|
|
void MenuOrchestrator::BuildEditMenu() {
|
|
menu_builder_.BeginMenu("Edit", ICON_MD_EDIT);
|
|
|
|
menu_builder_.Item(
|
|
"Undo", ICON_MD_UNDO,
|
|
[this]() { OnUndo(); },
|
|
"Ctrl+Z",
|
|
[this]() { return CanUndo(); } // NEW
|
|
);
|
|
|
|
menu_builder_.Item(
|
|
"Redo", ICON_MD_REDO,
|
|
[this]() { OnRedo(); },
|
|
"Ctrl+Y",
|
|
[this]() { return CanRedo(); } // NEW
|
|
);
|
|
|
|
menu_builder_.Separator();
|
|
|
|
menu_builder_.Item(
|
|
"Cut", ICON_MD_CONTENT_CUT,
|
|
[this]() { OnCut(); },
|
|
"Ctrl+X",
|
|
[this]() { return HasActiveSelection(); } // NEW
|
|
);
|
|
|
|
menu_builder_.Item(
|
|
"Copy", ICON_MD_CONTENT_COPY,
|
|
[this]() { OnCopy(); },
|
|
"Ctrl+C",
|
|
[this]() { return HasActiveSelection(); } // NEW
|
|
);
|
|
|
|
menu_builder_.Item(
|
|
"Paste", ICON_MD_CONTENT_PASTE,
|
|
[this]() { OnPaste(); },
|
|
"Ctrl+V",
|
|
[this]() { return HasClipboardData(); } // NEW
|
|
);
|
|
|
|
menu_builder_.EndMenu();
|
|
}
|
|
```
|
|
|
|
#### 2.3 Menu Spacing and Visual Grouping
|
|
|
|
**File**: `src/app/editor/ui/menu_builder.h`
|
|
|
|
Add spacing control methods:
|
|
|
|
```cpp
|
|
class MenuBuilder {
|
|
public:
|
|
// ... existing methods ...
|
|
|
|
// NEW: Visual spacing
|
|
void SeparatorWithSpacing(float height = 4.0f) {
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Dummy(ImVec2(0, height));
|
|
}
|
|
|
|
// NEW: Menu section header (for long menus)
|
|
void SectionHeader(const char* label) {
|
|
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
|
ImGui::PushStyleColor(ImGuiCol_Text,
|
|
gui::ConvertColorToImVec4(theme.text_disabled));
|
|
ImGui::TextUnformatted(label);
|
|
ImGui::PopStyleColor();
|
|
ImGui::Spacing();
|
|
}
|
|
};
|
|
```
|
|
|
|
**Usage**:
|
|
|
|
```cpp
|
|
void MenuOrchestrator::BuildToolsMenu() {
|
|
menu_builder_.BeginMenu("Tools", ICON_MD_BUILD);
|
|
|
|
menu_builder_.SectionHeader("ROM Analysis");
|
|
menu_builder_.Item("ROM Info", ICON_MD_INFO, ...);
|
|
menu_builder_.Item("Validate ROM", ICON_MD_CHECK_CIRCLE, ...);
|
|
|
|
menu_builder_.SeparatorWithSpacing();
|
|
|
|
menu_builder_.SectionHeader("Utilities");
|
|
menu_builder_.Item("Global Search", ICON_MD_SEARCH, ...);
|
|
menu_builder_.Item("Command Palette", ICON_MD_TERMINAL, ...);
|
|
|
|
menu_builder_.EndMenu();
|
|
}
|
|
```
|
|
|
|
#### 2.4 Status Cluster Improvements
|
|
|
|
**File**: `src/app/editor/ui/ui_coordinator.cc`
|
|
|
|
```cpp
|
|
void UICoordinator::DrawMenuBarExtras() {
|
|
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
|
|
|
// Calculate cluster width dynamically
|
|
float cluster_width = 0.0f;
|
|
cluster_width += 30.0f; // Dirty badge
|
|
cluster_width += 30.0f; // Notification bell
|
|
if (session_coordinator_.HasMultipleSessions()) {
|
|
cluster_width += 80.0f; // Session button with name
|
|
}
|
|
cluster_width += 60.0f; // Version
|
|
cluster_width += 20.0f; // Padding
|
|
|
|
// Right-align with proper spacing
|
|
ImGui::SameLine(ImGui::GetWindowWidth() - cluster_width);
|
|
|
|
// Add subtle separator before status cluster
|
|
ImGui::PushStyleColor(ImGuiCol_Separator,
|
|
gui::ConvertColorToImVec4(theme.border));
|
|
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
|
ImGui::PopStyleColor();
|
|
|
|
ImGui::SameLine();
|
|
ImGui::Dummy(ImVec2(8.0f, 0)); // Spacing
|
|
ImGui::SameLine();
|
|
|
|
// 1. Dirty badge (with animation)
|
|
Rom* current_rom = rom_manager_.GetCurrentRom();
|
|
if (current_rom && current_rom->dirty()) {
|
|
DrawDirtyBadge();
|
|
}
|
|
|
|
// 2. Notification bell
|
|
DrawNotificationBell();
|
|
|
|
// 3. Session button (if multiple)
|
|
if (session_coordinator_.HasMultipleSessions()) {
|
|
DrawSessionButton();
|
|
}
|
|
|
|
// 4. Version
|
|
ImGui::SameLine();
|
|
ImGui::PushStyleColor(ImGuiCol_Text,
|
|
gui::ConvertColorToImVec4(theme.text_disabled));
|
|
ImGui::TextUnformatted(ICON_MD_INFO " v0.1.0");
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
void UICoordinator::DrawDirtyBadge() {
|
|
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
|
|
|
// Pulsing animation
|
|
static float pulse_time = 0.0f;
|
|
pulse_time += ImGui::GetIO().DeltaTime;
|
|
float pulse_alpha = 0.7f + 0.3f * sin(pulse_time * 3.0f);
|
|
|
|
ImVec4 warning_color = gui::ConvertColorToImVec4(theme.warning);
|
|
warning_color.w = pulse_alpha;
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
|
ImVec4(warning_color.x, warning_color.y, warning_color.z, 0.3f));
|
|
|
|
if (ImGui::SmallButton(ICON_MD_CIRCLE)) {
|
|
// Quick save on click
|
|
OnSaveRom();
|
|
}
|
|
|
|
ImGui::PopStyleColor(2);
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Unsaved changes\nClick to save (Ctrl+S)");
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. AgentUITheme Extensions
|
|
|
|
### New Theme Properties Needed
|
|
|
|
**File**: `src/app/editor/agent/agent_ui_theme.h`
|
|
|
|
```cpp
|
|
struct AgentUITheme {
|
|
// ... existing fields ...
|
|
|
|
// NEW: Sidebar specific colors
|
|
ImVec4 sidebar_bg;
|
|
ImVec4 sidebar_border;
|
|
ImVec4 sidebar_icon_active;
|
|
ImVec4 sidebar_icon_inactive;
|
|
ImVec4 sidebar_icon_disabled;
|
|
ImVec4 sidebar_icon_hover;
|
|
ImVec4 sidebar_badge_bg;
|
|
ImVec4 sidebar_badge_text;
|
|
|
|
// NEW: Menu bar specific colors
|
|
ImVec4 menu_separator;
|
|
ImVec4 menu_section_header;
|
|
ImVec4 menu_item_disabled;
|
|
|
|
// NEW: Panel sizing (responsive)
|
|
float sidebar_width_base = 48.0f;
|
|
float sidebar_icon_size = 40.0f;
|
|
float sidebar_icon_spacing = 6.0f;
|
|
float panel_padding = 8.0f;
|
|
float status_cluster_spacing = 8.0f;
|
|
|
|
// NEW: Animation timing
|
|
float hover_transition_speed = 0.15f;
|
|
float badge_pulse_speed = 3.0f;
|
|
|
|
static AgentUITheme FromCurrentTheme();
|
|
};
|
|
```
|
|
|
|
**File**: `src/app/editor/agent/agent_ui_theme.cc`
|
|
|
|
```cpp
|
|
AgentUITheme AgentUITheme::FromCurrentTheme() {
|
|
AgentUITheme theme;
|
|
const auto& current = gui::ThemeManager::Get().GetCurrentTheme();
|
|
|
|
// ... existing code ...
|
|
|
|
// NEW: Sidebar colors
|
|
theme.sidebar_bg = ConvertColorToImVec4(current.surface);
|
|
theme.sidebar_border = ConvertColorToImVec4(current.border);
|
|
theme.sidebar_icon_active = ConvertColorToImVec4(current.accent);
|
|
theme.sidebar_icon_inactive = ConvertColorToImVec4(current.button);
|
|
theme.sidebar_icon_disabled = ImVec4(0.3f, 0.3f, 0.3f, 1.0f);
|
|
theme.sidebar_icon_hover = ConvertColorToImVec4(current.button_hovered);
|
|
theme.sidebar_badge_bg = ConvertColorToImVec4(current.error);
|
|
theme.sidebar_badge_text = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
// NEW: Menu bar colors
|
|
theme.menu_separator = ConvertColorToImVec4(current.border);
|
|
theme.menu_section_header = ConvertColorToImVec4(current.text_disabled);
|
|
theme.menu_item_disabled = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
|
|
|
|
// NEW: Responsive sizing from theme
|
|
theme.sidebar_width_base = 48.0f * current.compact_factor;
|
|
theme.sidebar_icon_size = 40.0f * current.widget_height_multiplier;
|
|
theme.sidebar_icon_spacing = 6.0f * current.spacing_multiplier;
|
|
theme.panel_padding = 8.0f * current.panel_padding_multiplier;
|
|
|
|
return theme;
|
|
}
|
|
```
|
|
|
|
### Helper Functions
|
|
|
|
**File**: `src/app/editor/agent/agent_ui_theme.h`
|
|
|
|
```cpp
|
|
namespace AgentUI {
|
|
|
|
// ... existing helpers ...
|
|
|
|
// NEW: Sidebar helpers
|
|
void PushSidebarStyle();
|
|
void PopSidebarStyle();
|
|
|
|
void RenderSidebarButton(const char* icon, bool is_active, bool is_enabled,
|
|
int notification_count = 0);
|
|
|
|
// NEW: Menu helpers
|
|
void PushMenuSectionStyle();
|
|
void PopMenuSectionStyle();
|
|
|
|
// NEW: Status cluster helpers
|
|
void RenderDirtyIndicator(bool dirty);
|
|
void RenderNotificationBadge(int count);
|
|
void RenderSessionIndicator(const char* session_name, bool is_active);
|
|
|
|
} // namespace AgentUI
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Right Panel System Integration
|
|
|
|
### Current State
|
|
- `RightPanelManager` exists and handles agent chat, proposals, settings, help
|
|
- Uses fixed widths per panel type
|
|
- No animation (animating_ flag exists but unused)
|
|
- Good theme integration already
|
|
|
|
### Improvements Needed
|
|
|
|
#### 4.1 Slide Animation
|
|
|
|
**File**: `src/app/editor/ui/right_panel_manager.cc`
|
|
|
|
```cpp
|
|
void RightPanelManager::Draw() {
|
|
if (active_panel_ == PanelType::kNone && panel_animation_ <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
const float viewport_height = viewport->WorkSize.y;
|
|
const float viewport_width = viewport->WorkSize.x;
|
|
|
|
// Animate panel expansion/collapse
|
|
if (animating_) {
|
|
const float animation_speed = 6.0f * ImGui::GetIO().DeltaTime;
|
|
|
|
if (active_panel_ != PanelType::kNone) {
|
|
// Expanding
|
|
panel_animation_ = std::min(1.0f, panel_animation_ + animation_speed);
|
|
if (panel_animation_ >= 1.0f) {
|
|
animating_ = false;
|
|
}
|
|
} else {
|
|
// Collapsing
|
|
panel_animation_ = std::max(0.0f, panel_animation_ - animation_speed);
|
|
if (panel_animation_ <= 0.0f) {
|
|
animating_ = false;
|
|
return; // Fully collapsed, don't draw
|
|
}
|
|
}
|
|
}
|
|
|
|
// Eased animation curve (smooth in/out)
|
|
float eased_progress = panel_animation_ * panel_animation_ * (3.0f - 2.0f * panel_animation_);
|
|
|
|
const float target_width = GetPanelWidth();
|
|
const float current_width = target_width * eased_progress;
|
|
|
|
// ... rest of drawing code, use current_width instead of GetPanelWidth()
|
|
}
|
|
```
|
|
|
|
#### 4.2 Responsive Width Calculation
|
|
|
|
```cpp
|
|
float RightPanelManager::GetPanelWidth() const {
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
const float viewport_width = viewport->WorkSize.x;
|
|
|
|
// Calculate max panel width (30% of viewport, clamped)
|
|
const float max_panel_width = std::min(600.0f, viewport_width * 0.3f);
|
|
|
|
float base_width = 0.0f;
|
|
switch (active_panel_) {
|
|
case PanelType::kAgentChat:
|
|
base_width = agent_chat_width_;
|
|
break;
|
|
case PanelType::kProposals:
|
|
base_width = proposals_width_;
|
|
break;
|
|
case PanelType::kSettings:
|
|
base_width = settings_width_;
|
|
break;
|
|
case PanelType::kHelp:
|
|
base_width = help_width_;
|
|
break;
|
|
default:
|
|
return 0.0f;
|
|
}
|
|
|
|
return std::min(base_width, max_panel_width);
|
|
}
|
|
```
|
|
|
|
#### 4.3 Docking Space Adjustment
|
|
|
|
**File**: `src/app/editor/editor_manager.cc`
|
|
|
|
When drawing the main docking space, reserve space for the right panel:
|
|
|
|
```cpp
|
|
void EditorManager::UpdateMainDockingSpace() {
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
|
|
float sidebar_width = 0.0f;
|
|
if (ui_coordinator_->IsCardSidebarVisible() &&
|
|
!card_registry_.IsSidebarCollapsed()) {
|
|
sidebar_width = EditorCardRegistry::GetSidebarWidth();
|
|
}
|
|
|
|
// NEW: Reserve space for right panel
|
|
float right_panel_width = right_panel_manager_->GetPanelWidth();
|
|
if (right_panel_manager_->IsPanelExpanded()) {
|
|
right_panel_width *= right_panel_manager_->GetAnimationProgress();
|
|
}
|
|
|
|
// Docking space occupies the remaining space
|
|
ImVec2 dockspace_pos(viewport->WorkPos.x + sidebar_width, viewport->WorkPos.y);
|
|
ImVec2 dockspace_size(
|
|
viewport->WorkSize.x - sidebar_width - right_panel_width,
|
|
viewport->WorkSize.y
|
|
);
|
|
|
|
ImGui::SetNextWindowPos(dockspace_pos);
|
|
ImGui::SetNextWindowSize(dockspace_size);
|
|
|
|
// ... rest of docking space setup
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Overall Layout System
|
|
|
|
### 5.1 Layout Zones
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────────┐
|
|
│ Menu Bar (Full Width) [●][🔔][📄][v]│
|
|
├─┬─────────────────────────────────────────────────────────┬───┤
|
|
│S│ │ R │
|
|
│i│ │ i │
|
|
│d│ Main Docking Space │ g │
|
|
│e│ │ h │
|
|
│b│ (Editor Cards) │ t │
|
|
│a│ │ │
|
|
│r│ │ P │
|
|
│ │ │ a │
|
|
│ │ │ n │
|
|
│ │ │ e │
|
|
│ │ │ l │
|
|
│ │ │ │
|
|
└─┴─────────────────────────────────────────────────────────┴───┘
|
|
```
|
|
|
|
### 5.2 Responsive Breakpoints
|
|
|
|
**Add to EditorManager or new LayoutManager class**:
|
|
|
|
```cpp
|
|
enum class LayoutMode {
|
|
kCompact, // < 1280px width
|
|
kNormal, // 1280-1920px
|
|
kWide // > 1920px
|
|
};
|
|
|
|
LayoutMode GetLayoutMode() const {
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
float width = viewport->WorkSize.x;
|
|
|
|
if (width < 1280.0f) return LayoutMode::kCompact;
|
|
if (width < 1920.0f) return LayoutMode::kNormal;
|
|
return LayoutMode::kWide;
|
|
}
|
|
|
|
// Adjust UI based on mode:
|
|
void ApplyLayoutMode(LayoutMode mode) {
|
|
switch (mode) {
|
|
case LayoutMode::kCompact:
|
|
// Auto-collapse sidebar on small screens?
|
|
// Reduce panel widths
|
|
right_panel_manager_->SetPanelWidth(PanelType::kAgentChat, 300.0f);
|
|
break;
|
|
case LayoutMode::kNormal:
|
|
right_panel_manager_->SetPanelWidth(PanelType::kAgentChat, 380.0f);
|
|
break;
|
|
case LayoutMode::kWide:
|
|
right_panel_manager_->SetPanelWidth(PanelType::kAgentChat, 480.0f);
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Implementation Priority Order
|
|
|
|
### Phase 1: Critical Foundation (Week 1)
|
|
**Priority: P0 - Immediate**
|
|
|
|
1. **Extend AgentUITheme** with sidebar/menu colors and sizing
|
|
- Files: `agent_ui_theme.h`, `agent_ui_theme.cc`
|
|
- Effort: 2 hours
|
|
- Benefit: Foundation for all other improvements
|
|
|
|
2. **Add CardInfo enabled_condition field**
|
|
- Files: `editor_card_registry.h`, `editor_card_registry.cc`
|
|
- Effort: 1 hour
|
|
- Benefit: Enables disabled state system
|
|
|
|
3. **Implement disabled state rendering in sidebar**
|
|
- Files: `editor_card_registry.cc` (DrawSidebar)
|
|
- Effort: 3 hours
|
|
- Benefit: Immediate visual feedback improvement
|
|
|
|
### Phase 2: Menu Bar Improvements (Week 1-2)
|
|
**Priority: P1 - High**
|
|
|
|
4. **Add helper methods to MenuOrchestrator**
|
|
- Files: `menu_orchestrator.h`, `menu_orchestrator.cc`
|
|
- Methods: `CanUndo()`, `CanRedo()`, `HasActiveSelection()`, etc.
|
|
- Effort: 4 hours
|
|
- Benefit: Foundation for proper menu item states
|
|
|
|
5. **Apply enabled conditions to all menu items**
|
|
- Files: `menu_orchestrator.cc` (all Build*Menu methods)
|
|
- Effort: 6 hours
|
|
- Benefit: Professional menu behavior
|
|
|
|
6. **Improve status cluster layout and animations**
|
|
- Files: `ui_coordinator.cc` (DrawMenuBarExtras)
|
|
- Effort: 4 hours
|
|
- Benefit: Polished status area
|
|
|
|
### Phase 3: Sidebar Enhancements (Week 2)
|
|
**Priority: P1 - High**
|
|
|
|
7. **Add notification badge system**
|
|
- Files: `editor_card_registry.h`, `editor_card_registry.cc`
|
|
- Effort: 4 hours
|
|
- Benefit: Visual notification system
|
|
|
|
8. **Enhance hover effects and animations**
|
|
- Files: `editor_card_registry.cc` (DrawSidebar)
|
|
- Effort: 3 hours
|
|
- Benefit: More polished interactions
|
|
|
|
9. **Implement responsive sidebar sizing**
|
|
- Files: `editor_card_registry.cc` (DrawSidebar)
|
|
- Effort: 2 hours
|
|
- Benefit: Better UX on different screen sizes
|
|
|
|
### Phase 4: Right Panel System (Week 2-3)
|
|
**Priority: P2 - Medium**
|
|
|
|
10. **Add slide-in/out animation**
|
|
- Files: `right_panel_manager.cc`
|
|
- Effort: 4 hours
|
|
- Benefit: Smooth panel transitions
|
|
|
|
11. **Implement responsive panel widths**
|
|
- Files: `right_panel_manager.cc`
|
|
- Effort: 2 hours
|
|
- Benefit: Adapts to screen size
|
|
|
|
12. **Integrate panel with docking space**
|
|
- Files: `editor_manager.cc`
|
|
- Effort: 3 hours
|
|
- Benefit: Proper space reservation
|
|
|
|
### Phase 5: Polish and Refinement (Week 3-4)
|
|
**Priority: P3 - Nice to Have**
|
|
|
|
13. **Add category headers/separators to sidebar**
|
|
- Files: `editor_card_registry.h`, `editor_card_registry.cc`
|
|
- Effort: 4 hours
|
|
- Benefit: Better organization for large card lists
|
|
|
|
14. **Implement layout breakpoints**
|
|
- Files: New `layout_manager.h/cc` or in `editor_manager.cc`
|
|
- Effort: 6 hours
|
|
- Benefit: Professional responsive design
|
|
|
|
15. **Add menu section headers**
|
|
- Files: `menu_builder.h`, `menu_orchestrator.cc`
|
|
- Effort: 3 hours
|
|
- Benefit: Better menu organization
|
|
|
|
---
|
|
|
|
## 7. New Classes/Methods Summary
|
|
|
|
### New Classes
|
|
- None required (all improvements extend existing classes)
|
|
|
|
### New Methods
|
|
|
|
**EditorCardRegistry**:
|
|
- `void DrawCardWithBadge(const CardInfo& card, bool is_active)`
|
|
- `void DrawSidebarWithSections(size_t session_id, const std::string& category, const std::map<std::string, std::vector<CardInfo>>& card_sections)`
|
|
|
|
**MenuOrchestrator**:
|
|
- `bool HasActiveSelection() const`
|
|
- `bool CanUndo() const`
|
|
- `bool CanRedo() const`
|
|
- `bool HasClipboardData() const`
|
|
- `bool IsRomModified() const`
|
|
|
|
**MenuBuilder**:
|
|
- `void SeparatorWithSpacing(float height = 4.0f)`
|
|
- `void SectionHeader(const char* label)`
|
|
|
|
**UICoordinator**:
|
|
- `void DrawDirtyBadge()`
|
|
|
|
**RightPanelManager**:
|
|
- `float GetAnimationProgress() const { return panel_animation_; }`
|
|
|
|
**AgentUI namespace**:
|
|
- `void PushSidebarStyle()`
|
|
- `void PopSidebarStyle()`
|
|
- `void RenderSidebarButton(const char* icon, bool is_active, bool is_enabled, int notification_count = 0)`
|
|
- `void PushMenuSectionStyle()`
|
|
- `void PopMenuSectionStyle()`
|
|
- `void RenderDirtyIndicator(bool dirty)`
|
|
- `void RenderNotificationBadge(int count)`
|
|
- `void RenderSessionIndicator(const char* session_name, bool is_active)`
|
|
|
|
---
|
|
|
|
## 8. Testing Strategy
|
|
|
|
### Unit Tests
|
|
1. **CardInfo enabled_condition**: Test cards with/without conditions
|
|
2. **Badge rendering**: Test badge count display (0, 1-99, 100+)
|
|
3. **Menu item enabling**: Test all enabled condition helpers
|
|
4. **Panel animation**: Test animation state transitions
|
|
|
|
### Integration Tests
|
|
1. **Sidebar disabled states**: Open/close ROM, verify card states
|
|
2. **Menu bar preconditions**: Test menu items with various editor states
|
|
3. **Right panel animation**: Test panel open/close/switch
|
|
4. **Responsive layout**: Test at different window sizes (1024, 1920, 2560)
|
|
|
|
### Visual Regression Tests
|
|
1. **Sidebar appearance**: Screenshot tests for active/inactive/disabled states
|
|
2. **Menu bar appearance**: Screenshot tests for enabled/disabled items
|
|
3. **Status cluster**: Screenshot tests for dirty/clean, notifications
|
|
4. **Panel transitions**: Record video of animations for smoothness verification
|
|
|
|
---
|
|
|
|
## 9. Migration Notes
|
|
|
|
### Backward Compatibility
|
|
- All changes are additive (new fields/methods)
|
|
- Existing code continues to work
|
|
- `enabled_condition` is optional (defaults to always enabled)
|
|
|
|
### Editor Migration
|
|
Editors should register cards with enabled conditions:
|
|
|
|
```cpp
|
|
// OLD:
|
|
card_registry.RegisterCard(session_id, {
|
|
.card_id = "dungeon.room_selector",
|
|
.display_name = "Room Selector",
|
|
.icon = ICON_MD_GRID_VIEW,
|
|
.category = "Dungeon",
|
|
.visibility_flag = &show_room_selector_
|
|
});
|
|
|
|
// NEW (with enabled condition):
|
|
card_registry.RegisterCard(session_id, {
|
|
.card_id = "dungeon.room_selector",
|
|
.display_name = "Room Selector",
|
|
.icon = ICON_MD_GRID_VIEW,
|
|
.category = "Dungeon",
|
|
.visibility_flag = &show_room_selector_,
|
|
.enabled_condition = [this]() { return rom_->is_loaded(); } // NEW
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Future Enhancements
|
|
|
|
### Beyond Initial Implementation
|
|
1. **Command Palette Integration**: Quick access to all cards/menus
|
|
2. **Keyboard Navigation**: Full keyboard control of sidebar/panels
|
|
3. **Custom Layouts**: Save/restore sidebar configurations
|
|
4. **Accessibility**: Screen reader support, high-contrast mode
|
|
5. **Theming**: Allow users to customize sidebar colors
|
|
6. **Gesture Support**: Swipe to open/close panels on touch devices
|
|
|
|
---
|
|
|
|
## 11. Success Metrics
|
|
|
|
### Measurable Goals
|
|
1. **User feedback**: "Feels like VSCode" in user testing
|
|
2. **Discoverability**: New users find features without documentation
|
|
3. **Efficiency**: Power users can navigate faster with keyboard
|
|
4. **Consistency**: Zero hardcoded colors, all theme-based
|
|
5. **Responsiveness**: Smooth at 60fps on all supported platforms
|
|
|
|
### Acceptance Criteria
|
|
- [ ] All sidebar cards show disabled state when ROM not loaded
|
|
- [ ] All menu items properly gray out based on preconditions
|
|
- [ ] Status cluster is visible and informative
|
|
- [ ] Right panel animates smoothly
|
|
- [ ] No visual glitches during window resize
|
|
- [ ] Theme changes apply to all new UI elements
|
|
- [ ] Keyboard shortcuts work for all major actions
|
|
|
|
---
|
|
|
|
## Appendix A: Code Snippets Repository
|
|
|
|
All code snippets from this document are reference implementations. Actual implementation may vary based on:
|
|
- Performance profiling results
|
|
- User feedback
|
|
- Platform-specific considerations
|
|
- Integration with existing systems
|
|
|
|
## Appendix B: Visual Design References
|
|
|
|
### IDE Inspirations
|
|
- **VSCode**: Sidebar icon layout, status bar clusters
|
|
- **JetBrains IDEs**: Menu organization, tool window badges
|
|
- **Sublime Text**: Minimap, distraction-free mode
|
|
- **Atom**: Theme system, package management UI
|
|
|
|
### Material Design Principles
|
|
- **Elevation**: Use shadows/borders for depth
|
|
- **Motion**: Meaningful animations (not decorative)
|
|
- **Color**: Semantic color usage (error=red, success=green)
|
|
- **Typography**: Clear hierarchy, readable at all sizes
|