From c495368d6a093def37eca807fdea1ae2cb3d5599 Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 03:38:01 -0400 Subject: [PATCH] feat: Revamp Welcome Screen and EditorManager Menus for Improved User Interaction - Updated the Welcome Screen to include a particle system for enhanced visual effects and a more engaging user experience. - Reduced the number of triforces for a cleaner layout and adjusted animation settings for subtlety. - Streamlined project card dimensions and layout for a more compact display, improving accessibility. - Enhanced the EditorManager menus by adding new items for session management and editor selection, improving workflow efficiency. - Improved the styling and functionality of buttons and inputs across the UI for better user interaction. --- src/app/editor/agent/agent_chat_widget.cc | 232 ++++++++++++------- src/app/editor/editor_manager.cc | 149 +++++++------ src/app/editor/ui/welcome_screen.cc | 259 ++++++++++++---------- src/app/editor/ui/welcome_screen.h | 19 +- 4 files changed, 397 insertions(+), 262 deletions(-) diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index a03243d0..6bd7e7ca 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -553,17 +553,37 @@ void AgentChatWidget::Draw() { if (ImGui::BeginTabBar("AgentChatTabs", ImGuiTabBarFlags_None)) { // Main Chat Tab - with integrated controls if (ImGui::BeginTabItem(ICON_MD_CHAT " Chat")) { - // Connection Status Bar at top - ImGui::BeginChild("ConnectionStatusBar", ImVec2(0, 80), true, ImGuiWindowFlags_NoScrollbar); + // Stylish connection status bar with gradient + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 bar_start = ImGui::GetCursorScreenPos(); + ImVec2 bar_size(ImGui::GetContentRegionAvail().x, 85); + + // Gradient background + ImU32 color_top = ImGui::GetColorU32(ImVec4(0.18f, 0.22f, 0.28f, 1.0f)); + ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.12f, 0.16f, 0.22f, 1.0f)); + draw_list->AddRectFilledMultiColor(bar_start, + ImVec2(bar_start.x + bar_size.x, bar_start.y + bar_size.y), + color_top, color_top, color_bottom, color_bottom); + + // Colored accent bar based on provider + ImVec4 accent_color = (agent_config_.ai_provider == "ollama") ? ImVec4(0.2f, 0.8f, 0.4f, 1.0f) : + (agent_config_.ai_provider == "gemini") ? ImVec4(0.196f, 0.6f, 0.8f, 1.0f) : + ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + draw_list->AddRectFilled(bar_start, ImVec2(bar_start.x + bar_size.x, bar_start.y + 3), + ImGui::GetColorU32(accent_color)); + + ImGui::BeginChild("ConnectionStatusBar", bar_size, false, ImGuiWindowFlags_NoScrollbar); { + ImGui::Spacing(); + // Provider selection and connection status in one row - ImGui::Text(ICON_MD_SMART_TOY " AI Provider:"); + ImGui::TextColored(accent_color, 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)) { + if (ImGui::Combo("##chat_provider_combo", ¤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(), @@ -572,46 +592,53 @@ void AgentChatWidget::Draw() { ImGui::SameLine(); if (agent_config_.ai_provider == "ollama") { - ImGui::Text(ICON_MD_LINK " Host:"); + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f), ICON_MD_LINK " Host:"); ImGui::SameLine(); ImGui::SetNextItemWidth(200); - if (ImGui::InputText("##ollama_host", agent_config_.ollama_host_buffer, + if (ImGui::InputText("##chat_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")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.6f, 0.3f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.8f, 0.4f, 1.0f)); + if (ImGui::Button(ICON_MD_REFRESH " Test##test_ollama_conn")) { if (toast_manager_) { toast_manager_->Show("Testing Ollama connection...", ToastType::kInfo); } } + ImGui::PopStyleColor(2); 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::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_KEY " API Key:"); ImGui::SameLine(); ImGui::SetNextItemWidth(250); - if (ImGui::InputText("##gemini_key", agent_config_.gemini_key_buffer, + if (ImGui::InputText("##chat_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")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + if (ImGui::Button(ICON_MD_CHECK " Verify##verify_gemini_key")) { if (toast_manager_) { toast_manager_->Show("Verifying Gemini API key...", ToastType::kInfo); } } + ImGui::PopStyleColor(2); } else { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(Mock mode - no external connection needed)"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), + ICON_MD_INFO " Mock mode - no external connection needed"); } // Second row: Model selection and quick settings - ImGui::Text(ICON_MD_PSYCHOLOGY " Model:"); + ImGui::TextColored(accent_color, ICON_MD_PSYCHOLOGY " Model:"); ImGui::SameLine(); ImGui::SetNextItemWidth(200); - ImGui::InputText("##model", agent_config_.model_buffer, sizeof(agent_config_.model_buffer)); + ImGui::InputText("##chat_model_input", agent_config_.model_buffer, sizeof(agent_config_.model_buffer)); ImGui::SameLine(); ImGui::Checkbox(ICON_MD_VISIBILITY " Reasoning", &agent_config_.show_reasoning); @@ -619,9 +646,11 @@ void AgentChatWidget::Draw() { ImGui::SetTooltip("Show agent's reasoning process in responses"); } + ImGui::SameLine(); + ImGui::TextColored(accent_color, ICON_MD_LOOP); ImGui::SameLine(); ImGui::SetNextItemWidth(80); - ImGui::InputInt("Max Iterations", &agent_config_.max_tool_iterations); + ImGui::InputInt("##chat_max_iter", &agent_config_.max_tool_iterations); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Maximum tool call iterations per request"); } @@ -770,46 +799,53 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::TableNextRow(); - // LEFT COLUMN: Session Details + // LEFT COLUMN: Session Details with gradient background ImGui::TableSetColumnIndex(0); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.18f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.2f, 0.18f, 0.4f)); ImGui::BeginChild("session_details", ImVec2(0, 180), true); + ImVec4 status_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); const bool connected = collaboration_state_.active; - ImGui::TextColored(connected ? ImVec4(0.4f, 1.0f, 0.4f, 1.0f) - : ImVec4(0.7f, 0.7f, 0.7f, 1.0f), - "%s %s", connected ? "●" : "○", - connected ? "Connected" : "Not connected"); - - ImGui::Separator(); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_INFO " Session Status:"); + ImGui::Spacing(); + if (connected) { + ImGui::TextColored(status_color, ICON_MD_CHECK_CIRCLE " Connected"); + } else { + ImGui::TextDisabled(ICON_MD_CANCEL " Not connected"); + } if (collaboration_state_.mode == CollaborationMode::kNetwork) { - ImGui::Text("Server:"); - ImGui::TextWrapped("%s", collaboration_state_.server_url.c_str()); ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_CLOUD " Server:"); + ImGui::TextWrapped("%s", collaboration_state_.server_url.c_str()); } if (!collaboration_state_.session_name.empty()) { - ImGui::Text("Session:"); - ImGui::TextWrapped("%s", collaboration_state_.session_name.c_str()); ImGui::Spacing(); + ImGui::TextColored(status_color, ICON_MD_LABEL " Session:"); + ImGui::TextWrapped("%s", collaboration_state_.session_name.c_str()); } if (!collaboration_state_.session_id.empty()) { - ImGui::Text("Code:"); + ImGui::Spacing(); + ImGui::TextColored(status_color, ICON_MD_KEY " Session Code:"); ImGui::TextWrapped("%s", collaboration_state_.session_id.c_str()); - if (ImGui::SmallButton("Copy")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); + if (ImGui::SmallButton(ICON_MD_CONTENT_COPY " Copy##copy_session_id")) { ImGui::SetClipboardText(collaboration_state_.session_id.c_str()); if (toast_manager_) { - toast_manager_->Show("Code copied", ToastType::kInfo, 2.0f); + toast_manager_->Show("Session code copied!", ToastType::kSuccess, 2.0f); } } - ImGui::Spacing(); + ImGui::PopStyleColor(2); } if (collaboration_state_.last_synced != absl::InfinitePast()) { - ImGui::TextDisabled("Last sync:"); - ImGui::TextDisabled( + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_ACCESS_TIME " Last sync:"); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s", absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, absl::LocalTimeZone()) .c_str()); @@ -819,18 +855,20 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::PopStyleColor(); // Show participants list below session details + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.14f, 0.4f)); ImGui::BeginChild("participants", ImVec2(0, 0), true); if (collaboration_state_.participants.empty()) { - ImGui::TextDisabled("No participants"); + ImGui::TextDisabled(ICON_MD_PEOPLE " No participants yet"); } else { - ImGui::Text("Participants (%zu):", + ImGui::TextColored(status_color, ICON_MD_PEOPLE " Participants (%zu):", collaboration_state_.participants.size()); ImGui::Separator(); for (const auto& participant : collaboration_state_.participants) { - ImGui::BulletText("%s", participant.c_str()); + ImGui::BulletText(ICON_MD_PERSON " %s", participant.c_str()); } } ImGui::EndChild(); + ImGui::PopStyleColor(); // RIGHT COLUMN: Controls ImGui::TableSetColumnIndex(1); @@ -847,30 +885,39 @@ void AgentChatWidget::RenderCollaborationPanel() { const bool can_refresh = static_cast(collaboration_callbacks_.refresh_session); - // Network mode: Show server URL input + // Network mode: Show server URL input with styling if (collaboration_state_.mode == CollaborationMode::kNetwork) { - ImGui::Text("Server URL:"); - ImGui::InputText("##server_url", server_url_buffer_, + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_CLOUD " Server URL:"); + ImGui::SetNextItemWidth(-80); + ImGui::InputText("##collab_server_url", server_url_buffer_, IM_ARRAYSIZE(server_url_buffer_)); - if (ImGui::Button("Connect to Server")) { + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + if (ImGui::Button(ICON_MD_LINK "##connect_server_btn")) { collaboration_state_.server_url = server_url_buffer_; - // TODO: Trigger network coordinator connection if (toast_manager_) { - toast_manager_->Show("Network mode: connecting...", ToastType::kInfo, - 3.0f); + toast_manager_->Show("Connecting to server...", ToastType::kInfo, 3.0f); } } + ImGui::PopStyleColor(2); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Connect to collaboration server"); + } ImGui::Separator(); } - ImGui::Text("Host New Session:"); - ImGui::InputTextWithHint("##session_name", "Session name", + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ADD_CIRCLE " Host New Session:"); + ImGui::SetNextItemWidth(-70); + ImGui::InputTextWithHint("##collab_session_name", "Enter session name...", session_name_buffer_, IM_ARRAYSIZE(session_name_buffer_)); ImGui::SameLine(); if (!can_host) ImGui::BeginDisabled(); - if (ImGui::Button("Host")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.5f, 0.0f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.843f, 0.0f, 1.0f)); + if (ImGui::Button(ICON_MD_ROCKET_LAUNCH "##host_session_btn")) { std::string name = session_name_buffer_; if (name.empty()) { if (toast_manager_) { @@ -899,21 +946,27 @@ void AgentChatWidget::RenderCollaborationPanel() { } } } + ImGui::PopStyleColor(2); if (!can_host) { - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide host_session callback to enable hosting"); } ImGui::EndDisabled(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Host a new collaboration session"); } ImGui::Spacing(); - ImGui::Text("Join Existing Session:"); - ImGui::InputTextWithHint("##join_code", "Session code", join_code_buffer_, + ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_LOGIN " Join Existing Session:"); + ImGui::SetNextItemWidth(-70); + ImGui::InputTextWithHint("##collab_join_code", "Enter session code...", join_code_buffer_, IM_ARRAYSIZE(join_code_buffer_)); ImGui::SameLine(); if (!can_join) ImGui::BeginDisabled(); - if (ImGui::Button("Join")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.4f, 0.1f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); + if (ImGui::Button(ICON_MD_MEETING_ROOM "##join_session_btn")) { std::string code = join_code_buffer_; if (code.empty()) { if (toast_manager_) { @@ -941,17 +994,25 @@ void AgentChatWidget::RenderCollaborationPanel() { } } } + ImGui::PopStyleColor(2); if (!can_join) { - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide join_session callback to enable joining"); } ImGui::EndDisabled(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Join an existing collaboration session"); } if (connected) { + ImGui::Separator(); + ImGui::Spacing(); + if (!can_leave) ImGui::BeginDisabled(); - if (ImGui::Button("Leave Session")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.863f, 0.078f, 0.235f, 1.0f)); + if (ImGui::Button(ICON_MD_LOGOUT " Leave Session##leave_session_btn", ImVec2(-1, 0))) { absl::Status status = collaboration_callbacks_.leave_session ? collaboration_callbacks_.leave_session() : absl::OkStatus(); @@ -978,10 +1039,13 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::Separator(); if (!can_refresh) ImGui::BeginDisabled(); - if (ImGui::Button("Refresh Session")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); + if (ImGui::Button(ICON_MD_REFRESH " Refresh Session##refresh_collab_btn", ImVec2(-1, 0))) { RefreshCollaboration(); } - if (!can_refresh && ImGui::IsItemHovered()) { + ImGui::PopStyleColor(2); + if (!can_refresh && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide refresh_session callback to enable"); } if (!can_refresh) @@ -997,7 +1061,9 @@ void AgentChatWidget::RenderCollaborationPanel() { } void AgentChatWidget::RenderMultimodalPanel() { - if (!ImGui::CollapsingHeader("Gemini Multimodal (Preview)", + ImVec4 gemini_color = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); + + if (!ImGui::CollapsingHeader(ICON_MD_CAMERA " Gemini Multimodal Vision", ImGuiTreeNodeFlags_DefaultOpen)) { return; } @@ -1005,33 +1071,35 @@ void AgentChatWidget::RenderMultimodalPanel() { bool can_capture = static_cast(multimodal_callbacks_.capture_snapshot); bool can_send = static_cast(multimodal_callbacks_.send_to_gemini); - // Capture mode selection - ImGui::Text("Capture Mode:"); - ImGui::RadioButton("Full Window", + // Capture mode selection with icons + ImGui::TextColored(gemini_color, ICON_MD_SCREENSHOT " Capture Mode:"); + ImGui::RadioButton(ICON_MD_FULLSCREEN " Full Window##mm_radio_full", reinterpret_cast(&multimodal_state_.capture_mode), static_cast(CaptureMode::kFullWindow)); ImGui::SameLine(); - ImGui::RadioButton("Active Editor", + ImGui::RadioButton(ICON_MD_DASHBOARD " Active Editor##mm_radio_active", reinterpret_cast(&multimodal_state_.capture_mode), static_cast(CaptureMode::kActiveEditor)); ImGui::SameLine(); - ImGui::RadioButton("Specific Window", + ImGui::RadioButton(ICON_MD_WINDOW " Specific##mm_radio_specific", reinterpret_cast(&multimodal_state_.capture_mode), static_cast(CaptureMode::kSpecificWindow)); // If specific window mode, show input for window name if (multimodal_state_.capture_mode == CaptureMode::kSpecificWindow) { - ImGui::InputText("Window Name", multimodal_state_.specific_window_buffer, + ImGui::SetNextItemWidth(-1); + ImGui::InputTextWithHint("##mm_window_name_input", "Window name (e.g., Overworld Editor)...", + multimodal_state_.specific_window_buffer, IM_ARRAYSIZE(multimodal_state_.specific_window_buffer)); - ImGui::TextDisabled( - "Examples: Overworld Editor, Dungeon Editor, Sprite Editor"); } ImGui::Separator(); if (!can_capture) ImGui::BeginDisabled(); - if (ImGui::Button("Capture Snapshot")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gemini_color); + if (ImGui::Button(ICON_MD_PHOTO_CAMERA " Capture Snapshot##mm_capture_btn", ImVec2(-1, 0))) { if (multimodal_callbacks_.capture_snapshot) { std::filesystem::path captured_path; absl::Status status = @@ -1056,26 +1124,36 @@ void AgentChatWidget::RenderMultimodalPanel() { } } } + ImGui::PopStyleColor(2); if (!can_capture) { - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide capture_snapshot callback to enable"); } ImGui::EndDisabled(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Capture a screenshot for vision analysis"); } + ImGui::Spacing(); + if (multimodal_state_.last_capture_path.has_value()) { - ImGui::TextDisabled("Last capture: %s", - multimodal_state_.last_capture_path->string().c_str()); + ImGui::TextColored(gemini_color, ICON_MD_CHECK_CIRCLE " Last capture: %s", + multimodal_state_.last_capture_path->filename().string().c_str()); } else { - ImGui::TextDisabled("No capture yet"); + ImGui::TextDisabled(ICON_MD_CAMERA_ALT " No capture yet"); } - ImGui::InputTextMultiline("##gemini_prompt", multimodal_prompt_buffer_, + ImGui::Spacing(); + ImGui::TextColored(gemini_color, ICON_MD_EDIT " Vision Prompt:"); + ImGui::InputTextMultiline("##mm_gemini_prompt_textarea", multimodal_prompt_buffer_, IM_ARRAYSIZE(multimodal_prompt_buffer_), - ImVec2(-1, 60.0f)); + ImVec2(-1, 80)); + if (!can_send) ImGui::BeginDisabled(); - if (ImGui::Button("Send to Gemini")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gemini_color); + if (ImGui::Button(ICON_MD_SEND " Send to Gemini##mm_send_to_gemini_btn", ImVec2(-1, 0))) { if (!multimodal_state_.last_capture_path.has_value()) { if (toast_manager_) { toast_manager_->Show("Capture a snapshot first", ToastType::kWarning, @@ -1104,18 +1182,24 @@ void AgentChatWidget::RenderMultimodalPanel() { } } } + ImGui::PopStyleColor(2); if (!can_send) { - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide send_to_gemini callback to enable"); } ImGui::EndDisabled(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Send captured image to Gemini for analysis"); } if (!multimodal_state_.status_message.empty()) { - ImGui::TextDisabled("Status: %s", multimodal_state_.status_message.c_str()); + ImGui::Separator(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), + ICON_MD_INFO " %s", multimodal_state_.status_message.c_str()); if (multimodal_state_.last_updated != absl::InfinitePast()) { - ImGui::TextDisabled( - "Updated: %s", + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + ICON_MD_ACCESS_TIME " %s", absl::FormatTime("%H:%M:%S", multimodal_state_.last_updated, absl::LocalTimeZone()) .c_str()); diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 2f48e69a..22a2fb7a 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -835,7 +835,7 @@ void EditorManager::BuildModernMenu() { [this]() { show_global_search_ = true; }, "Ctrl+Shift+F") .EndMenu(); - // View Menu - editors and layout + // View Menu - editors only menu_builder_.BeginMenu("View") .Item("Editor Selection", ICON_MD_DASHBOARD, [this]() { show_editor_selection_ = true; }, "Ctrl+E") @@ -859,6 +859,35 @@ void EditorManager::BuildModernMenu() { .Item("Hex Editor", ICON_MD_DATA_ARRAY, [this]() { show_memory_editor_ = true; }, "Ctrl+0") .Separator() + .Item("Welcome Screen", ICON_MD_HOME, + [this]() { show_welcome_screen_ = true; }) + .Item("Command Palette", ICON_MD_TERMINAL, + [this]() { show_command_palette_ = true; }, "Ctrl+Shift+P") + .Item("Emulator", ICON_MD_GAMEPAD, + [this]() { show_emulator_ = true; }, + nullptr, nullptr, + [this]() { return show_emulator_; }) + .EndMenu(); + + // Window Menu - layout and session management + menu_builder_.BeginMenu("Window") + .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() .Item("Save Layout", ICON_MD_SAVE, [this]() { SaveWorkspaceLayout(); }, "Ctrl+Shift+S") .Item("Load Layout", ICON_MD_FOLDER_OPEN, @@ -874,52 +903,10 @@ void EditorManager::BuildModernMenu() { [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("Command Palette", ICON_MD_TERMINAL, - [this]() { show_command_palette_ = true; }, "Ctrl+Shift+P") - .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; }) -#ifdef YAZE_ENABLE_TESTING - .Item("Test Dashboard", ICON_MD_SCIENCE, - [this]() { show_test_dashboard_ = true; }, "Ctrl+T") -#endif - .Separator() - .Item("ImGui Demo", ICON_MD_HELP, - [this]() { show_imgui_demo_ = true; }, - nullptr, nullptr, - [this]() { return show_imgui_demo_; }) - .Item("ImGui Metrics", ICON_MD_ANALYTICS, - [this]() { show_imgui_metrics_ = true; }, - nullptr, nullptr, - [this]() { return show_imgui_metrics_; }) - .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("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; }) + .Item("Restore All", ICON_MD_FULLSCREEN_EXIT, + [this]() { RestoreAllWindows(); }) + .Item("Close All Floating", ICON_MD_CLOSE_FULLSCREEN, + [this]() { CloseAllFloatingWindows(); }) .EndMenu(); @@ -1004,12 +991,28 @@ void EditorManager::BuildModernMenu() { .EndMenu(); #endif - // Debug Menu + // Debug Menu - comprehensive development tools 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; }) + .Separator() + .Item("Performance Dashboard", ICON_MD_SPEED, + [this]() { show_performance_dashboard_ = true; }) +#ifdef YAZE_ENABLE_TESTING + .Item("Test Dashboard", ICON_MD_SCIENCE, + [this]() { show_test_dashboard_ = true; }, "Ctrl+T") +#endif + .Separator() + .Item("ImGui Demo", ICON_MD_HELP, + [this]() { show_imgui_demo_ = true; }, + nullptr, nullptr, + [this]() { return show_imgui_demo_; }) + .Item("ImGui Metrics", ICON_MD_ANALYTICS, + [this]() { show_imgui_metrics_ = true; }, + nullptr, nullptr, + [this]() { return show_imgui_metrics_; }) .EndMenu(); // Help Menu @@ -1034,15 +1037,12 @@ void EditorManager::DrawMenuBar() { status_ = DrawRomSelector(); // Calculate proper right-side positioning - // With the wider menu (8 top-level items), we need more compact right-side layout std::string version_text = absl::StrFormat("v%s", version_.c_str()); float version_width = CalcTextSize(version_text.c_str()).x; - float settings_width = CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x + 16; - float total_right_width = version_width + settings_width + 30; // Increased padding for visibility - // Allocate space for ROM status and sessions - float session_rom_area_width = 220.0f; // Increased slightly for better spacing - SameLine(GetWindowWidth() - total_right_width - session_rom_area_width); + // Allocate space for ROM status and sessions (wider for better ROM title display) + float session_rom_area_width = 280.0f; // Increased for wider ROM title + SameLine(GetWindowWidth() - version_width - 10 - session_rom_area_width); // Multi-session indicator if (sessions_.size() > 1) { @@ -1056,25 +1056,42 @@ void EditorManager::DrawMenuBar() { SameLine(); } - // Editor selection quick button (when ROM loaded) + // Editor selection and display settings quick buttons (when ROM loaded) if (current_rom_ && current_rom_->is_loaded()) { + // Editor selection button + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 1.0f)); if (SmallButton(ICON_MD_APPS)) { show_editor_selection_ = true; } + ImGui::PopStyleColor(2); if (IsItemHovered()) { - SetTooltip("Open Editor Selection (Ctrl+E)"); + SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)"); } SameLine(); + + // Display settings button + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 1.0f)); + if (SmallButton(ICON_MD_DISPLAY_SETTINGS)) { + show_display_settings = !show_display_settings; + } + ImGui::PopStyleColor(2); + if (IsItemHovered()) { + SetTooltip(ICON_MD_TUNE " Display Settings"); + } + SameLine(); + ImGui::Separator(); SameLine(); } // Compact ROM status with metadata popup if (current_rom_ && current_rom_->is_loaded()) { - // Truncate long ROM titles for compact display + // Truncate long ROM titles for wider display std::string rom_display = current_rom_->title(); - if (rom_display.length() > 15) { - rom_display = rom_display.substr(0, 12) + "..."; + if (rom_display.length() > 22) { + rom_display = rom_display.substr(0, 19) + "..."; } ImVec4 status_color = @@ -1187,18 +1204,8 @@ void EditorManager::DrawMenuBar() { SameLine(); } - // Settings and version (using pre-calculated positioning) - SameLine(GetWindowWidth() - total_right_width); - ImGui::Separator(); - SameLine(); - - PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - if (Button(ICON_MD_DISPLAY_SETTINGS)) { - show_display_settings = !show_display_settings; - } - PopStyleColor(); - - SameLine(); + // Version display on far right + SameLine(GetWindowWidth() - version_width - 10); Text("%s", version_text.c_str()); EndMenuBar(); } diff --git a/src/app/editor/ui/welcome_screen.cc b/src/app/editor/ui/welcome_screen.cc index bb7920b9..93985605 100644 --- a/src/app/editor/ui/welcome_screen.cc +++ b/src/app/editor/ui/welcome_screen.cc @@ -179,22 +179,12 @@ bool WelcomeScreen::Show(bool* p_open) { }; TriforceConfig triforce_configs[] = { - {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 + {0.08f, 0.12f, 36.0f, 0.025f, 50.0f}, // Top left corner + {0.92f, 0.15f, 34.0f, 0.022f, 50.0f}, // Top right corner + {0.06f, 0.88f, 32.0f, 0.020f, 45.0f}, // Bottom left + {0.94f, 0.85f, 34.0f, 0.023f, 50.0f}, // Bottom right + {0.50f, 0.08f, 38.0f, 0.028f, 55.0f}, // Top center + {0.50f, 0.92f, 32.0f, 0.020f, 45.0f}, // Bottom center }; // Initialize base positions on first frame @@ -215,20 +205,16 @@ bool WelcomeScreen::Show(bool* p_open) { 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_; + // Slow, subtle floating animation + float time_offset = i * 1.2f; // Offset each triforce's animation + float float_speed_x = (0.15f + (i % 2) * 0.1f) * triforce_speed_multiplier_; // Very slow + float float_speed_y = (0.12f + ((i + 1) % 2) * 0.08f) * triforce_speed_multiplier_; + float float_amount_x = (20.0f + (i % 2) * 10.0f) * triforce_size_multiplier_; // Smaller amplitude + float float_amount_y = (25.0f + ((i + 1) % 2) * 15.0f) * triforce_size_multiplier_; - // Create complex orbital/figure-8 motion with more variation + // Create gentle orbital motion 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); + float float_y = std::cos(animation_time_ * float_speed_y + time_offset * 1.2f) * float_amount_y; // Calculate distance from mouse float dx = triforce_base_positions_[i].x - mouse_pos.x; @@ -269,6 +255,61 @@ bool WelcomeScreen::Show(bool* p_open) { adjusted_size, adjusted_alpha, 0.0f); } + // Update and draw particle system + if (particles_enabled_) { + // Spawn new particles + static float spawn_accumulator = 0.0f; + spawn_accumulator += ImGui::GetIO().DeltaTime * particle_spawn_rate_; + while (spawn_accumulator >= 1.0f && active_particle_count_ < kMaxParticles) { + // Find inactive particle slot + for (int i = 0; i < kMaxParticles; ++i) { + if (particles_[i].lifetime <= 0.0f) { + // Spawn from random triforce + int source_triforce = rand() % kNumTriforces; + particles_[i].position = triforce_positions_[source_triforce]; + + // Random direction and speed + float angle = (rand() % 360) * (M_PI / 180.0f); + float speed = 20.0f + (rand() % 40); + particles_[i].velocity = ImVec2(std::cos(angle) * speed, std::sin(angle) * speed); + + particles_[i].size = 2.0f + (rand() % 4); + particles_[i].alpha = 0.4f + (rand() % 40) / 100.0f; + particles_[i].max_lifetime = 2.0f + (rand() % 30) / 10.0f; + particles_[i].lifetime = particles_[i].max_lifetime; + active_particle_count_++; + break; + } + } + spawn_accumulator -= 1.0f; + } + + // Update and draw particles + float dt = ImGui::GetIO().DeltaTime; + for (int i = 0; i < kMaxParticles; ++i) { + if (particles_[i].lifetime > 0.0f) { + // Update lifetime + particles_[i].lifetime -= dt; + if (particles_[i].lifetime <= 0.0f) { + active_particle_count_--; + continue; + } + + // Update position + particles_[i].position.x += particles_[i].velocity.x * dt; + particles_[i].position.y += particles_[i].velocity.y * dt; + + // Fade out near end of life + float life_ratio = particles_[i].lifetime / particles_[i].max_lifetime; + float alpha = particles_[i].alpha * life_ratio * triforce_alpha_multiplier_; + + // Draw particle as small golden circle + ImU32 particle_color = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha)); + bg_draw_list->AddCircleFilled(particles_[i].position, particles_[i].size, particle_color, 8); + } + } + } + DrawHeader(); ImGui::Spacing(); @@ -348,57 +389,6 @@ 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(); @@ -416,7 +406,7 @@ void WelcomeScreen::UpdateAnimations() { card_hover_scale_[i] += (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 10.0f; } - // Note: Triforce positions are updated in Show() based on mouse position + // Note: Triforce positions and particles are updated in Show() based on mouse position } void WelcomeScreen::RefreshRecentProjects() { @@ -499,7 +489,7 @@ void WelcomeScreen::DrawQuickActions() { float button_width = ImGui::GetContentRegionAvail().x; - // Animated button colors + // Animated button colors (compact height) auto draw_action_button = [&](const char* icon, const char* text, const ImVec4& color, bool enabled, std::function callback) { @@ -510,7 +500,7 @@ void WelcomeScreen::DrawQuickActions() { if (!enabled) ImGui::BeginDisabled(); bool clicked = ImGui::Button(absl::StrFormat("%s %s", icon, text).c_str(), - ImVec2(button_width, 45)); + ImVec2(button_width, 38)); // Reduced from 45 to 38 if (!enabled) ImGui::EndDisabled(); @@ -540,14 +530,6 @@ void WelcomeScreen::DrawQuickActions() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip(ICON_MD_INFO " Create a new ROM hacking project"); } - - ImGui::Spacing(); - - // Clone Project button - Blue like water/ice dungeon - draw_action_button(ICON_MD_CLOUD_DOWNLOAD, "Clone Project", kMasterSwordBlue, false, nullptr); - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip(ICON_MD_CONSTRUCTION " Clone a project from git (Coming soon)"); - } } void WelcomeScreen::DrawRecentProjects() { @@ -569,10 +551,10 @@ void WelcomeScreen::DrawRecentProjects() { return; } - // Grid layout for project cards - 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))); + // Grid layout for project cards (compact) + float card_width = 200.0f; // Reduced for compactness + float card_height = 95.0f; // Reduced for less scrolling + int columns = std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12))); for (size_t i = 0; i < recent_projects_.size(); ++i) { if (i % columns != 0) { @@ -585,7 +567,7 @@ void WelcomeScreen::DrawRecentProjects() { void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { ImGui::BeginGroup(); - ImVec2 card_size(260, 120); // Reduced height from 140 to 120 + ImVec2 card_size(200, 95); // Compact size ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); // Subtle hover scale (only on actual hover, no animation) @@ -635,39 +617,41 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { hover_color, 6.0f); } - // Draw content - ImVec2 content_pos(cursor_pos.x + 12, cursor_pos.y + 12); + // Draw content (tighter layout) + ImVec2 content_pos(cursor_pos.x + 8, cursor_pos.y + 8); - // Icon with colored background circle (smaller and centered) - ImVec2 icon_center(content_pos.x + 16, content_pos.y + 16); + // Icon with colored background circle (compact) + ImVec2 icon_center(content_pos.x + 13, content_pos.y + 13); ImU32 icon_bg = ImGui::GetColorU32(border_color_base); - draw_list->AddCircleFilled(icon_center, 18, icon_bg, 32); // Reduced from 25 to 18 + draw_list->AddCircleFilled(icon_center, 15, icon_bg, 24); // 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 + 40, content_pos.y + 12)); - ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 12); + // Project name (compact) + ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 32, content_pos.y + 8)); + ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 8); + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font ImGui::TextColored(kTriforceGold, "%s", project.name.c_str()); + ImGui::PopFont(); ImGui::PopTextWrapPos(); - // ROM title - 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()); + // ROM title (compact) + ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 35)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.65f, 0.65f, 0.65f, 1.0f)); + ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str()); + ImGui::PopStyleColor(); - // Path in card (condensed) - ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 5, content_pos.y + 70)); + // Path in card (compact) + ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 58)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); std::string short_path = project.filepath; - if (short_path.length() > 38) { - short_path = "..." + short_path.substr(short_path.length() - 35); + if (short_path.length() > 28) { + short_path = "..." + short_path.substr(short_path.length() - 25); } ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str()); ImGui::PopStyleColor(); @@ -694,7 +678,53 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { } void WelcomeScreen::DrawTemplatesSection() { + // Header with visual settings button + float content_width = ImGui::GetContentRegionAvail().x; ImGui::TextColored(kGanonPurple, ICON_MD_LAYERS " Templates"); + ImGui::SameLine(content_width - 25); + if (ImGui::SmallButton(show_triforce_settings_ ? ICON_MD_CLOSE : ICON_MD_TUNE)) { + show_triforce_settings_ = !show_triforce_settings_; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(ICON_MD_AUTO_AWESOME " Visual Effects Settings"); + } + + ImGui::Spacing(); + + // Visual effects settings panel (when opened) + if (show_triforce_settings_) { + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18f, 0.15f, 0.22f, 0.4f)); + ImGui::BeginChild("VisualSettingsCompact", ImVec2(0, 115), true, ImGuiWindowFlags_NoScrollbar); + { + ImGui::TextColored(kGanonPurple, ICON_MD_AUTO_AWESOME " Visual Effects"); + ImGui::Spacing(); + + 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.05f, 1.0f, "%.2fx"); + + ImGui::Checkbox(ICON_MD_MOUSE " Mouse Interaction", &triforce_mouse_repel_enabled_); + ImGui::SameLine(); + ImGui::Checkbox(ICON_MD_AUTO_FIX_HIGH " Particles", &particles_enabled_); + + if (ImGui::SmallButton(ICON_MD_REFRESH " Reset")) { + triforce_alpha_multiplier_ = 1.0f; + triforce_speed_multiplier_ = 0.3f; + triforce_size_multiplier_ = 1.0f; + triforce_mouse_repel_enabled_ = true; + particles_enabled_ = true; + particle_spawn_rate_ = 2.0f; + } + } + ImGui::EndChild(); + ImGui::PopStyleColor(); + ImGui::Spacing(); + } + ImGui::Spacing(); struct Template { @@ -706,10 +736,9 @@ void WelcomeScreen::DrawTemplatesSection() { Template templates[] = { {ICON_MD_COTTAGE, "Vanilla ALTTP", kHyruleGreen}, {ICON_MD_MAP, "ZSCustomOverworld v3", kMasterSwordBlue}, - {ICON_MD_PUBLIC, "Parallel Worlds Base", kGanonPurple}, }; - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < 2; ++i) { bool is_selected = (selected_template_ == i); // Subtle selection highlight (no animation) @@ -738,7 +767,7 @@ void WelcomeScreen::DrawTemplatesSection() { ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kSpiritOrange); ImGui::BeginDisabled(true); ImGui::Button(absl::StrFormat("%s Use Template", ICON_MD_ROCKET_LAUNCH).c_str(), - ImVec2(-1, 35)); + ImVec2(-1, 30)); // Reduced from 35 to 30 ImGui::EndDisabled(); ImGui::PopStyleColor(2); } diff --git a/src/app/editor/ui/welcome_screen.h b/src/app/editor/ui/welcome_screen.h index 01e4f98b..e9427a0a 100644 --- a/src/app/editor/ui/welcome_screen.h +++ b/src/app/editor/ui/welcome_screen.h @@ -106,17 +106,32 @@ class WelcomeScreen { int hovered_card_ = -1; // Interactive triforce positions (smooth interpolation) - static constexpr int kNumTriforces = 16; + static constexpr int kNumTriforces = 6; ImVec2 triforce_positions_[kNumTriforces] = {}; ImVec2 triforce_base_positions_[kNumTriforces] = {}; bool triforce_positions_initialized_ = false; + // Particle system + static constexpr int kMaxParticles = 50; + struct Particle { + ImVec2 position; + ImVec2 velocity; + float size; + float alpha; + float lifetime; + float max_lifetime; + }; + Particle particles_[kMaxParticles] = {}; + int active_particle_count_ = 0; + // Triforce animation settings bool show_triforce_settings_ = false; float triforce_alpha_multiplier_ = 1.0f; - float triforce_speed_multiplier_ = 1.0f; + float triforce_speed_multiplier_ = 0.3f; // Default slower speed float triforce_size_multiplier_ = 1.0f; bool triforce_mouse_repel_enabled_ = true; + bool particles_enabled_ = true; + float particle_spawn_rate_ = 2.0f; // Particles per second }; } // namespace editor