From 8d6453df5ea823b242328cfabe3aa955a871942a Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 15:38:41 -0400 Subject: [PATCH] feat: Introduce Agent UI Theme Management and Refactor Color Usage - Added agent_ui_theme.h and agent_ui_theme.cc to centralize theme color definitions for the Agent UI, improving consistency across components. - Updated AgentChatWidget and AgentChatHistoryPopup to utilize the new theme management system, replacing hardcoded colors with theme-derived values. - Introduced agent_ui_theme.cc to define color properties for various UI elements, enhancing maintainability and visual coherence. - Included agent_ui_theme.h in relevant files to streamline access to theme colors, promoting a cleaner codebase. --- src/app/editor/agent/agent_chat_widget.cc | 45 +++-- src/app/editor/agent/agent_ui_theme.cc | 188 ++++++++++++++++++ src/app/editor/agent/agent_ui_theme.h | 97 +++++++++ src/app/editor/editor_library.cmake | 1 + .../editor/system/agent_chat_history_popup.cc | 23 +-- 5 files changed, 318 insertions(+), 36 deletions(-) create mode 100644 src/app/editor/agent/agent_ui_theme.cc create mode 100644 src/app/editor/agent/agent_ui_theme.h diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index 421c318f..29f0dcff 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -20,6 +20,7 @@ #include "absl/time/time.h" #include "app/core/project.h" #include "app/editor/agent/agent_chat_history_codec.h" +#include "app/editor/agent/agent_ui_theme.h" #include "app/editor/system/agent_chat_history_popup.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" @@ -37,11 +38,6 @@ namespace { using yaze::cli::agent::ChatMessage; -const ImVec4 kUserColor = ImVec4(0.88f, 0.76f, 0.36f, 1.0f); -const ImVec4 kAgentColor = ImVec4(0.56f, 0.82f, 0.62f, 1.0f); -const ImVec4 kJsonTextColor = ImVec4(0.78f, 0.83f, 0.90f, 1.0f); -const ImVec4 kProposalPanelColor = ImVec4(0.20f, 0.35f, 0.20f, 0.35f); - std::filesystem::path ExpandUserPath(std::string path) { if (!path.empty() && path.front() == '~') { const char* home = nullptr; @@ -423,9 +419,10 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { } ImGui::PushID(index); + const auto& theme = AgentUI::GetTheme(); const bool from_user = (msg.sender == ChatMessage::Sender::kUser); - const ImVec4 header_color = from_user ? kUserColor : kAgentColor; + const ImVec4 header_color = from_user ? theme.user_message_color : theme.agent_message_color; const char* header_label = from_user ? "You" : "Agent"; ImGui::TextColored(header_color, "%s", header_label); @@ -437,8 +434,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { // Add copy button for all messages ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Button, theme.button_copy); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.button_copy_hover); if (ImGui::SmallButton(ICON_MD_CONTENT_COPY)) { std::string copy_text = msg.message; if (copy_text.empty() && msg.json_pretty.has_value()) { @@ -460,7 +457,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { RenderTable(*msg.table_data); } else if (msg.json_pretty.has_value()) { // Don't show JSON as a message - it's internal structure - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 0.8f)); + const auto& theme = AgentUI::GetTheme(); + ImGui::PushStyleColor(ImGuiCol_Text, theme.json_text_color); ImGui::TextDisabled(ICON_MD_DATA_OBJECT " (Structured response)"); ImGui::PopStyleColor(); } else { @@ -483,14 +481,15 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg, return; } + const auto& theme = AgentUI::GetTheme(); const auto& proposal = *msg.proposal; - ImGui::PushStyleColor(ImGuiCol_ChildBg, kProposalPanelColor); + ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.proposal_panel_bg); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); ImGui::BeginChild(absl::StrFormat("proposal_panel_%d", index).c_str(), ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true, ImGuiWindowFlags_None); - ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f), "%s Proposal %s", + ImGui::TextColored(theme.proposal_accent, "%s Proposal %s", ICON_MD_PREVIEW, proposal.id.c_str()); ImGui::Text("Changes: %d", proposal.change_count); ImGui::Text("Commands: %d", proposal.executed_commands); @@ -523,12 +522,13 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg, } void AgentChatWidget::RenderHistory() { + const auto& theme = AgentUI::GetTheme(); const auto& history = agent_service_.GetHistory(); float reserved_height = ImGui::GetFrameHeightWithSpacing() * 4.0f; reserved_height += 100.0f; // Reduced to 100 for much taller chat area // Styled chat history container - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.10f, 0.95f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color); if (ImGui::BeginChild("History", ImVec2(0, -reserved_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { if (history.empty()) { @@ -560,9 +560,10 @@ void AgentChatWidget::RenderHistory() { } void AgentChatWidget::RenderInputBox() { + const auto& theme = AgentUI::GetTheme(); + ImGui::Separator(); - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), - ICON_MD_EDIT " Message:"); + ImGui::TextColored(theme.command_text_color, ICON_MD_EDIT " Message:"); bool submitted = ImGui::InputTextMultiline( "##agent_input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 60.0f), @@ -578,10 +579,11 @@ void AgentChatWidget::RenderInputBox() { ImGui::Spacing(); - // Send button row - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); + // Send button row + ImGui::PushStyleColor(ImGuiCol_Button, theme.provider_gemini); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + ImVec4(theme.provider_gemini.x * 1.2f, theme.provider_gemini.y * 1.1f, + theme.provider_gemini.z, theme.provider_gemini.w)); if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(140, 0)) || send) { @@ -1602,12 +1604,13 @@ void AgentChatWidget::UpdateAgentConfig(const AgentConfigState& config) { } void AgentChatWidget::RenderAgentConfigPanel() { + const auto& theme = AgentUI::GetTheme(); + // Dense header (no collapsing) - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.14f, 0.18f, 0.95f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color); ImGui::BeginChild("AgentConfig", ImVec2(0, 140), true); // Reduced from 350 - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), - ICON_MD_SETTINGS " Config"); - ImGui::Separator(); + AgentUI::RenderSectionHeader(ICON_MD_SETTINGS, "Config", theme.command_text_color); + // Compact provider selection int provider_idx = 0; diff --git a/src/app/editor/agent/agent_ui_theme.cc b/src/app/editor/agent/agent_ui_theme.cc new file mode 100644 index 00000000..581c8bf6 --- /dev/null +++ b/src/app/editor/agent/agent_ui_theme.cc @@ -0,0 +1,188 @@ +#include "app/editor/agent/agent_ui_theme.h" + +#include "app/gui/theme_manager.h" +#include "app/gui/color.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +// Global theme instance +static AgentUITheme g_agent_theme; +static bool g_theme_initialized = false; + +AgentUITheme AgentUITheme::FromCurrentTheme() { + AgentUITheme theme; + const auto& current = gui::ThemeManager::Get().GetCurrentTheme(); + + // Message colors - derived from theme primary/secondary + theme.user_message_color = ImVec4( + current.primary.red * 1.1f, + current.primary.green * 0.95f, + current.primary.blue * 0.6f, + 1.0f + ); + + theme.agent_message_color = ImVec4( + current.secondary.red * 0.9f, + current.secondary.green * 1.3f, + current.secondary.blue * 1.0f, + 1.0f + ); + + theme.system_message_color = ImVec4( + current.info.red, + current.info.green, + current.info.blue, + 1.0f + ); + + // Content colors + theme.json_text_color = ImVec4(0.78f, 0.83f, 0.90f, 1.0f); + theme.command_text_color = ImVec4(1.0f, 0.647f, 0.0f, 1.0f); + theme.code_bg_color = ImVec4(0.08f, 0.08f, 0.10f, 0.95f); + + // UI element colors + theme.panel_bg_color = ImVec4(0.12f, 0.14f, 0.18f, 0.95f); + theme.panel_bg_darker = ImVec4(0.08f, 0.10f, 0.14f, 0.95f); + theme.panel_border_color = ConvertColorToImVec4(current.border); + theme.accent_color = ConvertColorToImVec4(current.accent); + + // Status colors + theme.status_active = ConvertColorToImVec4(current.success); + theme.status_inactive = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + theme.status_success = ConvertColorToImVec4(current.success); + theme.status_warning = ConvertColorToImVec4(current.warning); + theme.status_error = ConvertColorToImVec4(current.error); + + // Provider-specific colors + theme.provider_ollama = ImVec4(0.2f, 0.8f, 0.4f, 1.0f); // Green + theme.provider_gemini = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); // Blue + theme.provider_mock = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); // Gray + + // Collaboration colors + theme.collaboration_active = ImVec4(0.133f, 0.545f, 0.133f, 1.0f); // Forest green + theme.collaboration_inactive = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + + // Proposal colors + theme.proposal_panel_bg = ImVec4(0.20f, 0.35f, 0.20f, 0.35f); + theme.proposal_accent = ImVec4(0.8f, 1.0f, 0.8f, 1.0f); + + // Button colors + theme.button_copy = ImVec4(0.3f, 0.3f, 0.4f, 0.6f); + theme.button_copy_hover = ImVec4(0.4f, 0.4f, 0.5f, 0.8f); + + // Gradient colors + theme.gradient_top = ImVec4(0.18f, 0.22f, 0.28f, 1.0f); + theme.gradient_bottom = ImVec4(0.12f, 0.16f, 0.22f, 1.0f); + + return theme; +} + +namespace AgentUI { + +const AgentUITheme& GetTheme() { + if (!g_theme_initialized) { + RefreshTheme(); + } + return g_agent_theme; +} + +void RefreshTheme() { + g_agent_theme = AgentUITheme::FromCurrentTheme(); + g_theme_initialized = true; +} + +void PushPanelStyle() { + const auto& theme = GetTheme(); + ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12)); +} + +void PopPanelStyle() { + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); +} + +void RenderSectionHeader(const char* icon, const char* label, const ImVec4& color) { + ImGui::TextColored(color, "%s %s", icon, label); + ImGui::Separator(); +} + +void RenderStatusIndicator(const char* label, bool active) { + const auto& theme = GetTheme(); + ImVec4 color = active ? theme.status_active : theme.status_inactive; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + float radius = 4.0f; + + pos.x += radius + 2; + pos.y += ImGui::GetTextLineHeight() * 0.5f; + + draw_list->AddCircleFilled(pos, radius, ImGui::GetColorU32(color)); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8); + ImGui::Text("%s", label); +} + +void RenderProviderBadge(const char* provider) { + const auto& theme = GetTheme(); + + ImVec4 badge_color; + if (strcmp(provider, "ollama") == 0) { + badge_color = theme.provider_ollama; + } else if (strcmp(provider, "gemini") == 0) { + badge_color = theme.provider_gemini; + } else { + badge_color = theme.provider_mock; + } + + ImGui::PushStyleColor(ImGuiCol_Button, badge_color); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4)); + ImGui::SmallButton(provider); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); +} + +void VerticalSpacing(float amount) { + ImGui::Dummy(ImVec2(0, amount)); +} + +void HorizontalSpacing(float amount) { + ImGui::Dummy(ImVec2(amount, 0)); + ImGui::SameLine(); +} + +bool StyledButton(const char* label, const ImVec4& color, const ImVec2& size) { + ImGui::PushStyleColor(ImGuiCol_Button, color); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); + + bool result = ImGui::Button(label, size); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); + + return result; +} + +bool IconButton(const char* icon, const char* tooltip) { + bool result = ImGui::SmallButton(icon); + + if (tooltip && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltip); + } + + return result; +} + +} // namespace AgentUI + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/agent/agent_ui_theme.h b/src/app/editor/agent/agent_ui_theme.h new file mode 100644 index 00000000..d4c208c7 --- /dev/null +++ b/src/app/editor/agent/agent_ui_theme.h @@ -0,0 +1,97 @@ +#ifndef YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H +#define YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H + +#include "imgui/imgui.h" +#include "app/gui/theme_manager.h" +#include "app/gui/color.h" + +namespace yaze { +namespace editor { + +/** + * @struct AgentUITheme + * @brief Centralized theme colors for Agent UI components + * + * All hardcoded colors from AgentChatWidget, AgentEditor, and AgentChatHistoryPopup + * are consolidated here and derived from the current theme. + */ +struct AgentUITheme { + // Message colors + ImVec4 user_message_color; + ImVec4 agent_message_color; + ImVec4 system_message_color; + + // Content colors + ImVec4 json_text_color; + ImVec4 command_text_color; + ImVec4 code_bg_color; + + // UI element colors + ImVec4 panel_bg_color; + ImVec4 panel_bg_darker; + ImVec4 panel_border_color; + ImVec4 accent_color; + + // Status colors + ImVec4 status_active; + ImVec4 status_inactive; + ImVec4 status_success; + ImVec4 status_warning; + ImVec4 status_error; + + // Provider colors + ImVec4 provider_ollama; + ImVec4 provider_gemini; + ImVec4 provider_mock; + + // Collaboration colors + ImVec4 collaboration_active; + ImVec4 collaboration_inactive; + + // Proposal colors + ImVec4 proposal_panel_bg; + ImVec4 proposal_accent; + + // Button colors + ImVec4 button_copy; + ImVec4 button_copy_hover; + + // Gradient colors + ImVec4 gradient_top; + ImVec4 gradient_bottom; + + // Initialize from current theme + static AgentUITheme FromCurrentTheme(); +}; + +// Helper functions for common UI patterns +namespace AgentUI { + +// Get current theme colors +const AgentUITheme& GetTheme(); + +// Refresh theme from ThemeManager +void RefreshTheme(); + +// Common UI components +void PushPanelStyle(); +void PopPanelStyle(); + +void RenderSectionHeader(const char* icon, const char* label, const ImVec4& color); +void RenderStatusIndicator(const char* label, bool active); +void RenderProviderBadge(const char* provider); + +// Spacing helpers +void VerticalSpacing(float amount = 8.0f); +void HorizontalSpacing(float amount = 8.0f); + +// Common button styles +bool StyledButton(const char* label, const ImVec4& color, const ImVec2& size = ImVec2(0, 0)); +bool IconButton(const char* icon, const char* tooltip = nullptr); + +} // namespace AgentUI + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index 6f39dd2d..c216ebaf 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -49,6 +49,7 @@ if(YAZE_WITH_GRPC) list(APPEND YAZE_APP_EDITOR_SRC app/editor/agent/agent_editor.cc app/editor/agent/agent_chat_widget.cc + app/editor/agent/agent_ui_theme.cc app/editor/agent/agent_collaboration_coordinator.cc app/editor/agent/network_collaboration_coordinator.cc app/editor/agent/automation_bridge.cc diff --git a/src/app/editor/system/agent_chat_history_popup.cc b/src/app/editor/system/agent_chat_history_popup.cc index bdb31081..9bbf10a0 100644 --- a/src/app/editor/system/agent_chat_history_popup.cc +++ b/src/app/editor/system/agent_chat_history_popup.cc @@ -4,25 +4,16 @@ #include "absl/strings/str_format.h" #include "absl/time/time.h" -#include "imgui/imgui.h" -#include "app/gui/icons.h" +#include "app/editor/agent/agent_ui_theme.h" #include "app/editor/system/toast_manager.h" +#include "app/gui/icons.h" +#include "app/gui/style.h" +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" namespace yaze { namespace editor { -namespace { - -// Theme-matched colors -const ImVec4 kUserColor = ImVec4(0.90f, 0.70f, 0.00f, 1.0f); // Gold -const ImVec4 kAgentColor = ImVec4(0.40f, 0.76f, 0.64f, 1.0f); // Teal -const ImVec4 kTimestampColor = ImVec4(0.6f, 0.6f, 0.6f, 0.9f); -const ImVec4 kAccentColor = ImVec4(0.26f, 0.59f, 0.98f, 1.0f); // Theme blue -const ImVec4 kBackgroundColor = ImVec4(0.10f, 0.10f, 0.13f, 0.98f); // Darker -const ImVec4 kHeaderColor = ImVec4(0.14f, 0.14f, 0.16f, 1.0f); - -} // namespace - AgentChatHistoryPopup::AgentChatHistoryPopup() { std::memset(input_buffer_, 0, sizeof(input_buffer_)); } @@ -30,6 +21,8 @@ AgentChatHistoryPopup::AgentChatHistoryPopup() { void AgentChatHistoryPopup::Draw() { if (!visible_) return; + const auto& theme = AgentUI::GetTheme(); + // Set drawer position on the LEFT side (full height) ImGuiIO& io = ImGui::GetIO(); @@ -54,7 +47,7 @@ void AgentChatHistoryPopup::Draw() { // Calculate proper list height float list_height = ImGui::GetContentRegionAvail().y - 220.0f; - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.05f, 0.05f, 0.08f, 0.95f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color); ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); DrawMessageList();