From 57cac6a451ab421d09435e309d38fe46fb0432a4 Mon Sep 17 00:00:00 2001 From: JiXieShi Date: Fri, 12 Sep 2025 19:04:20 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E:=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89=E4=B8=BB=E9=A2=98=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E7=9B=B8=E5=85=B3=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/inc/AppState.h | 30 +++- app/inc/Manager.h | 132 +++++++++------- app/src/AppState.cpp | 87 +++++++++++ app/src/Manager.cpp | 353 +++++++++++++++++++++++++++++++++++++++---- app/src/Units.cpp | 110 ++++++++++---- 5 files changed, 587 insertions(+), 125 deletions(-) diff --git a/app/inc/AppState.h b/app/inc/AppState.h index aff72b8..f44f529 100644 --- a/app/inc/AppState.h +++ b/app/inc/AppState.h @@ -5,7 +5,26 @@ #include #include #include -#include + +#include "imgui.h" + + +struct LogColors { + ImVec4 error_color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + ImVec4 warn_color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + ImVec4 info_color = ImVec4(0.4f, 1.0f, 0.4f, 1.0f); + ImVec4 debug_color = ImVec4(0.6f, 0.6f, 1.0f, 1.0f); + ImVec4 trace_color = ImVec4(0.8f, 0.8f, 0.8f, 1.0f); + + void ResetToDefaults() { + error_color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + warn_color = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + info_color = ImVec4(0.4f, 1.0f, 0.4f, 1.0f); + debug_color = ImVec4(0.6f, 0.6f, 1.0f, 1.0f); + trace_color = ImVec4(0.8f, 0.8f, 0.8f, 1.0f); + } +}; + class AppState { public: @@ -50,8 +69,12 @@ public: std::vector command_history; int max_command_history; + LogColors log_colors; + bool use_custom_log_colors = false; + bool use_ansi_colors = true; bool settings_dirty; + private: // 环境变量序列化辅助函数 std::string SerializeEnvironmentVariables() const; @@ -60,8 +83,11 @@ private: // 编码序列化辅助函数 std::string SerializeOutputEncoding() const; void DeserializeOutputEncoding(const std::string& serialized); + // 日志颜色序列化辅助 + std::string SerializeLogColors() const; + void DeserializeLogColors(const std::string &serialized); - // 新增:命令历史记录序列化辅助函数 + // 命令历史记录序列化辅助函数 std::string SerializeCommandHistory() const; void DeserializeCommandHistory(const std::string& serialized); }; diff --git a/app/inc/Manager.h b/app/inc/Manager.h index b9453ac..7fd2cff 100644 --- a/app/inc/Manager.h +++ b/app/inc/Manager.h @@ -37,6 +37,7 @@ #include "AppState.h" #include "TrayIcon.h" + class Manager { public: // 构造函数和析构函数 @@ -45,59 +46,68 @@ public: ~Manager(); // 核心生命周期管理 - bool Initialize(); // 初始化应用程序 - void Run(); // 运行主循环 - void Shutdown(); // 关闭应用程序 + bool Initialize(); // 初始化应用程序 + void Run(); // 运行主循环 + void Shutdown(); // 关闭应用程序 // 托盘事件回调 void OnTrayShowWindow(); // 托盘显示窗口事件 - void OnTrayExit(); // 托盘退出事件 + void OnTrayExit(); // 托盘退出事件 // 公共成员变量 - AppState m_app_state; // 应用程序状态 + AppState m_app_state; // 应用程序状态 private: // 枚举类型定义 enum class LayoutPreset { - Classic, // 经典布局 - Development, // 开发布局 - Monitoring // 监控布局 + Classic, // 经典布局 + Development, // 开发布局 + Monitoring // 监控布局 }; // UI渲染相关方法 - void RenderUI(); // 渲染主UI - void RenderMenuBar(); // 渲染菜单栏 - void RenderMainContent(); // 渲染主内容区域 - void RenderSettingsMenu(); // 渲染设置菜单 - void RenderStopCommandSettings(); // 渲染停止命令设置 - void RenderEnvironmentVariablesSettings(); // 渲染环境变量设置 - void RenderOutputEncodingSettings(); // 渲染输出编码设置 - void RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth); // 渲染控制面板 - void RenderCommandPanel(float buttonWidth, float inputWidth); // 渲染命令面板 - void RenderLogPanel(); // 渲染日志面板 - void RenderCommandHistory(); // 渲染命令历史 - void RenderStatusMessages(); // 渲染状态消息 + void RenderUI(); // 渲染主UI + void RenderMenuBar(); // 渲染菜单栏 + void RenderMainContent(); // 渲染主内容区域 + + + void RenderSettingsMenu(); // 渲染设置菜单 + void RenderStopCommandSettings(); // 渲染停止命令设置 + void RenderEnvironmentVariablesSettings(); // 渲染环境变量设置 + void RenderOutputEncodingSettings(); // 渲染输出编码设置 + void RenderColorThemeSettings(); + + void RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth); // 渲染控制面板 + void RenderCommandPanel(float buttonWidth, float inputWidth); // 渲染命令面板 + void RenderLogPanel(); // 渲染日志面板 + void RenderCommandHistory(); // 渲染命令历史 + void RenderStatusMessages(); // 渲染状态消息 // 布局管理相关方法 - void SetupDefaultDockingLayout(ImGuiID dockspace_id); // 设置默认停靠布局 - void RenderLayoutMenu(); // 渲染布局菜单 - static void ApplyPresetLayout(LayoutPreset preset); // 应用预设布局 - void SaveCurrentLayout(); // 保存当前布局 - void LoadSavedLayout(); // 加载已保存布局 + void SetupDefaultDockingLayout(ImGuiID dockspace_id); // 设置默认停靠布局 + void RenderLayoutMenu(); // 渲染布局菜单 + static void ApplyPresetLayout(LayoutPreset preset); // 应用预设布局 + void SaveCurrentLayout(); // 保存当前布局 + void LoadSavedLayout(); // 加载已保存布局 // 事件处理相关方法 - void HandleMessages(); // 处理消息 - bool ShouldExit() const; // 检查是否应该退出 - static void ContentScaleCallback(GLFWwindow *window, float xscale, float yscale); // 内容缩放回调 + void HandleMessages(); // 处理消息 + bool ShouldExit() const; // 检查是否应该退出 + static void ContentScaleCallback(GLFWwindow *window, float xscale, float yscale); // 内容缩放回调 // 窗口管理相关方法 - void ShowMainWindow(); // 显示主窗口 - void HideMainWindow(); // 隐藏主窗口 + void ShowMainWindow(); // 显示主窗口 + void HideMainWindow(); // 隐藏主窗口 // DPI相关方法 - void UpdateDPIScale(); // 更新DPI缩放 - void ReloadFonts() const; // 重新加载字体 + void UpdateDPIScale(); // 更新DPI缩放 + void ReloadFonts() const; // 重新加载字体 + + void SaveCurrentTheme(); + void LoadSavedTheme(); + + ImVec4 GetCustomLogLevelColor(const std::string &log); // 平台相关初始化方法 #ifdef USE_WIN32_BACKEND @@ -108,17 +118,17 @@ private: static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // 窗口过程 #else - bool InitializeGLFW(); // 初始化GLFW - void CleanupGLFW(); // 清理GLFW - static void GlfwErrorCallback(int error, const char *description); // GLFW错误回调 + bool InitializeGLFW(); // 初始化GLFW + void CleanupGLFW(); // 清理GLFW + static void GlfwErrorCallback(int error, const char *description); // GLFW错误回调 #endif // 托盘相关方法 - bool InitializeTray(); // 初始化托盘 - void CleanupTray(); // 清理托盘 + bool InitializeTray(); // 初始化托盘 + void CleanupTray(); // 清理托盘 #ifdef _WIN32 - static HWND CreateHiddenWindow(); // 创建隐藏窗口 + static HWND CreateHiddenWindow(); // 创建隐藏窗口 #endif // 平台相关成员变量 @@ -130,41 +140,47 @@ private: IDXGISwapChain* m_pSwapChain = nullptr; // 交换链 ID3D11RenderTargetView* m_mainRenderTargetView = nullptr; // 主渲染目标视图 #else - GLFWwindow *m_window = nullptr; // GLFW窗口 - const char *m_glsl_version = nullptr; // GLSL版本 + GLFWwindow *m_window = nullptr; // GLFW窗口 + const char *m_glsl_version = nullptr; // GLSL版本 #endif // 托盘相关成员变量 - std::unique_ptr m_tray; // 托盘图标 + std::unique_ptr m_tray; // 托盘图标 #ifdef _WIN32 - HWND m_tray_hwnd = nullptr; // 托盘窗口句柄 + HWND m_tray_hwnd = nullptr; // 托盘窗口句柄 #endif // 控制标志 - bool m_should_exit = false; // 是否应该退出 - bool m_initialized = false; // 是否已初始化 + bool m_should_exit = false; // 是否应该退出 + bool m_initialized = false; // 是否已初始化 bool m_fullscreen = false; bool m_padding = false; // DPI缩放相关 - float m_dpi_scale = 1.0f; // 当前DPI缩放 - float m_last_dpi_scale = 1.0f; // 上次DPI缩放 + float m_dpi_scale = 1.0f; // 当前DPI缩放 + float m_last_dpi_scale = 1.0f; // 上次DPI缩放 // 布局相关成员变量 ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_MenuBar; - bool m_apply_preset_layout = false; // 是否需要应用预设布局 - LayoutPreset m_pending_preset = LayoutPreset::Classic; // 待应用的预设布局 - bool m_reset_layout = false; // 是否重置布局 - bool m_show_save_success = false; // 是否显示保存成功消息 - bool m_show_load_success = false; // 是否显示加载成功消息 - float m_save_success_timer = 0.0f; // 保存成功消息计时器 - float m_load_success_timer = 0.0f; // 加载成功消息计时器 + bool m_apply_preset_layout = false; // 是否需要应用预设布局 + LayoutPreset m_pending_preset = LayoutPreset::Classic; // 待应用的预设布局 + bool m_reset_layout = false; // 是否重置布局 + bool m_show_save_success = false; // 是否显示保存成功消息 + bool m_show_load_success = false; // 是否显示加载成功消息 + float m_save_success_timer = 0.0f; // 保存成功消息计时器 + float m_load_success_timer = 0.0f; // 加载成功消息计时器 // UI状态相关成员变量 - char env_key_input_[256] = {}; // 环境变量键输入缓冲区 - char env_value_input_[512] = {}; // 环境变量值输入缓冲区 - bool show_env_settings_ = false; // 是否显示环境变量设置 - bool show_encoding_settings_ = false; // 是否显示编码设置 - bool show_command_history_ = false; // 是否显示命令历史 + char env_key_input_[256] = {}; // 环境变量键输入缓冲区 + char env_value_input_[512] = {}; // 环境变量值输入缓冲区 + bool show_env_settings_ = false; // 是否显示环境变量设置 + bool show_encoding_settings_ = false; // 是否显示编码设置 + bool show_command_history_ = false; // 是否显示命令历史 + + bool m_show_theme_save_success = true; + float m_theme_save_success_timer = 3.0f; + + float m_theme_load_success_timer = 0.0f; + bool m_show_theme_load_success = false; }; diff --git a/app/src/AppState.cpp b/app/src/AppState.cpp index b9b93bc..bf63c15 100644 --- a/app/src/AppState.cpp +++ b/app/src/AppState.cpp @@ -160,6 +160,82 @@ void AppState::DeserializeOutputEncoding(const std::string& serialized) { } } +std::string AppState::SerializeLogColors() const { + std::ostringstream oss; + + // 按顺序保存每个颜色的RGBA值 + oss << log_colors.error_color.x << "," + << log_colors.error_color.y << "," + << log_colors.error_color.z << "," + << log_colors.error_color.w << "|"; + + oss << log_colors.warn_color.x << "," + << log_colors.warn_color.y << "," + << log_colors.warn_color.z << "," + << log_colors.warn_color.w << "|"; + + oss << log_colors.info_color.x << "," + << log_colors.info_color.y << "," + << log_colors.info_color.z << "," + << log_colors.info_color.w << "|"; + + oss << log_colors.debug_color.x << "," + << log_colors.debug_color.y << "," + << log_colors.debug_color.z << "," + << log_colors.debug_color.w << "|"; + + oss << log_colors.trace_color.x << "," + << log_colors.trace_color.y << "," + << log_colors.trace_color.z << "," + << log_colors.trace_color.w; + + return oss.str(); +} + +// 新增:反序列化日志颜色 +void AppState::DeserializeLogColors(const std::string& serialized) { + if (serialized.empty()) { + log_colors.ResetToDefaults(); + return; + } + + std::istringstream iss(serialized); + std::string colorStr; + int colorIndex = 0; + + while (std::getline(iss, colorStr, '|') && colorIndex < 5) { + std::istringstream colorIss(colorStr); + std::string component; + float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + int i = 0; + + while (std::getline(colorIss, component, ',') && i < 4) { + try { + rgba[i++] = std::stof(component); + } catch (const std::exception&) { + // 如果解析失败,使用默认值 + } + } + + ImVec4 color(rgba[0], rgba[1], rgba[2], rgba[3]); + + switch (colorIndex) { + case 0: log_colors.error_color = color; break; + case 1: log_colors.warn_color = color; break; + case 2: log_colors.info_color = color; break; + case 3: log_colors.debug_color = color; break; + case 4: log_colors.trace_color = color; break; + } + + colorIndex++; + } + + // 如果没有足够的颜色数据,重置为默认值 + if (colorIndex < 5) { + log_colors.ResetToDefaults(); + } +} + void AppState::LoadSettings() { std::ifstream file("climanager_settings.ini"); if (!file.is_open()) return; @@ -234,6 +310,14 @@ void AppState::LoadSettings() { else if (key == "MaxCommandHistory") { max_command_history = std::stoi(value); max_command_history = std::max(5, std::min(max_command_history, 100)); + }else if (key == "UseCustomLogColors") { + use_custom_log_colors = (value == "1"); + } + else if (key == "UseAnsiColors") { + use_ansi_colors = (value == "1"); + } + else if (key == "LogColors") { + DeserializeLogColors(value); } } } @@ -271,6 +355,9 @@ void AppState::SaveSettings() { file << "CommandHistory=" << SerializeCommandHistory() << "\n"; file << "MaxCommandHistory=" << max_command_history << "\n"; + file << "UseCustomLogColors=" << (use_custom_log_colors ? "1" : "0") << "\n"; + file << "UseAnsiColors=" << (use_ansi_colors ? "1" : "0") << "\n"; + file << "LogColors=" << SerializeLogColors() << "\n"; file.close(); settings_dirty = false; diff --git a/app/src/Manager.cpp b/app/src/Manager.cpp index 51c9bf6..5852bf4 100644 --- a/app/src/Manager.cpp +++ b/app/src/Manager.cpp @@ -28,10 +28,10 @@ bool Manager::Initialize() { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking -// io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows io.ConfigViewportsNoAutoMerge = true; io.IniFilename = "imgui.ini"; @@ -67,10 +67,10 @@ bool Manager::Initialize() { // 加载字体 #ifdef _WIN32 ImFont *font = io.Fonts->AddFontFromFileTTF( - "C:/Windows/Fonts/msyh.ttc", - 16.0f * m_dpi_scale, - nullptr, - io.Fonts->GetGlyphRangesChineseFull() + "C:/Windows/Fonts/msyh.ttc", + 16.0f * m_dpi_scale, + nullptr, + io.Fonts->GetGlyphRangesChineseFull() ); #elif __APPLE__ // macOS 中文字体路径 @@ -113,6 +113,7 @@ bool Manager::Initialize() { m_app_state.ApplySettings(); m_app_state.SaveSettings(); + LoadSavedTheme(); #ifdef _WIN32 m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url)); @@ -214,7 +215,7 @@ void Manager::RenderUI() { ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove; + ImGuiWindowFlags_NoMove; window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; } else { dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; @@ -226,7 +227,7 @@ void Manager::RenderUI() { if (!m_padding) ImGui::PopStyleVar(); if (m_fullscreen) - ImGui::PopStyleVar(2); // Submit the DockSpace + ImGui::PopStyleVar(2); // Submit the DockSpace ImGui::Begin("CLI程序管理工具", &m_app_state.show_main_window, window_flags); @@ -302,9 +303,10 @@ void Manager::RenderMenuBar() { // 添加主题菜单 if (ImGui::BeginMenu("主题")) { - if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); } - if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); } - if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); } + // if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); } + // if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); } + // if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); } + RenderColorThemeSettings(); ImGui::EndMenu(); } @@ -313,7 +315,7 @@ void Manager::RenderMenuBar() { ImGui::MenuItem("填充(Padding)", nullptr, &m_padding); ImGui::Separator(); if (ImGui::MenuItem("标志:不分割(Flag: NoSplit)", "", (dockspace_flags & ImGuiDockNodeFlags_NoSplit) != - 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoSplit; } + 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoSplit; } if (ImGui::MenuItem("标志:不调整大小(Flag: NoResize)", "", (dockspace_flags & ImGuiDockNodeFlags_NoResize) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoResize; } @@ -510,6 +512,45 @@ void Manager::RenderStatusMessages() { m_show_load_success = false; } } + + if (m_show_theme_save_success) { + ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always, + ImVec2(0.5f, 0.0f)); + ImGui::SetNextWindowBgAlpha(0.8f); + + if (ImGui::Begin("ThemeSaveSuccess", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "主题已保存"); + } + ImGui::End(); + + m_theme_save_success_timer -= ImGui::GetIO().DeltaTime; + if (m_theme_save_success_timer <= 0.0f) { + m_show_theme_save_success = false; + } + } + + // 渲染主题加载成功提示 + if (m_show_theme_load_success) { + ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always, + ImVec2(0.5f, 0.0f)); + ImGui::SetNextWindowBgAlpha(0.8f); + + if (ImGui::Begin("ThemeLoadSuccess", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "主题已加载"); + } + ImGui::End(); + + m_theme_load_success_timer -= ImGui::GetIO().DeltaTime; + if (m_theme_load_success_timer <= 0.0f) { + m_show_theme_load_success = false; + } + } } void Manager::RenderMainContent() { @@ -585,6 +626,7 @@ void Manager::RenderSettingsMenu() { RenderStopCommandSettings(); RenderEnvironmentVariablesSettings(); RenderOutputEncodingSettings(); + } void Manager::RenderStopCommandSettings() { @@ -690,7 +732,7 @@ void Manager::RenderEnvironmentVariablesSettings() { ImGui::Spacing(); ImGui::TextWrapped( - "说明:启用后,CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并,同名变量会被覆盖。"); + "说明:启用后,CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并,同名变量会被覆盖。"); ImGui::Unindent(); } else { @@ -773,8 +815,9 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in m_app_state.AddCommandToHistory(m_app_state.command_input); if (strlen(m_app_state.working_directory) > 0) { m_app_state.cli_process.SetWorkingDirectory(m_app_state.working_directory); - }else { - strncpy_s(m_app_state.working_directory,m_app_state.cli_process.GetWorkingDirectory().c_str(),sizeof(m_app_state.working_directory)-1); + } else { + strncpy_s(m_app_state.working_directory, m_app_state.cli_process.GetWorkingDirectory().c_str(), + sizeof(m_app_state.working_directory) - 1); } m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_app_state.cli_process.GetPid()); @@ -800,9 +843,9 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in // 状态显示 ImGui::SeparatorText("运行状态"); - ImVec4 statusColor = m_app_state.cli_process.IsRunning() ? - ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : - ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + ImVec4 statusColor = m_app_state.cli_process.IsRunning() + ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) + : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); ImGui::TextColored(statusColor, "状态: %s", m_app_state.cli_process.IsRunning() ? "运行中" : "已停止"); } @@ -875,11 +918,17 @@ void Manager::RenderLogPanel() { const std::string &log = logs[i]; if (m_app_state.enable_colored_logs) { - RenderColoredLogLine(log); + if (m_app_state.use_ansi_colors) { + // 使用ANSI颜色转义序列解析 + RenderColoredLogLine(log); + } else { + // 仅使用日志级别的颜色区分 + ImVec4 textColor = GetCustomLogLevelColor(log); + ImGui::TextColored(textColor, "%s", log.c_str()); + } } else { - // 简单的日志级别颜色区分 - ImVec4 textColor = GetLogLevelColor(log); - ImGui::TextColored(textColor, "%s", log.c_str()); + // 不启用彩色显示,使用默认白色 + ImGui::TextUnformatted(log.c_str()); } } } @@ -1087,14 +1136,14 @@ HWND Manager::CreateHiddenWindow() { } return CreateWindowEx( - 0, - wc.lpszClassName, - L"CLI Manager Tray Window", - 0, - 0, 0, 0, 0, - NULL, NULL, - wc.hInstance, - NULL + 0, + wc.lpszClassName, + L"CLI Manager Tray Window", + 0, + 0, 0, 0, 0, + NULL, NULL, + wc.hInstance, + NULL ); } @@ -1303,7 +1352,7 @@ bool Manager::InitializeGLFW() { int windowWidth = static_cast(screenWidth * 0.8); int windowHeight = static_cast(screenHeight * 0.8); -// glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//窗口隐藏 + // glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//窗口隐藏 m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr); if (!m_window) @@ -1502,3 +1551,241 @@ extern "C" { void* GetMacTrayIcon(); } #endif + + +void Manager::RenderColorThemeSettings() { + // 预设主题 + if (ImGui::MenuItem("暗黑(Dark)")) { + ImGui::StyleColorsDark(); + } + if (ImGui::MenuItem("明亮(Light)")) { + ImGui::StyleColorsLight(); + } + if (ImGui::MenuItem("经典(Classic)")) { + ImGui::StyleColorsClassic(); + } + ImGui::Separator(); + ImGui::Text("颜色主题设置"); + + if (ImGui::Checkbox("自定义日志颜色", &m_app_state.use_custom_log_colors)) { + m_app_state.settings_dirty = true; + } + + if (m_app_state.use_custom_log_colors) { + ImGui::Indent(); + + ImGui::Text("日志级别颜色:"); + + // 编辑错误日志颜色 + ImVec4 errorColor = ImVec4( + m_app_state.log_colors.error_color.x, + m_app_state.log_colors.error_color.y, + m_app_state.log_colors.error_color.z, + 1.0f + ); + if (ImGui::ColorEdit3("错误日志", (float *) &errorColor)) { + m_app_state.log_colors.error_color = errorColor; + m_app_state.settings_dirty = true; + } + + // 编辑警告日志颜色 + ImVec4 warnColor = ImVec4( + m_app_state.log_colors.warn_color.x, + m_app_state.log_colors.warn_color.y, + m_app_state.log_colors.warn_color.z, + 1.0f + ); + if (ImGui::ColorEdit3("警告日志", (float *) &warnColor)) { + m_app_state.log_colors.warn_color = warnColor; + m_app_state.settings_dirty = true; + } + + // 编辑信息日志颜色 + ImVec4 infoColor = ImVec4( + m_app_state.log_colors.info_color.x, + m_app_state.log_colors.info_color.y, + m_app_state.log_colors.info_color.z, + 1.0f + ); + if (ImGui::ColorEdit3("信息日志", (float *) &infoColor)) { + m_app_state.log_colors.info_color = infoColor; + m_app_state.settings_dirty = true; + } + + // 编辑调试日志颜色 + ImVec4 debugColor = ImVec4( + m_app_state.log_colors.debug_color.x, + m_app_state.log_colors.debug_color.y, + m_app_state.log_colors.debug_color.z, + 1.0f + ); + if (ImGui::ColorEdit3("调试日志", (float *) &debugColor)) { + m_app_state.log_colors.debug_color = debugColor; + m_app_state.settings_dirty = true; + } + + // 重置为默认颜色按钮 + if (ImGui::Button("重置为默认颜色")) { + m_app_state.log_colors.ResetToDefaults(); + m_app_state.settings_dirty = true; + } + + ImGui::Unindent(); + } + + // ANSI颜色设置 + ImGui::Separator(); + + if (ImGui::Checkbox("使用ANSI颜色转义", &m_app_state.use_ansi_colors)) { + m_app_state.settings_dirty = true; + } + + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted("启用后,日志中的ANSI颜色转义序列将被解析并显示为彩色文本。"); + ImGui::TextUnformatted("例如: \\033[31m红色文本\\033[0m"); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + // 颜色样式设置 + ImGui::Text("全局颜色样式:"); + + static int currentTheme = 0; // 默认暗色主题 + const char *themes[] = {"暗黑 (Dark)", "明亮 (Light)", "经典 (Classic)", "自定义 (Custom)"}; + + if (ImGui::Combo("应用主题", ¤tTheme, themes, IM_ARRAYSIZE(themes))) { + switch (currentTheme) { + case 0: ImGui::StyleColorsDark(); + break; + case 1: ImGui::StyleColorsLight(); + break; + case 2: ImGui::StyleColorsClassic(); + break; + case 3: /* 应用自定义主题 */ break; + } + } + + // 自定义主题编辑器(仅在选择"自定义"主题时显示) + if (currentTheme == 3) { + if (ImGui::TreeNode("自定义主题编辑器")) { + ImGuiStyle &style = ImGui::GetStyle(); + + // 添加主题颜色编辑器 + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char *name = ImGui::GetStyleColorName(i); + ImGui::ColorEdit4(name, (float *) &style.Colors[i]); + } + + // 添加样式参数编辑器 + ImGui::SliderFloat("窗口圆角", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("子窗口圆角", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("框架圆角", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("弹出窗口圆角", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("滚动条圆角", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("抓取圆角", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("标签圆角", &style.TabRounding, 0.0f, 12.0f, "%.0f"); + + ImGui::Separator(); + + // 保存/加载主题按钮 + if (ImGui::Button("保存当前主题")) { + SaveCurrentTheme(); + } + + ImGui::SameLine(); + + if (ImGui::Button("加载保存的主题")) { + LoadSavedTheme(); + } + + ImGui::TreePop(); + } + } +} + +// 保存当前主题设置 +void Manager::SaveCurrentTheme() { + std::ofstream file("theme.ini", std::ios::binary); + if (file.is_open()) { + ImGuiStyle &style = ImGui::GetStyle(); + + // 保存主题颜色 + for (int i = 0; i < ImGuiCol_COUNT; i++) { + file.write(reinterpret_cast(&style.Colors[i]), sizeof(ImVec4)); + } + + // 保存样式参数 + file.write(reinterpret_cast(&style.WindowRounding), sizeof(float)); + file.write(reinterpret_cast(&style.ChildRounding), sizeof(float)); + file.write(reinterpret_cast(&style.FrameRounding), sizeof(float)); + file.write(reinterpret_cast(&style.PopupRounding), sizeof(float)); + file.write(reinterpret_cast(&style.ScrollbarRounding), sizeof(float)); + file.write(reinterpret_cast(&style.GrabRounding), sizeof(float)); + file.write(reinterpret_cast(&style.TabRounding), sizeof(float)); + + file.close(); + + // 显示保存成功提示 + m_show_theme_save_success = true; + m_theme_save_success_timer = 3.0f; // 3秒后消失 + } +} + +// 加载保存的主题设置 +void Manager::LoadSavedTheme() { + std::ifstream file("theme.ini", std::ios::binary); + if (file.is_open()) { + ImGuiStyle &style = ImGui::GetStyle(); + + // 加载主题颜色 + for (int i = 0; i < ImGuiCol_COUNT; i++) { + file.read(reinterpret_cast(&style.Colors[i]), sizeof(ImVec4)); + } + + // 加载样式参数 + file.read(reinterpret_cast(&style.WindowRounding), sizeof(float)); + file.read(reinterpret_cast(&style.ChildRounding), sizeof(float)); + file.read(reinterpret_cast(&style.FrameRounding), sizeof(float)); + file.read(reinterpret_cast(&style.PopupRounding), sizeof(float)); + file.read(reinterpret_cast(&style.ScrollbarRounding), sizeof(float)); + file.read(reinterpret_cast(&style.GrabRounding), sizeof(float)); + file.read(reinterpret_cast(&style.TabRounding), sizeof(float)); + + file.close(); + + // 显示加载成功提示 + m_show_theme_load_success = true; + m_theme_load_success_timer = 3.0f; // 3秒后消失 + } +} + +ImVec4 Manager::GetCustomLogLevelColor(const std::string &log) { + if (!m_app_state.use_custom_log_colors) { + // 使用默认颜色 + return GetLogLevelColor(log); + } + + // 使用自定义颜色 + if (log.find("错误") != std::string::npos || log.find("[E]") != std::string::npos || + log.find("[ERROR]") != std::string::npos || log.find("error") != std::string::npos) { + return m_app_state.log_colors.error_color; + } else if (log.find("警告") != std::string::npos || log.find("[W]") != std::string::npos || + log.find("[WARN]") != std::string::npos || log.find("warning") != std::string::npos) { + return m_app_state.log_colors.warn_color; + } else if (log.find("信息") != std::string::npos || log.find("[I]") != std::string::npos || + log.find("[INFO]") != std::string::npos || log.find("info") != std::string::npos) { + return m_app_state.log_colors.info_color; + } else if (log.find("调试") != std::string::npos || log.find("[D]") != std::string::npos || + log.find("[DEBUG]") != std::string::npos || log.find("debug") != std::string::npos) { + return m_app_state.log_colors.debug_color; + } else if (log.find("跟踪") != std::string::npos || log.find("[T]") != std::string::npos || + log.find("[TRACE]") != std::string::npos || log.find("trace") != std::string::npos) { + return m_app_state.log_colors.trace_color; + } + + return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色 +} diff --git a/app/src/Units.cpp b/app/src/Units.cpp index ac678e5..03a8c60 100644 --- a/app/src/Units.cpp +++ b/app/src/Units.cpp @@ -6,7 +6,7 @@ #include #include -std::wstring StringToWide(const std::string& str) { +std::wstring StringToWide(const std::string &str) { if (str.empty()) return L""; int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); std::wstring wstr(size, 0); @@ -14,7 +14,7 @@ std::wstring StringToWide(const std::string& str) { return wstr; } -std::string WideToString(const std::wstring& wstr) { +std::string WideToString(const std::wstring &wstr) { if (wstr.empty()) return ""; int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); std::string str(size, 0); @@ -44,9 +44,8 @@ void SetAutoStart(bool enable) { if (enable) { RegSetValueEx(hKey, keyName, 0, REG_SZ, - (BYTE*)exePath, (wcslen(exePath) + 1) * sizeof(WCHAR)); - } - else { + (BYTE *) exePath, (wcslen(exePath) + 1) * sizeof(WCHAR)); + } else { RegDeleteValue(hKey, keyName); } RegCloseKey(hKey); @@ -79,6 +78,7 @@ bool IsAutoStartEnabled() { return exists; } +// 日志颜色处理函数 ImVec4 GetLogLevelColor(const std::string &log) { // 简单的日志级别颜色区分 if (log.find("错误") != std::string::npos || log.find("[E]") != std::string::npos || @@ -90,11 +90,17 @@ ImVec4 GetLogLevelColor(const std::string &log) { } else if (log.find("信息") != std::string::npos || log.find("[I]") != std::string::npos || log.find("[INFO]") != std::string::npos || log.find("info") != std::string::npos) { return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // 绿色 + } else if (log.find("调试") != std::string::npos || log.find("[D]") != std::string::npos || + log.find("[DEBUG]") != std::string::npos || log.find("debug") != std::string::npos) { + return ImVec4(0.6f, 0.6f, 1.0f, 1.0f); // 蓝色 + } else if (log.find("跟踪") != std::string::npos || log.find("[T]") != std::string::npos || + log.find("[TRACE]") != std::string::npos || log.find("trace") != std::string::npos) { + return ImVec4(0.8f, 0.8f, 0.8f, 1.0f); // 灰色 } return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色 } - +// ANSI颜色处理增强版本 void RenderColoredLogLine(const std::string &log) { auto segments = ParseAnsiColorCodes(log); @@ -197,18 +203,18 @@ ParseAnsiColorCode(const std::string &code, const ImVec4 ¤tColor, bool cur for (int colorCode: codes) { switch (colorCode) { - case 0: // 重置 + case 0: // 重置 newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); newBold = false; break; - case 1: // 粗体/亮色 + case 1: // 粗体/亮色 newBold = true; break; case 22: // 正常强度 newBold = false; break; - // 前景色 (30-37) + // 前景色 (30-37) case 30: newColor = GetAnsiColor(0, newBold); break; // 黑色 @@ -237,13 +243,13 @@ ParseAnsiColorCode(const std::string &code, const ImVec4 ¤tColor, bool cur newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); break; // 默认前景色 - // 亮色前景色 (90-97) + // 亮色前景色 (90-97) case 90: newColor = GetAnsiColor(8, false); - break; // 亮黑色(灰色) + break; // 亮黑色(灰色) case 91: newColor = GetAnsiColor(9, false); - break; // 亮红色 + break; // 亮红色 case 92: newColor = GetAnsiColor(10, false); break; // 亮绿色 @@ -263,10 +269,49 @@ ParseAnsiColorCode(const std::string &code, const ImVec4 ¤tColor, bool cur newColor = GetAnsiColor(15, false); break; // 亮白色 - // 背景色暂时忽略 (40-47, 100-107) + // 增加对背景色的支持 (40-47, 100-107) + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + // 背景色暂时不处理,因为ImGui不容易实现背景色 + break; + default: // 处理256色和RGB色彩(38;5;n 和 38;2;r;g;b) - // 这里可以根据需要扩展 + if (colorCode == 38 || colorCode == 48) { + // 38=前景,48=背景 + // 检查是否是256色模式或RGB模式 + if (codes.size() > 1) { + if (codes[1] == 5 && codes.size() > 2) { + // 256色模式 + // 暂时不实现所有256色,仅处理基本的16色 + int colorIndex = codes[2]; + if (colorIndex >= 0 && colorIndex < 16) { + newColor = GetAnsiColor(colorIndex, newBold); + } + } else if (codes[1] == 2 && codes.size() > 4) { + // RGB模式 + // 处理RGB值 + float r = static_cast(codes[2]) / 255.0f; + float g = static_cast(codes[3]) / 255.0f; + float b = static_cast(codes[4]) / 255.0f; + newColor = ImVec4(r, g, b, 1.0f); + } + } + } break; } } @@ -277,25 +322,25 @@ ParseAnsiColorCode(const std::string &code, const ImVec4 ¤tColor, bool cur ImVec4 GetAnsiColor(int colorIndex, bool bright) { // ANSI标准颜色表 static const ImVec4 ansiColors[16] = { - // 标准颜色 (0-7) - ImVec4(0.0f, 0.0f, 0.0f, 1.0f), // 0: 黑色 - ImVec4(0.8f, 0.0f, 0.0f, 1.0f), // 1: 红色 - ImVec4(0.0f, 0.8f, 0.0f, 1.0f), // 2: 绿色 - ImVec4(0.8f, 0.8f, 0.0f, 1.0f), // 3: 黄色 - ImVec4(0.0f, 0.0f, 0.8f, 1.0f), // 4: 蓝色 - ImVec4(0.8f, 0.0f, 0.8f, 1.0f), // 5: 洋红 - ImVec4(0.0f, 0.8f, 0.8f, 1.0f), // 6: 青色 - ImVec4(0.8f, 0.8f, 0.8f, 1.0f), // 7: 白色 + // 标准颜色 (0-7) + ImVec4(0.0f, 0.0f, 0.0f, 1.0f), // 0: 黑色 + ImVec4(0.8f, 0.0f, 0.0f, 1.0f), // 1: 红色 + ImVec4(0.0f, 0.8f, 0.0f, 1.0f), // 2: 绿色 + ImVec4(0.8f, 0.8f, 0.0f, 1.0f), // 3: 黄色 + ImVec4(0.0f, 0.0f, 0.8f, 1.0f), // 4: 蓝色 + ImVec4(0.8f, 0.0f, 0.8f, 1.0f), // 5: 洋红 + ImVec4(0.0f, 0.8f, 0.8f, 1.0f), // 6: 青色 + ImVec4(0.8f, 0.8f, 0.8f, 1.0f), // 7: 白色 - // 亮色 (8-15) - ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // 8: 亮黑色(灰色) - ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // 9: 亮红色 - ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // 10: 亮绿色 - ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // 11: 亮黄色 - ImVec4(0.0f, 0.0f, 1.0f, 1.0f), // 12: 亮蓝色 - ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // 13: 亮洋红 - ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // 14: 亮青色 - ImVec4(1.0f, 1.0f, 1.0f, 1.0f), // 15: 亮白色 + // 亮色 (8-15) + ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // 8: 亮黑色(灰色) + ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // 9: 亮红色 + ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // 10: 亮绿色 + ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // 11: 亮黄色 + ImVec4(0.0f, 0.0f, 1.0f, 1.0f), // 12: 亮蓝色 + ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // 13: 亮洋红 + ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // 14: 亮青色 + ImVec4(1.0f, 1.0f, 1.0f, 1.0f), // 15: 亮白色 }; if (colorIndex >= 0 && colorIndex < 16) { @@ -313,3 +358,4 @@ ImVec4 GetAnsiColor(int colorIndex, bool bright) { return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色 } +