diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index 43f2e01d..a03243d0 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -551,45 +551,105 @@ void AgentChatWidget::Draw() { // Modern tabbed interface if (ImGui::BeginTabBar("AgentChatTabs", ImGuiTabBarFlags_None)) { - // Main Chat Tab + // Main Chat Tab - with integrated controls if (ImGui::BeginTabItem(ICON_MD_CHAT " Chat")) { - if (ImGui::BeginTable("#agent_chat_table", 2, - ImGuiTableFlags_BordersInnerV | - ImGuiTableFlags_Resizable | - ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Panels", ImGuiTableColumnFlags_WidthFixed, 400.0f); - ImGui::TableSetupColumn("Conversation", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - ImGui::BeginChild("LeftPanels", ImVec2(0, 0), false); - - if (show_agent_config_) { - RenderAgentConfigPanel(); - } - RenderMultimodalPanel(); - if (show_z3ed_commands_) { - RenderZ3EDCommandPanel(); + // Connection Status Bar at top + ImGui::BeginChild("ConnectionStatusBar", ImVec2(0, 80), true, ImGuiWindowFlags_NoScrollbar); + { + // Provider selection and connection status in one row + ImGui::Text(ICON_MD_SMART_TOY " AI Provider:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(120); + const char* providers[] = { "Mock", "Ollama", "Gemini" }; + int current_provider = (agent_config_.ai_provider == "mock") ? 0 : + (agent_config_.ai_provider == "ollama") ? 1 : 2; + if (ImGui::Combo("##provider", ¤t_provider, providers, 3)) { + agent_config_.ai_provider = (current_provider == 0) ? "mock" : + (current_provider == 1) ? "ollama" : "gemini"; + strncpy(agent_config_.provider_buffer, agent_config_.ai_provider.c_str(), + sizeof(agent_config_.provider_buffer) - 1); } - ImGui::EndChild(); - - ImGui::TableSetColumnIndex(1); - RenderHistory(); - RenderInputBox(); - ImGui::EndTable(); + ImGui::SameLine(); + if (agent_config_.ai_provider == "ollama") { + ImGui::Text(ICON_MD_LINK " Host:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + if (ImGui::InputText("##ollama_host", agent_config_.ollama_host_buffer, + sizeof(agent_config_.ollama_host_buffer))) { + agent_config_.ollama_host = agent_config_.ollama_host_buffer; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_REFRESH " Test Connection")) { + if (toast_manager_) { + toast_manager_->Show("Testing Ollama connection...", ToastType::kInfo); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Test connection to Ollama server"); + } + } else if (agent_config_.ai_provider == "gemini") { + ImGui::Text(ICON_MD_KEY " API Key:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(250); + if (ImGui::InputText("##gemini_key", agent_config_.gemini_key_buffer, + sizeof(agent_config_.gemini_key_buffer), + ImGuiInputTextFlags_Password)) { + agent_config_.gemini_api_key = agent_config_.gemini_key_buffer; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_CHECK " Verify")) { + if (toast_manager_) { + toast_manager_->Show("Verifying Gemini API key...", ToastType::kInfo); + } + } + } else { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(Mock mode - no external connection needed)"); + } + + // Second row: Model selection and quick settings + ImGui::Text(ICON_MD_PSYCHOLOGY " Model:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::InputText("##model", agent_config_.model_buffer, sizeof(agent_config_.model_buffer)); + + ImGui::SameLine(); + ImGui::Checkbox(ICON_MD_VISIBILITY " Reasoning", &agent_config_.show_reasoning); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show agent's reasoning process in responses"); + } + + ImGui::SameLine(); + ImGui::SetNextItemWidth(80); + ImGui::InputInt("Max Iterations", &agent_config_.max_tool_iterations); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Maximum tool call iterations per request"); + } } + ImGui::EndChild(); + + ImGui::Spacing(); + + // Main chat area + RenderHistory(); + RenderInputBox(); + + ImGui::EndTabItem(); + } + + // Commands Tab - Z3ED and Multimodal + if (ImGui::BeginTabItem(ICON_MD_TERMINAL " Commands")) { + RenderZ3EDCommandPanel(); + ImGui::Separator(); + RenderMultimodalPanel(); ImGui::EndTabItem(); } // Collaboration Tab if (ImGui::BeginTabItem(ICON_MD_PEOPLE " Collaboration")) { RenderCollaborationPanel(); - if (show_rom_sync_) { - ImGui::Separator(); - RenderRomSyncPanel(); - } + ImGui::Separator(); + RenderRomSyncPanel(); if (show_snapshot_preview_) { ImGui::Separator(); RenderSnapshotPreviewPanel(); @@ -597,6 +657,12 @@ void AgentChatWidget::Draw() { ImGui::EndTabItem(); } + // Advanced Settings Tab + if (ImGui::BeginTabItem(ICON_MD_SETTINGS " Advanced")) { + RenderAgentConfigPanel(); + ImGui::EndTabItem(); + } + // Proposals Tab if (ImGui::BeginTabItem(ICON_MD_PREVIEW " Proposals")) { RenderProposalManagerPanel(); diff --git a/src/app/editor/editor.h b/src/app/editor/editor.h index 45f2bb28..d7a95d73 100644 --- a/src/app/editor/editor.h +++ b/src/app/editor/editor.h @@ -60,12 +60,13 @@ enum class EditorType { kScreen, kSprite, kMessage, + kHex, kSettings, }; -constexpr std::array kEditorNames = { +constexpr std::array kEditorNames = { "Assembly", "Dungeon", "Graphics", "Music", "Overworld", - "Palette", "Screen", "Sprite", "Message", "Settings", + "Palette", "Screen", "Sprite", "Message", "Hex", "Settings", }; /** diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index a43927a3..2f48e69a 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -327,10 +327,7 @@ void EditorManager::Initialize(const std::string& filename) { // Initialize welcome screen callbacks welcome_screen_.SetOpenRomCallback([this]() { status_ = LoadRom(); - if (status_.ok()) { - show_welcome_screen_ = false; - welcome_screen_manually_closed_ = true; - } + // LoadRom() already handles closing welcome screen and showing editor selection }); welcome_screen_.SetNewProjectCallback([this]() { @@ -753,22 +750,66 @@ absl::Status EditorManager::DrawRomSelector() { void EditorManager::BuildModernMenu() { menu_builder_.Clear(); - // File Menu (no icon to avoid cutoff) + // File Menu - enhanced with ROM features menu_builder_.BeginMenu("File") - .Item("Open", ICON_MD_FILE_OPEN, + .Item("Open ROM", ICON_MD_FILE_OPEN, [this]() { status_ = LoadRom(); }, "Ctrl+O") - .Item("Save", ICON_MD_SAVE, + .Item("Save ROM", ICON_MD_SAVE, [this]() { status_ = SaveRom(); }, "Ctrl+S", [this]() { return current_rom_ && current_rom_->is_loaded(); }) .Item("Save As...", ICON_MD_SAVE_AS, - [this]() { popup_manager_->Show("Save As.."); }) + [this]() { popup_manager_->Show("Save As.."); }, + nullptr, + [this]() { return current_rom_ && current_rom_->is_loaded(); }) .Separator() - .BeginSubMenu("Project", ICON_MD_FOLDER_SPECIAL) - .Item("New Project", ICON_MD_CREATE_NEW_FOLDER, - [this]() { status_ = CreateNewProject(); }) - .Item("Open Project", ICON_MD_FOLDER_OPEN, - [this]() { status_ = OpenProject(); }) - .EndMenu() + .Item("New Project", ICON_MD_CREATE_NEW_FOLDER, + [this]() { status_ = CreateNewProject(); }) + .Item("Open Project", ICON_MD_FOLDER_OPEN, + [this]() { status_ = OpenProject(); }) + .Item("Save Project", ICON_MD_SAVE, + [this]() { status_ = SaveProject(); }, + nullptr, + [this]() { return !current_project_.filepath.empty(); }) + .Item("Save Project As...", ICON_MD_SAVE_AS, + [this]() { status_ = SaveProjectAs(); }, + nullptr, + [this]() { return !current_project_.filepath.empty(); }) + .Separator() + .Item("ROM Information", ICON_MD_INFO, + [this]() { popup_manager_->Show("ROM Info"); }, + nullptr, + [this]() { return current_rom_ && current_rom_->is_loaded(); }) + .Item("Create Backup", ICON_MD_BACKUP, + [this]() { + if (current_rom_ && current_rom_->is_loaded()) { + Rom::SaveSettings settings; + settings.backup = true; + settings.filename = current_rom_->filename(); + status_ = current_rom_->SaveToFile(settings); + if (status_.ok()) { + toast_manager_.Show("Backup created successfully", ToastType::kSuccess); + } + } + }, + nullptr, + [this]() { return current_rom_ && current_rom_->is_loaded(); }) + .Item("Validate ROM", ICON_MD_CHECK_CIRCLE, + [this]() { + if (current_rom_ && current_rom_->is_loaded()) { + auto result = current_project_.Validate(); + if (result.ok()) { + toast_manager_.Show("ROM validation passed", ToastType::kSuccess); + } else { + toast_manager_.Show("ROM validation failed: " + std::string(result.message()), + ToastType::kError); + } + } + }, + nullptr, + [this]() { return current_rom_ && current_rom_->is_loaded(); }) + .Separator() + .Item("Settings", ICON_MD_SETTINGS, + [this]() { current_editor_set_->settings_editor_.set_active(true); }) .Separator() .Item("Quit", ICON_MD_EXIT_TO_APP, [this]() { quit_ = true; }, "Ctrl+Q") @@ -790,203 +831,68 @@ void EditorManager::BuildModernMenu() { .Separator() .Item("Find", ICON_MD_SEARCH, [this]() { if (current_editor_) status_ = current_editor_->Find(); }, "Ctrl+F") - .Separator() - .Item("Settings", ICON_MD_SETTINGS, - [this]() { current_editor_set_->settings_editor_.set_active(true); }) + .Item("Find in Files", ICON_MD_SEARCH, + [this]() { show_global_search_ = true; }, "Ctrl+Shift+F") .EndMenu(); - // View Menu + // View Menu - editors and layout menu_builder_.BeginMenu("View") - .BeginSubMenu("Editors", ICON_MD_APPS) - .Item("Show Editor Selection", ICON_MD_DASHBOARD, - [this]() { show_editor_selection_ = true; }, "Ctrl+E") - .Separator() - .Item("Overworld", ICON_MD_MAP, - [this]() { current_editor_set_->overworld_editor_.set_active(true); }) - .Item("Dungeon", ICON_MD_CASTLE, - [this]() { current_editor_set_->dungeon_editor_.set_active(true); }) - .Item("Graphics", ICON_MD_IMAGE, - [this]() { current_editor_set_->graphics_editor_.set_active(true); }) - .Item("Sprite", ICON_MD_TOYS, - [this]() { current_editor_set_->sprite_editor_.set_active(true); }) - .Item("Palette", ICON_MD_PALETTE, - [this]() { current_editor_set_->palette_editor_.set_active(true); }) - .Item("Message", ICON_MD_CHAT_BUBBLE, - [this]() { current_editor_set_->message_editor_.set_active(true); }) - .Item("Music", ICON_MD_MUSIC_NOTE, - [this]() { current_editor_set_->music_editor_.set_active(true); }) - .EndMenu() + .Item("Editor Selection", ICON_MD_DASHBOARD, + [this]() { show_editor_selection_ = true; }, "Ctrl+E") .Separator() + .Item("Overworld", ICON_MD_MAP, + [this]() { current_editor_set_->overworld_editor_.set_active(true); }, "Ctrl+1") + .Item("Dungeon", ICON_MD_CASTLE, + [this]() { current_editor_set_->dungeon_editor_.set_active(true); }, "Ctrl+2") + .Item("Graphics", ICON_MD_IMAGE, + [this]() { current_editor_set_->graphics_editor_.set_active(true); }, "Ctrl+3") + .Item("Sprites", ICON_MD_TOYS, + [this]() { current_editor_set_->sprite_editor_.set_active(true); }, "Ctrl+4") + .Item("Messages", ICON_MD_CHAT_BUBBLE, + [this]() { current_editor_set_->message_editor_.set_active(true); }, "Ctrl+5") + .Item("Music", ICON_MD_MUSIC_NOTE, + [this]() { current_editor_set_->music_editor_.set_active(true); }, "Ctrl+6") + .Item("Palettes", ICON_MD_PALETTE, + [this]() { current_editor_set_->palette_editor_.set_active(true); }, "Ctrl+7") + .Item("Screens", ICON_MD_TV, + [this]() { current_editor_set_->screen_editor_.set_active(true); }, "Ctrl+8") + .Item("Hex Editor", ICON_MD_DATA_ARRAY, + [this]() { show_memory_editor_ = true; }, "Ctrl+0") + .Separator() + .Item("Save Layout", ICON_MD_SAVE, + [this]() { SaveWorkspaceLayout(); }, "Ctrl+Shift+S") + .Item("Load Layout", ICON_MD_FOLDER_OPEN, + [this]() { LoadWorkspaceLayout(); }, "Ctrl+Shift+O") + .Item("Reset Layout", ICON_MD_RESET_TV, + [this]() { ResetWorkspaceLayout(); }) + .Item("Layout Presets", ICON_MD_BOOKMARK, + [this]() { show_layout_presets_ = true; }) + .Separator() + .Item("Show All Windows", ICON_MD_VISIBILITY, + [this]() { ShowAllWindows(); }) + .Item("Hide All Windows", ICON_MD_VISIBILITY_OFF, + [this]() { HideAllWindows(); }) + .Item("Maximize Current", ICON_MD_FULLSCREEN, + [this]() { MaximizeCurrentWindow(); }, "F11") + .EndMenu(); + + // Tools Menu - developer tools and utilities + menu_builder_.BeginMenu("Tools") .Item("Welcome Screen", ICON_MD_HOME, [this]() { show_welcome_screen_ = true; }) - .Item("Emulator", ICON_MD_GAMEPAD, - [this]() { show_emulator_ = true; }, - nullptr, - nullptr, - [this]() { return show_emulator_; }) .Item("Command Palette", ICON_MD_TERMINAL, [this]() { show_command_palette_ = true; }, "Ctrl+Shift+P") -#ifdef YAZE_WITH_GRPC - .Separator() - .Item("AI Agent Editor", ICON_MD_SMART_TOY, - [this]() { agent_editor_.SetChatActive(true); }, "Ctrl+Shift+A", - nullptr, - [this]() { return agent_editor_.IsChatActive(); }) -#endif - .EndMenu(); - - // Workspace Menu - menu_builder_.BeginMenu("Workspace") - .BeginSubMenu("Sessions", ICON_MD_TAB) - .Item("New Session", ICON_MD_ADD, - [this]() { CreateNewSession(); }, "Ctrl+Shift+N") - .Item("Duplicate Session", ICON_MD_CONTENT_COPY, - [this]() { DuplicateCurrentSession(); }, - nullptr, [this]() { return current_rom_ != nullptr; }) - .Item("Close Session", ICON_MD_CLOSE, - [this]() { CloseCurrentSession(); }, "Ctrl+Shift+W", - [this]() { return sessions_.size() > 1; }) - .Separator() - .Item("Session Switcher", ICON_MD_SWITCH_ACCOUNT, - [this]() { show_session_switcher_ = true; }, "Ctrl+Tab", - [this]() { return sessions_.size() > 1; }) - .Item("Session Manager", ICON_MD_VIEW_LIST, - [this]() { show_session_manager_ = true; }) - .EndMenu() - .Separator() - .BeginSubMenu("Layout", ICON_MD_DASHBOARD) - .Item("Save Layout", ICON_MD_SAVE, - [this]() { SaveWorkspaceLayout(); }, "Ctrl+Shift+S") - .Item("Load Layout", ICON_MD_FOLDER_OPEN, - [this]() { LoadWorkspaceLayout(); }, "Ctrl+Shift+O") - .Item("Reset Layout", ICON_MD_RESET_TV, - [this]() { ResetWorkspaceLayout(); }) - .Separator() - .Item("Layout Presets", ICON_MD_BOOKMARK, - [this]() { show_layout_presets_ = true; }) - .EndMenu() - .BeginSubMenu("Windows", ICON_MD_WINDOW) - .Item("Show All", ICON_MD_VISIBILITY, - [this]() { ShowAllWindows(); }) - .Item("Hide All", ICON_MD_VISIBILITY_OFF, - [this]() { HideAllWindows(); }) - .Separator() - .Item("Maximize Current", ICON_MD_FULLSCREEN, - [this]() { MaximizeCurrentWindow(); }, "F11") - .Item("Restore All", ICON_MD_FULLSCREEN_EXIT, - [this]() { RestoreAllWindows(); }) - .Separator() - .Item("Close All Floating", ICON_MD_CLOSE_FULLSCREEN, - [this]() { CloseAllFloatingWindows(); }) - .EndMenu() + .Item("Emulator", ICON_MD_GAMEPAD, + [this]() { show_emulator_ = true; }, + nullptr, nullptr, + [this]() { return show_emulator_; }) .Separator() .Item("Performance Dashboard", ICON_MD_SPEED, - [this]() { show_performance_dashboard_ = true; }, "Ctrl+Shift+P") - .EndMenu(); - -#ifdef YAZE_WITH_GRPC - // AI Agent Menu - menu_builder_.BeginMenu("Agent") - .Item("Open Agent Chat", ICON_MD_CHAT, - [this]() { agent_editor_.SetChatActive(true); }, "Ctrl+Shift+A") - .Separator() - .BeginSubMenu("Vision & Multimodal", ICON_MD_CAMERA) - .Item("Capture Active Editor", ICON_MD_SCREENSHOT, - [this]() { - std::filesystem::path output; - AgentEditor::CaptureConfig config; - config.mode = AgentEditor::CaptureConfig::CaptureMode::kActiveEditor; - status_ = agent_editor_.CaptureSnapshot(&output, config); - }) - .Item("Capture Full Window", ICON_MD_FULLSCREEN, - [this]() { - std::filesystem::path output; - AgentEditor::CaptureConfig config; - config.mode = AgentEditor::CaptureConfig::CaptureMode::kFullWindow; - status_ = agent_editor_.CaptureSnapshot(&output, config); - }) - .EndMenu() - .Separator() - .Item("Proposal Drawer", ICON_MD_RATE_REVIEW, - [this]() { show_proposal_drawer_ = !show_proposal_drawer_; }, - nullptr, nullptr, - [this]() { return show_proposal_drawer_; }) - .EndMenu(); - - // Collaboration Menu - menu_builder_.BeginMenu("Network") - .BeginSubMenu("Session", ICON_MD_MEETING_ROOM) - .Item("Host Session", ICON_MD_ADD_CIRCLE, - [this]() { - auto result = agent_editor_.HostSession("New Session"); - if (result.ok()) { - toast_manager_.Show("Hosted session: " + result->session_name, - ToastType::kSuccess); - } else { - toast_manager_.Show("Failed to host session: " + std::string(result.status().message()), - ToastType::kError); - } - }) - .Item("Join Session", ICON_MD_LOGIN, - [this]() { popup_manager_->Show("Join Collaboration Session"); }) - .Separator() - .Item("Leave Session", ICON_MD_LOGOUT, - [this]() { - status_ = agent_editor_.LeaveSession(); - if (status_.ok()) { - toast_manager_.Show("Left collaboration session", ToastType::kInfo); - } - }, - nullptr, - [this]() { return agent_editor_.IsInSession(); }) - .Item("Refresh Session", ICON_MD_REFRESH, - [this]() { - auto result = agent_editor_.RefreshSession(); - if (result.ok()) { - toast_manager_.Show("Session refreshed: " + std::to_string(result->participants.size()) + " participants", - ToastType::kSuccess); - } - }, - nullptr, - [this]() { return agent_editor_.IsInSession(); }) - .EndMenu() - .Separator() - .BeginSubMenu("Server", ICON_MD_CLOUD) - .Item("Connect to Server", ICON_MD_CLOUD_UPLOAD, - [this]() { popup_manager_->Show("Connect to Server"); }) - .Item("Disconnect", ICON_MD_CLOUD_OFF, - [this]() { - agent_editor_.DisconnectFromServer(); - toast_manager_.Show("Disconnected from server", ToastType::kInfo); - }, - nullptr, - [this]() { return agent_editor_.IsConnectedToServer(); }) - .EndMenu() - .Separator() - .BeginSubMenu("Mode", ICON_MD_SETTINGS_ETHERNET) - .Item("Local (Filesystem)", ICON_MD_FOLDER, - [this]() { /* Set local mode */ }, - nullptr, nullptr, - [this]() { return agent_editor_.GetCurrentMode() == AgentEditor::CollaborationMode::kLocal; }) - .Item("Network (WebSocket)", ICON_MD_WIFI, - [this]() { /* Set network mode */ }, - nullptr, nullptr, - [this]() { return agent_editor_.GetCurrentMode() == AgentEditor::CollaborationMode::kNetwork; }) - .EndMenu() - .EndMenu(); -#endif - - // Debug Menu - menu_builder_.BeginMenu("Debug") + [this]() { show_performance_dashboard_ = true; }) #ifdef YAZE_ENABLE_TESTING .Item("Test Dashboard", ICON_MD_SCIENCE, [this]() { show_test_dashboard_ = true; }, "Ctrl+T") - .Separator() #endif - .Item("Memory Editor", ICON_MD_MEMORY, - [this]() { show_memory_editor_ = true; }) - .Item("Assembly Editor", ICON_MD_CODE, - [this]() { show_asm_editor_ = true; }) .Separator() .Item("ImGui Demo", ICON_MD_HELP, [this]() { show_imgui_demo_ = true; }, @@ -996,13 +902,114 @@ void EditorManager::BuildModernMenu() { [this]() { show_imgui_metrics_ = true; }, nullptr, nullptr, [this]() { return show_imgui_metrics_; }) -#ifdef YAZE_WITH_GRPC + .EndMenu(); + + // Session Menu - workspace session management + menu_builder_.BeginMenu("Session") + .Item("New Session", ICON_MD_ADD, + [this]() { CreateNewSession(); }, "Ctrl+Shift+N") + .Item("Duplicate Session", ICON_MD_CONTENT_COPY, + [this]() { DuplicateCurrentSession(); }, + nullptr, [this]() { return current_rom_ != nullptr; }) + .Item("Close Session", ICON_MD_CLOSE, + [this]() { CloseCurrentSession(); }, "Ctrl+Shift+W", + [this]() { return sessions_.size() > 1; }) .Separator() - .Item("Agent Chat", ICON_MD_CHAT, - [this]() { agent_editor_.ToggleChat(); }, - nullptr, nullptr, + .Item("Session Switcher", ICON_MD_SWITCH_ACCOUNT, + [this]() { show_session_switcher_ = true; }, "Ctrl+Tab", + [this]() { return sessions_.size() > 1; }) + .Item("Session Manager", ICON_MD_VIEW_LIST, + [this]() { show_session_manager_ = true; }) + .EndMenu(); + + +#ifdef YAZE_WITH_GRPC + // Collaboration Menu - combined Agent + Network features + menu_builder_.BeginMenu("Collaborate") + .Item("AI Agent Chat", ICON_MD_SMART_TOY, + [this]() { agent_editor_.SetChatActive(true); }, "Ctrl+Shift+A", + nullptr, [this]() { return agent_editor_.IsChatActive(); }) + .Item("Proposal Drawer", ICON_MD_RATE_REVIEW, + [this]() { show_proposal_drawer_ = !show_proposal_drawer_; }, + nullptr, nullptr, + [this]() { return show_proposal_drawer_; }) + .Separator() + .Item("Host Session", ICON_MD_ADD_CIRCLE, + [this]() { + auto result = agent_editor_.HostSession("New Session"); + if (result.ok()) { + toast_manager_.Show("Hosted session: " + result->session_name, + ToastType::kSuccess); + } else { + toast_manager_.Show("Failed to host session: " + std::string(result.status().message()), + ToastType::kError); + } + }) + .Item("Join Session", ICON_MD_LOGIN, + [this]() { popup_manager_->Show("Join Collaboration Session"); }) + .Item("Leave Session", ICON_MD_LOGOUT, + [this]() { + status_ = agent_editor_.LeaveSession(); + if (status_.ok()) { + toast_manager_.Show("Left collaboration session", ToastType::kInfo); + } + }, + nullptr, + [this]() { return agent_editor_.IsInSession(); }) + .Item("Refresh Session", ICON_MD_REFRESH, + [this]() { + auto result = agent_editor_.RefreshSession(); + if (result.ok()) { + toast_manager_.Show("Session refreshed: " + std::to_string(result->participants.size()) + " participants", + ToastType::kSuccess); + } + }, + nullptr, + [this]() { return agent_editor_.IsInSession(); }) + .Separator() + .Item("Connect to Server", ICON_MD_CLOUD_UPLOAD, + [this]() { popup_manager_->Show("Connect to Server"); }) + .Item("Disconnect from Server", ICON_MD_CLOUD_OFF, + [this]() { + agent_editor_.DisconnectFromServer(); + toast_manager_.Show("Disconnected from server", ToastType::kInfo); + }, + nullptr, + [this]() { return agent_editor_.IsConnectedToServer(); }) + .Separator() + .Item("Capture Active Editor", ICON_MD_SCREENSHOT, + [this]() { + std::filesystem::path output; + AgentEditor::CaptureConfig config; + config.mode = AgentEditor::CaptureConfig::CaptureMode::kActiveEditor; + status_ = agent_editor_.CaptureSnapshot(&output, config); + }) + .Item("Capture Full Window", ICON_MD_FULLSCREEN, + [this]() { + std::filesystem::path output; + AgentEditor::CaptureConfig config; + config.mode = AgentEditor::CaptureConfig::CaptureMode::kFullWindow; + status_ = agent_editor_.CaptureSnapshot(&output, config); + }) + .Separator() + .Item("Local Mode", ICON_MD_FOLDER, + [this]() { /* Set local mode */ }, + nullptr, nullptr, + [this]() { return agent_editor_.GetCurrentMode() == AgentEditor::CollaborationMode::kLocal; }) + .Item("Network Mode", ICON_MD_WIFI, + [this]() { /* Set network mode */ }, + nullptr, nullptr, + [this]() { return agent_editor_.GetCurrentMode() == AgentEditor::CollaborationMode::kNetwork; }) + .EndMenu(); #endif + + // Debug Menu + menu_builder_.BeginMenu("Debug") + .Item("Memory Editor", ICON_MD_MEMORY, + [this]() { show_memory_editor_ = true; }) + .Item("Assembly Editor", ICON_MD_CODE, + [this]() { show_asm_editor_ = true; }) .EndMenu(); // Help Menu @@ -1842,9 +1849,8 @@ absl::Status EditorManager::LoadRom() { // Hide welcome screen when ROM is successfully loaded - don't reset manual close state show_welcome_screen_ = false; - // Optionally show editor selection dialog after ROM loads - // (Can be disabled via settings if users prefer to manually select editors) - // show_editor_selection_ = true; // Uncomment to auto-show after ROM load + // Show editor selection dialog after ROM loads + show_editor_selection_ = true; return absl::OkStatus(); } @@ -1967,8 +1973,9 @@ absl::Status EditorManager::OpenRomOrProject(const std::string& filename) { current_editor_set_ = &session.editors; RETURN_IF_ERROR(LoadAssets()); - // Reset welcome screen state when ROM is loaded - welcome_screen_manually_closed_ = false; + // Hide welcome screen and show editor selection when ROM is loaded + show_welcome_screen_ = false; + show_editor_selection_ = true; } return absl::OkStatus(); } @@ -2044,6 +2051,10 @@ absl::Status EditorManager::OpenProject() { } RETURN_IF_ERROR(LoadAssets()); + + // Hide welcome screen and show editor selection when project ROM is loaded + show_welcome_screen_ = false; + show_editor_selection_ = true; } // Apply workspace settings diff --git a/src/app/editor/ui/editor_selection_dialog.cc b/src/app/editor/ui/editor_selection_dialog.cc index d34e8442..263b0519 100644 --- a/src/app/editor/ui/editor_selection_dialog.cc +++ b/src/app/editor/ui/editor_selection_dialog.cc @@ -14,41 +14,51 @@ namespace yaze { namespace editor { EditorSelectionDialog::EditorSelectionDialog() { - // Initialize editor metadata + // Initialize editor metadata with distinct colors editors_ = { {EditorType::kOverworld, "Overworld", ICON_MD_MAP, - "Edit overworld maps, entrances, and properties", "Ctrl+1", false, true}, + "Edit overworld maps, entrances, and properties", "Ctrl+1", false, true, + ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green {EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE, - "Design dungeon rooms, layouts, and mechanics", "Ctrl+2", false, true}, + "Design dungeon rooms, layouts, and mechanics", "Ctrl+2", false, true, + ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple {EditorType::kGraphics, "Graphics", ICON_MD_PALETTE, - "Modify tiles, palettes, and graphics sets", "Ctrl+3", false, true}, + "Modify tiles, palettes, and graphics sets", "Ctrl+3", false, true, + ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold {EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS, - "Edit sprite graphics and properties", "Ctrl+4", false, true}, + "Edit sprite graphics and properties", "Ctrl+4", false, true, + ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange {EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE, - "Edit dialogue, signs, and text", "Ctrl+5", false, true}, + "Edit dialogue, signs, and text", "Ctrl+5", false, true, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue {EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE, - "Configure music and sound effects", "Ctrl+6", false, true}, + "Configure music and sound effects", "Ctrl+6", false, true, + ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple {EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS, - "Edit color palettes and animations", "Ctrl+7", false, true}, + "Edit color palettes and animations", "Ctrl+7", false, true, + ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red {EditorType::kScreen, "Screens", ICON_MD_TV, - "Edit title screen and ending screens", "Ctrl+8", false, true}, + "Edit title screen and ending screens", "Ctrl+8", false, true, + ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue {EditorType::kAssembly, "Assembly", ICON_MD_CODE, - "Write and edit assembly code", "Ctrl+9", false, false}, + "Write and edit assembly code", "Ctrl+9", false, false, + ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver - // TODO: Fix this - // {EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY, - // "Direct ROM memory editing", "Ctrl+0", false, true}, + {EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY, + "Direct ROM memory editing and comparison", "Ctrl+0", false, true, + ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green {EditorType::kSettings, "Settings", ICON_MD_SETTINGS, - "Configure ROM and project settings", "", false, true}, + "Configure ROM and project settings", "", false, true, + ImVec4(0.6f, 0.6f, 0.6f, 1.0f)}, // Gray }; LoadRecentEditors(); @@ -66,13 +76,13 @@ bool EditorSelectionDialog::Show(bool* p_open) { } bool editor_selected = false; + bool* window_open = p_open ? p_open : &is_open_; - // Center the dialog + // Set window properties immediately before Begin to prevent them from affecting tooltips ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(900, 600), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(950, 650), ImGuiCond_Appearing); // Slightly larger for better layout - bool* window_open = p_open ? p_open : &is_open_; if (ImGui::Begin("Editor Selection", window_open, ImGuiWindowFlags_NoCollapse)) { DrawWelcomeHeader(); @@ -131,19 +141,26 @@ bool EditorSelectionDialog::Show(bool* p_open) { } void EditorSelectionDialog::DrawWelcomeHeader() { - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Larger font if available + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 header_start = ImGui::GetCursorScreenPos(); - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), - ICON_MD_EDIT " Select an Editor"); + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font + + // Colorful gradient title + ImVec4 title_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); // Triforce gold + ImGui::TextColored(title_color, ICON_MD_EDIT " Select an Editor"); ImGui::PopFont(); - ImGui::TextWrapped("Choose an editor to begin working on your ROM. " + // Subtitle with gradient separator + ImVec2 subtitle_pos = ImGui::GetCursorScreenPos(); + ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), + "Choose an editor to begin working on your ROM. " "You can open multiple editors simultaneously."); } void EditorSelectionDialog::DrawQuickAccessButtons() { - ImGui::Text(ICON_MD_HISTORY " Recently Used"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_HISTORY " Recently Used"); ImGui::Spacing(); for (EditorType type : recent_editors_) { @@ -154,14 +171,20 @@ void EditorSelectionDialog::DrawQuickAccessButtons() { }); if (it != editors_.end()) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.8f, 0.6f)); + // Use editor's theme color for button + ImVec4 color = it->color; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x * 0.5f, color.y * 0.5f, + color.z * 0.5f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x * 0.7f, color.y * 0.7f, + color.z * 0.7f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(), - ImVec2(150, 30))) { + ImVec2(150, 35))) { selected_editor_ = type; } - ImGui::PopStyleColor(); + ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", it->description); @@ -177,44 +200,119 @@ void EditorSelectionDialog::DrawQuickAccessButtons() { void EditorSelectionDialog::DrawEditorCard(const EditorInfo& info, int index) { ImGui::PushID(index); - // Card styling + ImVec2 button_size(180, 120); + ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Card styling with gradients bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(), info.type) != recent_editors_.end(); + // Create gradient background + ImVec4 base_color = info.color; + ImU32 color_top = ImGui::GetColorU32(ImVec4(base_color.x * 0.4f, base_color.y * 0.4f, + base_color.z * 0.4f, 0.8f)); + ImU32 color_bottom = ImGui::GetColorU32(ImVec4(base_color.x * 0.2f, base_color.y * 0.2f, + base_color.z * 0.2f, 0.9f)); + + // Draw gradient card background + draw_list->AddRectFilledMultiColor( + cursor_pos, + ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), + color_top, color_top, color_bottom, color_bottom); + + // Colored border + ImU32 border_color = is_recent + ? ImGui::GetColorU32(ImVec4(base_color.x, base_color.y, base_color.z, 1.0f)) + : ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f, + base_color.z * 0.6f, 0.7f)); + draw_list->AddRect(cursor_pos, + ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), + border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f); + + // Recent indicator badge if (is_recent) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.25f, 0.55f, 0.85f, 0.4f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 0.9f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.35f, 0.65f, 0.95f, 0.8f)); + ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5); + draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color), 16); + ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8)); + ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR); } - // Editor button with icon - ImVec2 button_size(180, 100); - if (ImGui::Button(absl::StrCat(info.icon, "\n", info.name).c_str(), - button_size)) { - selected_editor_ = info.type; + // Make button transparent (we draw our own background) + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(base_color.x * 0.3f, base_color.y * 0.3f, + base_color.z * 0.3f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(base_color.x * 0.5f, base_color.y * 0.5f, + base_color.z * 0.5f, 0.7f)); + + ImGui::SetCursorScreenPos(cursor_pos); + bool clicked = ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size); + bool is_hovered = ImGui::IsItemHovered(); + + ImGui::PopStyleColor(3); + + // Draw icon with colored background circle + ImVec2 icon_center(cursor_pos.x + button_size.x / 2, cursor_pos.y + 30); + ImU32 icon_bg = ImGui::GetColorU32(base_color); + draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32); + + // Draw icon + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon + ImVec2 icon_size = ImGui::CalcTextSize(info.icon); + ImGui::SetCursorScreenPos(ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); + ImGui::TextColored(ImVec4(1, 1, 1, 1), "%s", info.icon); + ImGui::PopFont(); + + // Draw name + ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65)); + ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10); + ImVec2 name_size = ImGui::CalcTextSize(info.name); + ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + (button_size.x - name_size.x) / 2, + cursor_pos.y + 65)); + ImGui::TextColored(base_color, "%s", info.name); + ImGui::PopTextWrapPos(); + + // Draw shortcut hint if available + if (info.shortcut && info.shortcut[0]) { + ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); + ImGui::Text("%s", info.shortcut); + ImGui::PopStyleColor(); } - if (is_recent) { - ImGui::PopStyleColor(3); + // Hover glow effect + if (is_hovered) { + ImU32 glow_color = ImGui::GetColorU32(ImVec4(base_color.x, base_color.y, base_color.z, 0.2f)); + draw_list->AddRectFilled(cursor_pos, + ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), + glow_color, 4.0f); } - // Tooltip with description - if (ImGui::IsItemHovered()) { + // Enhanced tooltip with fixed sizing + if (is_hovered) { + // Force tooltip to have a fixed max width to prevent flickering + ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always); ImGui::BeginTooltip(); - ImGui::Text("%s %s", info.icon, info.name); + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font + ImGui::TextColored(base_color, "%s %s", info.icon, info.name); + ImGui::PopFont(); ImGui::Separator(); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 280); ImGui::TextWrapped("%s", info.description); + ImGui::PopTextWrapPos(); if (info.shortcut && info.shortcut[0]) { ImGui::Spacing(); - ImGui::TextDisabled("Shortcut: %s", info.shortcut); + ImGui::TextColored(base_color, ICON_MD_KEYBOARD " %s", info.shortcut); + } + if (is_recent) { + ImGui::Spacing(); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_STAR " Recently used"); } ImGui::EndTooltip(); } - // Recent indicator - if (is_recent) { - ImGui::SameLine(); - ImGui::TextDisabled(ICON_MD_STAR); + if (clicked) { + selected_editor_ = info.type; } ImGui::PopID(); diff --git a/src/app/editor/ui/editor_selection_dialog.h b/src/app/editor/ui/editor_selection_dialog.h index a6082be0..0783a33e 100644 --- a/src/app/editor/ui/editor_selection_dialog.h +++ b/src/app/editor/ui/editor_selection_dialog.h @@ -6,6 +6,7 @@ #include #include "app/editor/editor.h" +#include "imgui/imgui.h" namespace yaze { namespace editor { @@ -22,6 +23,7 @@ struct EditorInfo { const char* shortcut; bool recently_used = false; bool requires_rom = true; + ImVec4 color = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Theme color for this editor }; /** diff --git a/src/app/editor/ui/welcome_screen.cc b/src/app/editor/ui/welcome_screen.cc index 2e7db518..bb7920b9 100644 --- a/src/app/editor/ui/welcome_screen.cc +++ b/src/app/editor/ui/welcome_screen.cc @@ -179,15 +179,22 @@ bool WelcomeScreen::Show(bool* p_open) { }; TriforceConfig triforce_configs[] = { - {0.10f, 0.15f, 32.0f, 0.035f, 60.0f}, // Top left corner - {0.90f, 0.18f, 28.0f, 0.028f, 55.0f}, // Top right corner - {0.08f, 0.82f, 24.0f, 0.022f, 50.0f}, // Bottom left - {0.92f, 0.78f, 28.0f, 0.030f, 55.0f}, // Bottom right - {0.18f, 0.45f, 24.0f, 0.020f, 45.0f}, // Mid left - {0.82f, 0.52f, 24.0f, 0.020f, 45.0f}, // Mid right - {0.50f, 0.88f, 20.0f, 0.018f, 40.0f}, // Bottom center - {0.30f, 0.25f, 20.0f, 0.015f, 40.0f}, // Upper mid-left - {0.70f, 0.28f, 20.0f, 0.015f, 40.0f}, // Upper mid-right + {0.08f, 0.12f, 32.0f, 0.065f, 80.0f}, // Top left corner + {0.92f, 0.15f, 30.0f, 0.055f, 75.0f}, // Top right corner + {0.06f, 0.85f, 28.0f, 0.050f, 70.0f}, // Bottom left + {0.94f, 0.82f, 30.0f, 0.058f, 75.0f}, // Bottom right + {0.15f, 0.48f, 26.0f, 0.048f, 65.0f}, // Mid left + {0.85f, 0.52f, 26.0f, 0.048f, 65.0f}, // Mid right + {0.50f, 0.92f, 24.0f, 0.040f, 60.0f}, // Bottom center + {0.28f, 0.22f, 22.0f, 0.038f, 55.0f}, // Upper mid-left + {0.72f, 0.25f, 22.0f, 0.038f, 55.0f}, // Upper mid-right + {0.50f, 0.08f, 28.0f, 0.052f, 70.0f}, // Top center + {0.22f, 0.65f, 20.0f, 0.035f, 50.0f}, // Mid-lower left + {0.78f, 0.68f, 20.0f, 0.035f, 50.0f}, // Mid-lower right + {0.12f, 0.35f, 18.0f, 0.030f, 45.0f}, // Upper-mid left + {0.88f, 0.38f, 18.0f, 0.030f, 45.0f}, // Upper-mid right + {0.38f, 0.75f, 16.0f, 0.028f, 40.0f}, // Lower left-center + {0.62f, 0.77f, 16.0f, 0.028f, 40.0f}, // Lower right-center }; // Initialize base positions on first frame @@ -201,42 +208,65 @@ bool WelcomeScreen::Show(bool* p_open) { triforce_positions_initialized_ = true; } - // Update triforce positions based on mouse interaction + // Update triforce positions based on mouse interaction + floating animation for (int i = 0; i < kNumTriforces; ++i) { // Update base position in case window moved/resized float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct; float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct; triforce_base_positions_[i] = ImVec2(base_x, base_y); + // Add floating animation using sine waves with unique frequencies per triforce + float time_offset = i * 0.5f; // Offset each triforce's animation + float float_speed_x = (0.6f + (i % 4) * 0.25f) * triforce_speed_multiplier_; // Apply speed multiplier + float float_speed_y = (0.5f + ((i + 1) % 4) * 0.2f) * triforce_speed_multiplier_; + float float_amount_x = (35.0f + (i % 3) * 20.0f) * triforce_size_multiplier_; // Apply size multiplier + float float_amount_y = (40.0f + ((i + 1) % 3) * 25.0f) * triforce_size_multiplier_; + + // Create complex orbital/figure-8 motion with more variation + float float_x = std::sin(animation_time_ * float_speed_x + time_offset) * float_amount_x; + float float_y = std::cos(animation_time_ * float_speed_y + time_offset * 1.5f) * float_amount_y; + + // Add secondary wave for more complex movement + float_x += std::cos(animation_time_ * float_speed_x * 0.7f + time_offset * 2.0f) * (float_amount_x * 0.3f); + float_y += std::sin(animation_time_ * float_speed_y * 0.6f + time_offset * 1.8f) * (float_amount_y * 0.3f); + // Calculate distance from mouse float dx = triforce_base_positions_[i].x - mouse_pos.x; float dy = triforce_base_positions_[i].y - mouse_pos.y; float dist = std::sqrt(dx * dx + dy * dy); - // Calculate repulsion offset + // Calculate repulsion offset with stronger effect ImVec2 target_pos = triforce_base_positions_[i]; - float repel_radius = 150.0f; // Start repelling within this radius + float repel_radius = 200.0f; // Larger radius for more visible interaction - if (dist < repel_radius && dist > 0.1f) { + // Add floating motion to base position + target_pos.x += float_x; + target_pos.y += float_y; + + // Apply mouse repulsion if enabled + if (triforce_mouse_repel_enabled_ && dist < repel_radius && dist > 0.1f) { // Normalize direction away from mouse float dir_x = dx / dist; float dir_y = dy / dist; - // Stronger repulsion when closer - float repel_strength = (1.0f - dist / repel_radius) * triforce_configs[i].repel_distance; + // Much stronger repulsion when closer with exponential falloff + float normalized_dist = dist / repel_radius; + float repel_strength = (1.0f - normalized_dist * normalized_dist) * triforce_configs[i].repel_distance; target_pos.x += dir_x * repel_strength; target_pos.y += dir_y * repel_strength; } - // Smooth interpolation to target position - float lerp_speed = 6.0f * ImGui::GetIO().DeltaTime; + // Smooth interpolation to target position (faster response) + float lerp_speed = 8.0f * ImGui::GetIO().DeltaTime; triforce_positions_[i].x += (target_pos.x - triforce_positions_[i].x) * lerp_speed; triforce_positions_[i].y += (target_pos.y - triforce_positions_[i].y) * lerp_speed; - // Draw at current position + // Draw at current position with alpha multiplier + float adjusted_alpha = triforce_configs[i].alpha * triforce_alpha_multiplier_; + float adjusted_size = triforce_configs[i].size * triforce_size_multiplier_; DrawTriforceBackground(bg_draw_list, triforce_positions_[i], - triforce_configs[i].size, triforce_configs[i].alpha, 0.0f); + adjusted_size, adjusted_alpha, 0.0f); } DrawHeader(); @@ -318,6 +348,57 @@ bool WelcomeScreen::Show(bool* p_open) { ImGui::Dummy(ImVec2(0, 5)); DrawTipsSection(); + + // Triforce animation settings panel + ImGui::SameLine(ImGui::GetWindowWidth() - 50); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 5); + if (ImGui::SmallButton(show_triforce_settings_ ? ICON_MD_CLOSE : ICON_MD_TUNE)) { + show_triforce_settings_ = !show_triforce_settings_; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Triforce Animation Settings"); + } + + if (show_triforce_settings_) { + ImGui::Separator(); + ImGui::Spacing(); + ImGui::BeginChild("TriforceSettings", ImVec2(0, 120), true, ImGuiWindowFlags_NoScrollbar); + { + ImGui::TextColored(kTriforceGold, ICON_MD_AUTO_AWESOME " Triforce Animation"); + ImGui::Spacing(); + + ImGui::Columns(2, nullptr, false); + + // Left column + ImGui::Text(ICON_MD_OPACITY " Visibility"); + ImGui::SetNextItemWidth(-1); + ImGui::SliderFloat("##visibility", &triforce_alpha_multiplier_, 0.0f, 3.0f, "%.1fx"); + + ImGui::Text(ICON_MD_SPEED " Speed"); + ImGui::SetNextItemWidth(-1); + ImGui::SliderFloat("##speed", &triforce_speed_multiplier_, 0.1f, 3.0f, "%.1fx"); + + ImGui::NextColumn(); + + // Right column + ImGui::Text(ICON_MD_ASPECT_RATIO " Size"); + ImGui::SetNextItemWidth(-1); + ImGui::SliderFloat("##size", &triforce_size_multiplier_, 0.5f, 2.0f, "%.1fx"); + + ImGui::Checkbox(ICON_MD_MOUSE " Mouse Interaction", &triforce_mouse_repel_enabled_); + + ImGui::Columns(1); + + ImGui::Spacing(); + if (ImGui::SmallButton(ICON_MD_REFRESH " Reset to Defaults")) { + triforce_alpha_multiplier_ = 1.0f; + triforce_speed_multiplier_ = 1.0f; + triforce_size_multiplier_ = 1.0f; + triforce_mouse_repel_enabled_ = true; + } + } + ImGui::EndChild(); + } } ImGui::End(); @@ -489,8 +570,8 @@ void WelcomeScreen::DrawRecentProjects() { } // Grid layout for project cards - float card_width = 220.0f; - float card_height = 140.0f; + float card_width = 260.0f; // Increased from 220.0f + float card_height = 120.0f; // Reduced from 140.0f int columns = std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 15))); for (size_t i = 0; i < recent_projects_.size(); ++i) { @@ -504,7 +585,7 @@ void WelcomeScreen::DrawRecentProjects() { void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { ImGui::BeginGroup(); - ImVec2 card_size(220, 140); + ImVec2 card_size(260, 120); // Reduced height from 140 to 120 ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); // Subtle hover scale (only on actual hover, no animation) @@ -555,39 +636,38 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { } // Draw content - ImVec2 content_pos(cursor_pos.x + 15, cursor_pos.y + 15); + ImVec2 content_pos(cursor_pos.x + 12, cursor_pos.y + 12); - // Icon with colored background circle - ImVec2 icon_center(content_pos.x + 20, content_pos.y + 20); + // Icon with colored background circle (smaller and centered) + ImVec2 icon_center(content_pos.x + 16, content_pos.y + 16); ImU32 icon_bg = ImGui::GetColorU32(border_color_base); - draw_list->AddCircleFilled(icon_center, 25, icon_bg, 32); + draw_list->AddCircleFilled(icon_center, 18, icon_bg, 32); // Reduced from 25 to 18 - ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 8)); + // Center the icon properly + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font for icon + ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET); + ImGui::SetCursorScreenPos(ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1)); ImGui::Text(ICON_MD_VIDEOGAME_ASSET); ImGui::PopStyleColor(); + ImGui::PopFont(); // Project name - ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 50, content_pos.y + 15)); - ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 15); + ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 40, content_pos.y + 12)); + ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 12); ImGui::TextColored(kTriforceGold, "%s", project.name.c_str()); ImGui::PopTextWrapPos(); // ROM title - ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 55)); + ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 45)); ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), ICON_MD_GAMEPAD " %s", project.rom_title.c_str()); - // Last modified with clock icon - ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 80)); - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), - ICON_MD_ACCESS_TIME " %s", project.last_modified.c_str()); - - // Path in card - ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 105)); + // Path in card (condensed) + ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 70)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); std::string short_path = project.filepath; - if (short_path.length() > 35) { - short_path = "..." + short_path.substr(short_path.length() - 32); + if (short_path.length() > 38) { + short_path = "..." + short_path.substr(short_path.length() - 35); } ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str()); ImGui::PopStyleColor(); @@ -598,8 +678,8 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { ImGui::TextColored(kMasterSwordBlue, ICON_MD_INFO " Project Details"); ImGui::Separator(); ImGui::Text("Name: %s", project.name.c_str()); + ImGui::Text("ROM: %s", project.rom_title.c_str()); ImGui::Text("Path: %s", project.filepath.c_str()); - ImGui::Text("Modified: %s", project.last_modified.c_str()); ImGui::Separator(); ImGui::TextColored(kTriforceGold, ICON_MD_TOUCH_APP " Click to open"); ImGui::EndTooltip(); @@ -666,8 +746,8 @@ void WelcomeScreen::DrawTemplatesSection() { void WelcomeScreen::DrawTipsSection() { // Static tip (or could rotate based on session start time rather than animation) const char* tips[] = { - "Press Ctrl+P to open the command palette", - "Use z3ed agent for AI-powered ROM editing", + "Press Ctrl+Shift+P to open the command palette", + "Use z3ed agent for AI-powered ROM editing (Ctrl+Shift+A)", "Enable ZSCustomOverworld in Debug menu for expanded features", "Check the Performance Dashboard for optimization insights", "Collaborate in real-time with yaze-server" @@ -693,7 +773,7 @@ void WelcomeScreen::DrawWhatsNew() { ImGui::Spacing(); // Version badge (no animation) - ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED " yaze v0.2.0-alpha"); + ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED "yaze v%s", YAZE_VERSION_STRING); ImGui::Spacing(); // Feature list with icons and colors diff --git a/src/app/editor/ui/welcome_screen.h b/src/app/editor/ui/welcome_screen.h index 2bf85936..01e4f98b 100644 --- a/src/app/editor/ui/welcome_screen.h +++ b/src/app/editor/ui/welcome_screen.h @@ -106,10 +106,17 @@ class WelcomeScreen { int hovered_card_ = -1; // Interactive triforce positions (smooth interpolation) - static constexpr int kNumTriforces = 9; + static constexpr int kNumTriforces = 16; ImVec2 triforce_positions_[kNumTriforces] = {}; ImVec2 triforce_base_positions_[kNumTriforces] = {}; bool triforce_positions_initialized_ = false; + + // Triforce animation settings + bool show_triforce_settings_ = false; + float triforce_alpha_multiplier_ = 1.0f; + float triforce_speed_multiplier_ = 1.0f; + float triforce_size_multiplier_ = 1.0f; + bool triforce_mouse_repel_enabled_ = true; }; } // namespace editor