UP 优化托盘界面

main
jixieshi 2025-09-09 00:57:21 +08:00
parent d2113117cc
commit 59c791c5a7
9 changed files with 460 additions and 502 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.31) cmake_minimum_required(VERSION 3.26)
project(CLI_Manager) project(CLI_Manager)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
@ -12,7 +12,7 @@ set(IMGUI_BACKENDS "glfw_opengl")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++") #set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
add_definitions(-DUNICODE -D_UNICODE) add_definitions(-DUNICODE -D_UNICODE)
@ -88,4 +88,3 @@ endif()
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES
LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows" LINK_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic -lpthread -Wl,-subsystem,windows"
) )

View File

@ -53,6 +53,8 @@ public:
void Stop(); void Stop();
void Restart(const std::string& command); void Restart(const std::string& command);
std::wstring GetPid() const;
void ClearLogs(); void ClearLogs();
void AddLog(const std::string& log); void AddLog(const std::string& log);
const std::vector<std::string>& GetLogs() const; const std::vector<std::string>& GetLogs() const;

View File

@ -8,25 +8,29 @@
// 平台相关头文件 // 平台相关头文件
#ifdef USE_WIN32_BACKEND #ifdef USE_WIN32_BACKEND
#include <d3d11.h> #include <d3d11.h>
#include <windows.h> #include <windows.h>
#include "imgui_impl_win32.h" #include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h" #include "imgui_impl_dx11.h"
#else #else
#ifdef _WIN32 #ifdef _WIN32
#define GLFW_EXPOSE_NATIVE_WIN32 #define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h> #include <GLFW/glfw3.h>
#include <windows.h> #include <GLFW/glfw3native.h>
#elif __APPLE__ #include <windows.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3.h> #elif __APPLE__
#include <GLFW/glfw3native.h> #define GLFW_EXPOSE_NATIVE_COCOA
#else #include <GLFW/glfw3.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3native.h>
#endif #else
#include "imgui_impl_glfw.h" #include <GLFW/glfw3.h>
#include "imgui_impl_opengl3.h" #endif
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#endif #endif
// 项目头文件 // 项目头文件
@ -37,6 +41,7 @@ class Manager {
public: public:
// 构造函数和析构函数 // 构造函数和析构函数
Manager(); Manager();
~Manager(); ~Manager();
// 核心生命周期管理 // 核心生命周期管理
@ -59,11 +64,6 @@ private:
Monitoring // 监控布局 Monitoring // 监控布局
}; };
// 结构体定义
struct ColoredTextSegment {
std::string text; // 文本内容
ImVec4 color; // 文本颜色
};
// UI渲染相关方法 // UI渲染相关方法
void RenderUI(); // 渲染主UI void RenderUI(); // 渲染主UI
@ -86,13 +86,6 @@ private:
void SaveCurrentLayout(); // 保存当前布局 void SaveCurrentLayout(); // 保存当前布局
void LoadSavedLayout(); // 加载已保存布局 void LoadSavedLayout(); // 加载已保存布局
// 日志颜色处理方法
ImVec4 GetLogLevelColor(const std::string& log); // 获取日志级别颜色
void RenderColoredLogLine(const std::string& log); // 渲染彩色日志行
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string& text); // 解析ANSI颜色代码
std::pair<ImVec4, bool> ParseAnsiColorCode(const std::string& code, const ImVec4& currentColor, bool currentBold); // 解析单个ANSI颜色代码
ImVec4 GetAnsiColor(int colorIndex, bool bright); // 获取ANSI颜色
// 事件处理相关方法 // 事件处理相关方法
void HandleMessages(); // 处理消息 void HandleMessages(); // 处理消息
bool ShouldExit() const; // 检查是否应该退出 bool ShouldExit() const; // 检查是否应该退出
@ -114,15 +107,17 @@ private:
void CleanupDirectX11(); // 清理DirectX11 void CleanupDirectX11(); // 清理DirectX11
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // 窗口过程 static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // 窗口过程
#else #else
bool InitializeGLFW(); // 初始化GLFW bool InitializeGLFW(); // 初始化GLFW
void CleanupGLFW(); // 清理GLFW void CleanupGLFW(); // 清理GLFW
static void GlfwErrorCallback(int error, const char* description); // GLFW错误回调 static void GlfwErrorCallback(int error, const char *description); // GLFW错误回调
#endif #endif
// 托盘相关方法 // 托盘相关方法
bool InitializeTray(); // 初始化托盘 bool InitializeTray(); // 初始化托盘
void CleanupTray(); // 清理托盘 void CleanupTray(); // 清理托盘
#ifdef _WIN32 #ifdef _WIN32
static HWND CreateHiddenWindow(); // 创建隐藏窗口 static HWND CreateHiddenWindow(); // 创建隐藏窗口
#endif #endif
@ -135,8 +130,8 @@ private:
IDXGISwapChain* m_pSwapChain = nullptr; // 交换链 IDXGISwapChain* m_pSwapChain = nullptr; // 交换链
ID3D11RenderTargetView* m_mainRenderTargetView = nullptr; // 主渲染目标视图 ID3D11RenderTargetView* m_mainRenderTargetView = nullptr; // 主渲染目标视图
#else #else
GLFWwindow* m_window = nullptr; // GLFW窗口 GLFWwindow *m_window = nullptr; // GLFW窗口
const char* m_glsl_version = nullptr; // GLSL版本 const char *m_glsl_version = nullptr; // GLSL版本
#endif #endif
// 托盘相关成员变量 // 托盘相关成员变量
@ -148,12 +143,16 @@ private:
// 控制标志 // 控制标志
bool m_should_exit = false; // 是否应该退出 bool m_should_exit = false; // 是否应该退出
bool m_initialized = false; // 是否已初始化 bool m_initialized = false; // 是否已初始化
bool m_fullscreen = false;
bool m_padding = false;
// DPI缩放相关 // DPI缩放相关
float m_dpi_scale = 1.0f; // 当前DPI缩放 float m_dpi_scale = 1.0f; // 当前DPI缩放
float m_last_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; // 是否需要应用预设布局 bool m_apply_preset_layout = false; // 是否需要应用预设布局
LayoutPreset m_pending_preset = LayoutPreset::Classic; // 待应用的预设布局 LayoutPreset m_pending_preset = LayoutPreset::Classic; // 待应用的预设布局
bool m_reset_layout = false; // 是否重置布局 bool m_reset_layout = false; // 是否重置布局
@ -168,4 +167,4 @@ private:
bool show_env_settings_ = false; // 是否显示环境变量设置 bool show_env_settings_ = false; // 是否显示环境变量设置
bool show_encoding_settings_ = false; // 是否显示编码设置 bool show_encoding_settings_ = false; // 是否显示编码设置
bool show_command_history_ = false; // 是否显示命令历史 bool show_command_history_ = false; // 是否显示命令历史
}; };

View File

@ -17,6 +17,8 @@ public:
#ifdef _WIN32 #ifdef _WIN32
TrayIcon(HWND hwnd, HICON icon); TrayIcon(HWND hwnd, HICON icon);
void UpdateWebUrl(const std::wstring& url); void UpdateWebUrl(const std::wstring& url);
void UpdateStatus(const std::wstring &status, const std::wstring &pid);
// 静态窗口过程 // 静态窗口过程
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
#else #else
@ -45,6 +47,8 @@ private:
HICON m_icon; HICON m_icon;
NOTIFYICONDATA m_nid{}; NOTIFYICONDATA m_nid{};
std::wstring m_web_url; std::wstring m_web_url;
std::wstring m_status;
std::wstring m_pid;
HMENU m_menu; HMENU m_menu;
#else #else
void ShowMacTrayIcon(); void ShowMacTrayIcon();
@ -73,4 +77,4 @@ extern "C" {
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -1,11 +1,26 @@
#ifndef UNITS_H #ifndef UNITS_H
#define UNITS_H #define UNITS_H
#include <string> #include <string>
#include <imgui.h>
#include <vector>
std::wstring StringToWide(const std::string& str); std::wstring StringToWide(const std::string& str);
std::string WideToString(const std::wstring& wstr); std::string WideToString(const std::wstring& wstr);
void SetAutoStart(bool enable); void SetAutoStart(bool enable);
bool IsAutoStartEnabled(); bool IsAutoStartEnabled();
// 结构体定义
struct ColoredTextSegment {
std::string text; // 文本内容
ImVec4 color; // 文本颜色
};
// 日志颜色处理方法
ImVec4 GetLogLevelColor(const std::string &log); // 获取日志级别颜色
void RenderColoredLogLine(const std::string &log); // 渲染彩色日志行
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string &text); // 解析ANSI颜色代码
std::pair<ImVec4, bool>
ParseAnsiColorCode(const std::string &code, const ImVec4 &currentColor, bool currentBold); // 解析单个ANSI颜色代码
ImVec4 GetAnsiColor(int colorIndex, bool bright); // 获取ANSI颜色
#endif //UNITS_H #endif //UNITS_H

View File

@ -894,3 +894,8 @@ while (process_running_) {
} }
#endif #endif
} }
std::wstring CLIProcess::GetPid() const {
if (pi_.hProcess == nullptr) return L"";
return StringToWide(std::to_string(pi_.dwProcessId));
}

View File

@ -27,14 +27,16 @@ bool Manager::Initialize() {
// 初始化ImGui // 初始化ImGui
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; 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.ConfigViewportsNoAutoMerge = true;
io.IniFilename = "imgui.ini"; io.IniFilename = "imgui.ini";
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
ImGuiStyle& style = ImGui::GetStyle(); ImGuiStyle &style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
style.WindowRounding = 0.0f; style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f; style.Colors[ImGuiCol_WindowBg].w = 1.0f;
@ -59,16 +61,16 @@ bool Manager::Initialize() {
ImGui_ImplOpenGL3_Init(m_glsl_version); ImGui_ImplOpenGL3_Init(m_glsl_version);
float xscale, yscale; float xscale, yscale;
glfwGetWindowContentScale(m_window, &xscale, &yscale); glfwGetWindowContentScale(m_window, &xscale, &yscale);
m_dpi_scale=xscale; m_dpi_scale = xscale;
#endif #endif
// 加载字体 // 加载字体
#ifdef _WIN32 #ifdef _WIN32
ImFont* font = io.Fonts->AddFontFromFileTTF( ImFont *font = io.Fonts->AddFontFromFileTTF(
"C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/msyh.ttc",
16.0f*m_dpi_scale, 16.0f * m_dpi_scale,
nullptr, nullptr,
io.Fonts->GetGlyphRangesChineseFull() io.Fonts->GetGlyphRangesChineseFull()
); );
#elif __APPLE__ #elif __APPLE__
// macOS 中文字体路径 // macOS 中文字体路径
@ -112,6 +114,7 @@ bool Manager::Initialize() {
#ifdef _WIN32 #ifdef _WIN32
m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url)); m_tray->UpdateWebUrl(StringToWide(m_app_state.web_url));
#else #else
m_tray->UpdateWebUrl(m_app_state.web_url); m_tray->UpdateWebUrl(m_app_state.web_url);
#endif #endif
@ -120,7 +123,7 @@ bool Manager::Initialize() {
if (m_app_state.auto_start && strlen(m_app_state.command_input) > 0) { if (m_app_state.auto_start && strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Start(m_app_state.command_input); m_app_state.cli_process.Start(m_app_state.command_input);
} }
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止", m_app_state.cli_process.GetPid());
m_initialized = true; m_initialized = true;
return true; return true;
} }
@ -200,22 +203,41 @@ void Manager::RenderUI() {
ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(display_w, display_h)); ImGui::SetNextWindowSize(ImVec2(display_w, display_h));
if (m_fullscreen) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
} else {
dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode;
}
if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
window_flags |= ImGuiWindowFlags_NoBackground;
if (!m_padding)
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
if (!m_padding)
ImGui::PopStyleVar();
if (m_fullscreen)
ImGui::PopStyleVar(2); // Submit the DockSpace
ImGui::Begin("CLI程序管理工具", &m_app_state.show_main_window, ImGui::Begin("CLI程序管理工具", &m_app_state.show_main_window,
ImGuiWindowFlags_NoTitleBar | window_flags);
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_MenuBar);
RenderMenuBar(); RenderMenuBar();
// 创建主要的Docking空间 // 创建主要的Docking空间
ImGuiID m_dockspace_id = ImGui::GetID("MainDockSpace"); ImGuiID m_dockspace_id = ImGui::GetID("MainDockSpace");
ImGui::DockSpace( m_dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); ImGui::DockSpace(m_dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags | ImGuiDockNodeFlags_NoTabBar);
// 设置默认布局(仅在第一次运行时)
SetupDefaultDockingLayout( m_dockspace_id);
// 设置默认布局(仅在第一次运行时)
SetupDefaultDockingLayout(m_dockspace_id);
ImGuiWindowClass window_class;
window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoWindowMenuButton;
ImGui::SetNextWindowClass(&window_class);
RenderMainContent(); RenderMainContent();
ImGui::End(); ImGui::End();
@ -272,12 +294,41 @@ void Manager::RenderMenuBar() {
RenderLayoutMenu(); RenderLayoutMenu();
ImGui::EndMenu(); ImGui::EndMenu();
} }
// 添加主题菜单
if (ImGui::BeginMenu("主题")) { if (ImGui::BeginMenu("主题")) {
if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); } if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); }
if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); } if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); }
if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); } if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("选项(Options)")) {
ImGui::MenuItem("全屏(Fullscreen)", nullptr, &m_fullscreen);
ImGui::MenuItem("填充(Padding)", nullptr, &m_padding);
ImGui::Separator();
if (ImGui::MenuItem("标志:不分割(Flag: NoSplit)", "", (dockspace_flags & ImGuiDockNodeFlags_NoSplit) !=
0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoSplit; }
if (ImGui::MenuItem("标志:不调整大小(Flag: NoResize)", "",
(dockspace_flags & ImGuiDockNodeFlags_NoResize) !=
0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoResize; }
if (ImGui::MenuItem("标志:不停靠在中心节点(Flag: NoDockingInCentralNode)", "",
(dockspace_flags & ImGuiDockNodeFlags_NoDockingInCentralNode) !=
0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingInCentralNode; }
if (ImGui::MenuItem("标志:自动隐藏选项卡栏(Flag: AutoHideTabBar)", "",
(dockspace_flags & ImGuiDockNodeFlags_AutoHideTabBar) !=
0)) { dockspace_flags = ImGuiDockNodeFlags_AutoHideTabBar; }
if (ImGui::MenuItem("标志:中心节点筛选器(Flag: PassthruCentralNode)", "",
(dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0,
m_fullscreen)) { dockspace_flags ^= ImGuiDockNodeFlags_PassthruCentralNode; }
ImGui::Separator();
//不关闭菜单
if (ImGui::MenuItem("关闭(Close)", nullptr, !m_app_state.show_main_window)) {
HideMainWindow();
}
ImGui::EndMenu();
}
ImGui::EndMenuBar(); ImGui::EndMenuBar();
} }
} }
@ -303,17 +354,17 @@ void Manager::RenderLayoutMenu() {
if (ImGui::BeginMenu("预设布局")) { if (ImGui::BeginMenu("预设布局")) {
if (ImGui::MenuItem("经典布局 (左控制右日志)")) { if (ImGui::MenuItem("经典布局 (左控制右日志)")) {
m_apply_preset_layout = true; m_apply_preset_layout = true;
m_pending_preset=LayoutPreset::Classic; m_pending_preset = LayoutPreset::Classic;
} }
if (ImGui::MenuItem("开发布局 (上控制下日志)")) { if (ImGui::MenuItem("开发布局 (上控制下日志)")) {
m_apply_preset_layout = true; m_apply_preset_layout = true;
m_pending_preset=LayoutPreset::Development; m_pending_preset = LayoutPreset::Development;
} }
if (ImGui::MenuItem("监控布局 (日志为主)")) { if (ImGui::MenuItem("监控布局 (日志为主)")) {
m_apply_preset_layout = true; m_apply_preset_layout = true;
m_pending_preset=LayoutPreset::Monitoring; m_pending_preset = LayoutPreset::Monitoring;
} }
ImGui::EndMenu(); ImGui::EndMenu();
@ -321,18 +372,18 @@ void Manager::RenderLayoutMenu() {
} }
void Manager::ApplyPresetLayout(LayoutPreset preset) { void Manager::ApplyPresetLayout(LayoutPreset preset) {
ImGuiID m_dockspace_id = ImGui::GetID("MainDockSpace"); ImGuiID m_dockspace_id = ImGui::GetID("MainDockSpace");
// 清除现有布局 // 清除现有布局
ImGui::DockBuilderRemoveNode( m_dockspace_id); ImGui::DockBuilderRemoveNode(m_dockspace_id);
ImGui::DockBuilderAddNode( m_dockspace_id, ImGuiDockNodeFlags_DockSpace); ImGui::DockBuilderAddNode(m_dockspace_id, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize( m_dockspace_id, ImGui::GetMainViewport()->Size); ImGui::DockBuilderSetNodeSize(m_dockspace_id, ImGui::GetMainViewport()->Size);
ImGuiID dock_1, dock_2, dock_3; ImGuiID dock_1, dock_2, dock_3;
switch (preset) { switch (preset) {
case LayoutPreset::Classic: { case LayoutPreset::Classic: {
// 左右分割 (左30%右70%) // 左右分割 (左30%右70%)
ImGui::DockBuilderSplitNode( m_dockspace_id, ImGuiDir_Left, 0.3f, &dock_1, &dock_2); ImGui::DockBuilderSplitNode(m_dockspace_id, ImGuiDir_Left, 0.3f, &dock_1, &dock_2);
// 右侧上下分割 (上30%下70%) // 右侧上下分割 (上30%下70%)
ImGui::DockBuilderSplitNode(dock_2, ImGuiDir_Up, 0.3f, &dock_2, &dock_3); ImGui::DockBuilderSplitNode(dock_2, ImGuiDir_Up, 0.3f, &dock_2, &dock_3);
@ -344,7 +395,7 @@ void Manager::ApplyPresetLayout(LayoutPreset preset) {
case LayoutPreset::Development: { case LayoutPreset::Development: {
// 上下分割 (上40%下60%) // 上下分割 (上40%下60%)
ImGui::DockBuilderSplitNode( m_dockspace_id, ImGuiDir_Up, 0.4f, &dock_1, &dock_2); ImGui::DockBuilderSplitNode(m_dockspace_id, ImGuiDir_Up, 0.4f, &dock_1, &dock_2);
// 上侧左右分割 (左60%右40%) // 上侧左右分割 (左60%右40%)
ImGui::DockBuilderSplitNode(dock_1, ImGuiDir_Left, 0.6f, &dock_1, &dock_3); ImGui::DockBuilderSplitNode(dock_1, ImGuiDir_Left, 0.6f, &dock_1, &dock_3);
@ -356,7 +407,7 @@ void Manager::ApplyPresetLayout(LayoutPreset preset) {
case LayoutPreset::Monitoring: { case LayoutPreset::Monitoring: {
// 上下分割 (上20%下80%) // 上下分割 (上20%下80%)
ImGui::DockBuilderSplitNode( m_dockspace_id, ImGuiDir_Up, 0.2f, &dock_1, &dock_2); ImGui::DockBuilderSplitNode(m_dockspace_id, ImGuiDir_Up, 0.2f, &dock_1, &dock_2);
// 上侧左右分割 (左70%右30%) // 上侧左右分割 (左70%右30%)
ImGui::DockBuilderSplitNode(dock_1, ImGuiDir_Left, 0.7f, &dock_1, &dock_3); ImGui::DockBuilderSplitNode(dock_1, ImGuiDir_Left, 0.7f, &dock_1, &dock_3);
@ -368,7 +419,7 @@ void Manager::ApplyPresetLayout(LayoutPreset preset) {
} }
// 完成布局构建并强制应用 // 完成布局构建并强制应用
ImGui::DockBuilderFinish( m_dockspace_id); ImGui::DockBuilderFinish(m_dockspace_id);
} }
void Manager::SaveCurrentLayout() { void Manager::SaveCurrentLayout() {
@ -377,7 +428,7 @@ void Manager::SaveCurrentLayout() {
// 获取当前ImGui布局数据 // 获取当前ImGui布局数据
size_t data_size = 0; size_t data_size = 0;
const char* data = ImGui::SaveIniSettingsToMemory(&data_size); const char *data = ImGui::SaveIniSettingsToMemory(&data_size);
if (data && data_size > 0) { if (data && data_size > 0) {
std::ofstream file(layout_file, std::ios::binary); std::ofstream file(layout_file, std::ios::binary);
@ -417,13 +468,14 @@ void Manager::LoadSavedLayout() {
void Manager::RenderStatusMessages() { void Manager::RenderStatusMessages() {
// 渲染保存成功提示 // 渲染保存成功提示
if (m_show_save_success) { if (m_show_save_success) {
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always, ImVec2(0.5f, 0.0f)); ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.8f); ImGui::SetNextWindowBgAlpha(0.8f);
if (ImGui::Begin("SaveSuccess", nullptr, if (ImGui::Begin("SaveSuccess", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "布局已保存"); ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "布局已保存");
} }
ImGui::End(); ImGui::End();
@ -436,13 +488,14 @@ void Manager::RenderStatusMessages() {
// 渲染加载成功提示 // 渲染加载成功提示
if (m_show_load_success) { if (m_show_load_success) {
ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always, ImVec2(0.5f, 0.0f)); ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x * 0.5f, 50), ImGuiCond_Always,
ImVec2(0.5f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.8f); ImGui::SetNextWindowBgAlpha(0.8f);
if (ImGui::Begin("LoadSuccess", nullptr, if (ImGui::Begin("LoadSuccess", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "布局已加载"); ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "布局已加载");
} }
ImGui::End(); ImGui::End();
@ -460,8 +513,8 @@ void Manager::RenderMainContent() {
// 控制面板窗口 // 控制面板窗口
if (ImGui::Begin("控制面板", nullptr, ImGuiWindowFlags_NoCollapse)) { if (ImGui::Begin("控制面板", nullptr, ImGuiWindowFlags_NoCollapse)) {
float buttonWidth = 80.0f * m_dpi_scale; float buttonWidth = 40.0f * m_dpi_scale;
float buttonHeight = 40.0f * m_dpi_scale; float buttonHeight = 25.0f * m_dpi_scale;
float inputWidth = ImGui::GetContentRegionAvail().x * 0.8f; float inputWidth = ImGui::GetContentRegionAvail().x * 0.8f;
RenderControlPanel(buttonWidth, buttonHeight, inputWidth); RenderControlPanel(buttonWidth, buttonHeight, inputWidth);
} }
@ -475,23 +528,13 @@ void Manager::RenderMainContent() {
// 命令发送窗口 // 命令发送窗口
if (ImGui::Begin("命令发送", nullptr, ImGuiWindowFlags_NoCollapse)) { if (ImGui::Begin("命令发送", nullptr, ImGuiWindowFlags_NoCollapse)) {
float buttonWidth = 80.0f * m_dpi_scale; float buttonWidth = 40.0f * m_dpi_scale;
float inputWidth = ImGui::GetContentRegionAvail().x * 0.8f; float inputWidth = ImGui::GetContentRegionAvail().x * 0.8f;
RenderCommandPanel(buttonWidth, inputWidth); RenderCommandPanel(buttonWidth, inputWidth);
} }
ImGui::End(); ImGui::End();
} }
// void Manager::RenderMenuBar() {
// if (ImGui::BeginMenuBar()) {
// if (ImGui::BeginMenu("设置")) {
// RenderSettingsMenu();
// ImGui::EndMenu();
// }
// ImGui::EndMenuBar();
// }
// }
void Manager::RenderSettingsMenu() { void Manager::RenderSettingsMenu() {
if (ImGui::MenuItem("开机自启动", nullptr, m_app_state.auto_start)) { if (ImGui::MenuItem("开机自启动", nullptr, m_app_state.auto_start)) {
m_app_state.auto_start = !m_app_state.auto_start; m_app_state.auto_start = !m_app_state.auto_start;
@ -602,7 +645,7 @@ void Manager::RenderEnvironmentVariablesSettings() {
if (ImGui::BeginChild("EnvVarsList", ImVec2(0, 150), true)) { if (ImGui::BeginChild("EnvVarsList", ImVec2(0, 150), true)) {
std::vector<std::string> keysToRemove; std::vector<std::string> keysToRemove;
for (const auto& pair : m_app_state.environment_variables) { for (const auto &pair: m_app_state.environment_variables) {
ImGui::PushID(pair.first.c_str()); ImGui::PushID(pair.first.c_str());
// 显示环境变量 // 显示环境变量
@ -618,7 +661,7 @@ void Manager::RenderEnvironmentVariablesSettings() {
} }
// 删除标记的环境变量 // 删除标记的环境变量
for (const auto& key : keysToRemove) { for (const auto &key: keysToRemove) {
m_app_state.environment_variables.erase(key); m_app_state.environment_variables.erase(key);
m_app_state.cli_process.RemoveEnvironmentVariable(key); m_app_state.cli_process.RemoveEnvironmentVariable(key);
m_app_state.settings_dirty = true; m_app_state.settings_dirty = true;
@ -637,7 +680,8 @@ void Manager::RenderEnvironmentVariablesSettings() {
} }
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextWrapped("说明启用后CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并同名变量会被覆盖。"); ImGui::TextWrapped(
"说明启用后CLI程序将使用这些自定义环境变量。这些变量会与系统环境变量合并同名变量会被覆盖。");
ImGui::Unindent(); ImGui::Unindent();
} else { } else {
@ -658,8 +702,8 @@ void Manager::RenderOutputEncodingSettings() {
int currentEncodingIndex = static_cast<int>(m_app_state.output_encoding); int currentEncodingIndex = static_cast<int>(m_app_state.output_encoding);
// 创建编码名称数组用于Combo // 创建编码名称数组用于Combo
std::vector<const char*> encodingNames; std::vector<const char *> encodingNames;
for (const auto& encoding : supportedEncodings) { for (const auto &encoding: supportedEncodings) {
encodingNames.push_back(encoding.second.c_str()); encodingNames.push_back(encoding.second.c_str());
} }
@ -674,7 +718,7 @@ void Manager::RenderOutputEncodingSettings() {
// 显示当前编码信息 // 显示当前编码信息
ImGui::SameLine(); ImGui::SameLine();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "当前: %s", ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "当前: %s",
CLIProcess::GetEncodingName(m_app_state.output_encoding).c_str()); CLIProcess::GetEncodingName(m_app_state.output_encoding).c_str());
// 编码说明 // 编码说明
ImGui::Spacing(); ImGui::Spacing();
@ -686,179 +730,6 @@ void Manager::RenderOutputEncodingSettings() {
ImGui::BulletText("Shift-JIS适用于日文程序"); ImGui::BulletText("Shift-JIS适用于日文程序");
} }
// void Manager::RenderMainContent() {
// float buttonWidth = 80.0f * m_dpi_scale;
// float buttonHeight = 40.0f * m_dpi_scale;
// float inputWidth = ImGui::GetContentRegionAvail().x * 0.5f; // 调整输入框宽度为50%
//
// // 启动命令输入区域
// ImGui::BeginGroup();
// ImGui::Text("启动命令");
//
// // 命令输入框和历史记录按钮
// ImGui::SetNextItemWidth(inputWidth);
// if (ImGui::InputText("##启动命令", m_app_state.command_input, IM_ARRAYSIZE(m_app_state.command_input))) {
// m_app_state.settings_dirty = true;
// }
//
// ImGui::SameLine();
// if (ImGui::Button("历史记录", ImVec2(100.0f * m_dpi_scale, 0))) {
// show_command_history_ = !show_command_history_;
// }
//
// // 显示命令历史记录下拉列表
// if (show_command_history_) {
// const auto& history = m_app_state.GetCommandHistory();
//
// if (!history.empty()) {
// ImGui::Indent();
// ImGui::Text("选择历史命令 (%d个):", static_cast<int>(history.size()));
//
// if (ImGui::BeginChild("CommandHistory", ImVec2(0, 120), true)) {
// for (int i = 0; i < static_cast<int>(history.size()); ++i) {
// ImGui::PushID(i);
//
// // 选择按钮
// if (ImGui::Button("选择", ImVec2(50.0f * m_dpi_scale, 0))) {
// strncpy_s(m_app_state.command_input, history[i].c_str(), sizeof(m_app_state.command_input) - 1);
// show_command_history_ = false;
// ImGui::PopID();
// break;
// }
// ImGui::SameLine();
//
// // 显示命令内容(限制显示长度)
// std::string displayCommand = history[i];
// if (displayCommand.length() > 60) {
// displayCommand = displayCommand.substr(0, 57) + "...";
// }
// ImGui::TextUnformatted(displayCommand.c_str());
//
// // 鼠标悬停时显示完整命令
// if (ImGui::IsItemHovered()) {
// ImGui::SetTooltip("%s", history[i].c_str());
// }
//
// ImGui::SameLine();
//
// // 删除按钮
// if (ImGui::SmallButton("删除")) {
// m_app_state.RemoveCommandFromHistory(i);
// ImGui::PopID();
// continue;
// }
//
// ImGui::PopID();
// }
// }
// ImGui::EndChild();
//
// // 操作按钮
// if (ImGui::Button("清空所有历史记录")) {
// m_app_state.ClearCommandHistory();
// }
// ImGui::SameLine();
//
// ImGui::Unindent();
// } else {
// ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "暂无启动命令历史");
// }
// }
// ImGui::EndGroup();
//
// ImGui::Spacing();
//
//
// ImGui::BeginGroup();
// if (ImGui::Button("启动", ImVec2(buttonWidth, buttonHeight))) {
// if (strlen(m_app_state.command_input) > 0) {
// m_app_state.cli_process.Start(m_app_state.command_input);
// m_app_state.AddCommandToHistory(m_app_state.command_input);
// }
// }
// ImGui::SameLine();
// if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
// m_app_state.cli_process.Stop();
// }
// ImGui::SameLine();
// if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) {
// if (strlen(m_app_state.command_input) > 0) {
// m_app_state.cli_process.Restart(m_app_state.command_input);
// m_app_state.AddCommandToHistory(m_app_state.command_input);
// }
// }
// ImGui::SameLine();
// if (ImGui::Button("清理日志", ImVec2(100.0f * m_dpi_scale, buttonHeight))) {
// m_app_state.cli_process.ClearLogs();
// }
// ImGui::EndGroup();
//
// ImGui::Text("状态: %s", m_app_state.cli_process.IsRunning() ? "运行中" : "已停止");
//
// ImGui::Separator();
// ImGui::Text("发送命令到CLI程序");
//
// // 命令发送
// ImGui::BeginGroup();
// ImGui::SetNextItemWidth(inputWidth);
// bool sendCommandPressed = ImGui::InputText("##命令输入", m_app_state.send_command, IM_ARRAYSIZE(m_app_state.send_command),
// ImGuiInputTextFlags_EnterReturnsTrue);
// ImGui::SameLine();
// if (ImGui::Button("发送", ImVec2(buttonWidth, 0)) || sendCommandPressed) {
// if (m_app_state.cli_process.IsRunning() && strlen(m_app_state.send_command) > 0) {
// m_app_state.cli_process.SendCommand(m_app_state.send_command);
// memset(m_app_state.send_command, 0, sizeof(m_app_state.send_command));
// }
// }
// ImGui::EndGroup();
//
// ImGui::Separator();
//
// // 日志控制
// ImGui::BeginGroup();
// ImGui::Text("程序日志");
//
// float logControlButtonWidth = 100.0f * m_dpi_scale;
// float checkboxWidth = 80.0f * m_dpi_scale;
// float statusTextWidth = 150.0f * m_dpi_scale;
//
// // 计算所有右侧控件的总宽度
// float totalRightControlsWidth = logControlButtonWidth +
// ImGui::GetStyle().ItemSpacing.x +
// checkboxWidth +
// ImGui::GetStyle().ItemSpacing.x +
// statusTextWidth;
//
// ImGui::SameLine();
// ImGui::SameLine(ImGui::GetContentRegionAvail().x - totalRightControlsWidth);
// if (ImGui::Button("复制日志", ImVec2(logControlButtonWidth, 0))) {
// m_app_state.cli_process.CopyLogsToClipboard();
// }
//
// ImGui::SameLine();
// ImGui::Checkbox("自动滚动", &m_app_state.auto_scroll_logs);
//
// ImGui::SameLine();
// ImGui::Text("行数: %d/%d",
// static_cast<int>(m_app_state.cli_process.GetLogs().size()),
// m_app_state.max_log_lines);
// ImGui::EndGroup();
//
// float logHeight = ImGui::GetContentRegionAvail().y - ImGui::GetStyle().ItemSpacing.y;
// ImGui::BeginChild("Logs", ImVec2(0, logHeight), true, ImGuiWindowFlags_HorizontalScrollbar);
//
// const auto& logs = m_app_state.cli_process.GetLogs();
// for (const auto& log : logs) {
// ImGui::TextUnformatted(log.c_str());
// }
//
// if (m_app_state.auto_scroll_logs && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
// ImGui::SetScrollHereY(1.0f);
// }
//
// ImGui::EndChild();
// }
void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth) { void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float inputWidth) {
// 启动命令输入区域 // 启动命令输入区域
ImGui::SeparatorText("启动命令"); ImGui::SeparatorText("启动命令");
@ -870,7 +741,7 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("历史记录", ImVec2(100.0f * m_dpi_scale, 0))) { if (ImGui::Button("历史记录", ImVec2(80.0f * m_dpi_scale, 0))) {
show_command_history_ = !show_command_history_; show_command_history_ = !show_command_history_;
} }
@ -887,17 +758,23 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
if (strlen(m_app_state.command_input) > 0) { if (strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Start(m_app_state.command_input); m_app_state.cli_process.Start(m_app_state.command_input);
m_app_state.AddCommandToHistory(m_app_state.command_input); m_app_state.AddCommandToHistory(m_app_state.command_input);
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止",
m_app_state.cli_process.GetPid());
} }
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) { if (ImGui::Button("停止", ImVec2(buttonWidth, buttonHeight))) {
m_app_state.cli_process.Stop(); m_app_state.cli_process.Stop();
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止",
m_app_state.cli_process.GetPid());
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) { if (ImGui::Button("重启", ImVec2(buttonWidth, buttonHeight))) {
if (strlen(m_app_state.command_input) > 0) { if (strlen(m_app_state.command_input) > 0) {
m_app_state.cli_process.Restart(m_app_state.command_input); m_app_state.cli_process.Restart(m_app_state.command_input);
m_app_state.AddCommandToHistory(m_app_state.command_input); m_app_state.AddCommandToHistory(m_app_state.command_input);
m_tray->UpdateStatus(m_app_state.cli_process.IsRunning() ? L"运行中" : L"已停止",
m_app_state.cli_process.GetPid());
} }
} }
@ -906,10 +783,10 @@ void Manager::RenderControlPanel(float buttonWidth, float buttonHeight, float in
// 状态显示 // 状态显示
ImGui::SeparatorText("运行状态"); ImGui::SeparatorText("运行状态");
ImVec4 statusColor = m_app_state.cli_process.IsRunning() ? ImVec4 statusColor = m_app_state.cli_process.IsRunning() ?
ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(0.0f, 1.0f, 0.0f, 1.0f) :
ImVec4(1.0f, 0.0f, 0.0f, 1.0f); ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
ImGui::TextColored(statusColor, "状态: %s", ImGui::TextColored(statusColor, "状态: %s",
m_app_state.cli_process.IsRunning() ? "运行中" : "已停止"); m_app_state.cli_process.IsRunning() ? "运行中" : "已停止");
} }
void Manager::RenderCommandPanel(float buttonWidth, float inputWidth) { void Manager::RenderCommandPanel(float buttonWidth, float inputWidth) {
@ -918,8 +795,8 @@ void Manager::RenderCommandPanel(float buttonWidth, float inputWidth) {
// 命令发送 // 命令发送
ImGui::SetNextItemWidth(inputWidth); ImGui::SetNextItemWidth(inputWidth);
bool sendCommandPressed = ImGui::InputText("##命令输入", m_app_state.send_command, bool sendCommandPressed = ImGui::InputText("##命令输入", m_app_state.send_command,
IM_ARRAYSIZE(m_app_state.send_command), IM_ARRAYSIZE(m_app_state.send_command),
ImGuiInputTextFlags_EnterReturnsTrue); ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("发送", ImVec2(buttonWidth, 0)) || sendCommandPressed) { if (ImGui::Button("发送", ImVec2(buttonWidth, 0)) || sendCommandPressed) {
if (m_app_state.cli_process.IsRunning() && strlen(m_app_state.send_command) > 0) { if (m_app_state.cli_process.IsRunning() && strlen(m_app_state.send_command) > 0) {
@ -937,8 +814,8 @@ void Manager::RenderCommandPanel(float buttonWidth, float inputWidth) {
void Manager::RenderLogPanel() { void Manager::RenderLogPanel() {
// 日志控制工具栏 // 日志控制工具栏
if (ImGui::BeginTable("LogControls", 3, ImGuiTableFlags_SizingStretchProp)) { if (ImGui::BeginTable("LogControls", 3, ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 120.0f * m_dpi_scale); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 85.0f * m_dpi_scale);
ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthFixed, 120.0f * m_dpi_scale); ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthFixed, 100.0f * m_dpi_scale);
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow(); ImGui::TableNextRow();
@ -969,7 +846,7 @@ void Manager::RenderLogPanel() {
// 日志内容区域 // 日志内容区域
if (ImGui::BeginChild("LogContent", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar)) { if (ImGui::BeginChild("LogContent", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar)) {
const auto& logs = m_app_state.cli_process.GetLogs(); const auto &logs = m_app_state.cli_process.GetLogs();
// 使用ImGuiListClipper优化大量日志的渲染性能 // 使用ImGuiListClipper优化大量日志的渲染性能
ImGuiListClipper clipper; ImGuiListClipper clipper;
@ -977,7 +854,7 @@ void Manager::RenderLogPanel() {
while (clipper.Step()) { while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
const std::string& log = logs[i]; const std::string &log = logs[i];
if (m_app_state.enable_colored_logs) { if (m_app_state.enable_colored_logs) {
RenderColoredLogLine(log); RenderColoredLogLine(log);
@ -997,207 +874,8 @@ void Manager::RenderLogPanel() {
ImGui::EndChild(); ImGui::EndChild();
} }
ImVec4 Manager::GetLogLevelColor(const std::string& 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 ImVec4(1.0f, 0.4f, 0.4f, 1.0f); // 红色
} 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 ImVec4(1.0f, 1.0f, 0.4f, 1.0f); // 黄色
} 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); // 绿色
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
void Manager::RenderColoredLogLine(const std::string& log) {
auto segments = ParseAnsiColorCodes(log);
if (segments.empty()) {
// 如果没有ANSI代码使用简单的日志级别颜色
ImVec4 textColor = GetLogLevelColor(log);
ImGui::TextColored(textColor, "%s", log.c_str());
return;
}
// 渲染带颜色的文本段
bool first = true;
for (const auto& segment : segments) {
if (!first) {
ImGui::SameLine(0, 0); // 在同一行继续显示
}
first = false;
if (!segment.text.empty()) {
ImGui::TextColored(segment.color, "%s", segment.text.c_str());
}
}
}
std::vector<Manager::ColoredTextSegment> Manager::ParseAnsiColorCodes(const std::string& text) {
std::vector<ColoredTextSegment> segments;
if (text.empty()) {
return segments;
}
size_t pos = 0;
ImVec4 currentColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
bool isBold = false;
while (pos < text.length()) {
size_t escapePos = text.find('\033', pos);
if (escapePos == std::string::npos) {
// 没有更多转义序列,添加剩余文本
if (pos < text.length()) {
segments.push_back({text.substr(pos), currentColor});
}
break;
}
// 添加转义序列之前的文本
if (escapePos > pos) {
segments.push_back({text.substr(pos, escapePos - pos), currentColor});
}
// 解析ANSI转义序列
size_t codeStart = escapePos + 1;
if (codeStart < text.length() && text[codeStart] == '[') {
size_t codeEnd = text.find('m', codeStart);
if (codeEnd != std::string::npos) {
std::string colorCode = text.substr(codeStart + 1, codeEnd - codeStart - 1);
auto newColor = ParseAnsiColorCode(colorCode, currentColor, isBold);
currentColor = newColor.first;
isBold = newColor.second;
pos = codeEnd + 1;
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
}
return segments;
}
std::pair<ImVec4, bool> Manager::ParseAnsiColorCode(const std::string& code, const ImVec4& currentColor, bool currentBold) {
ImVec4 newColor = currentColor;
bool newBold = currentBold;
// 分割多个颜色代码(用分号分隔)
std::vector<int> codes;
std::stringstream ss(code);
std::string item;
while (std::getline(ss, item, ';')) {
if (!item.empty()) {
try {
codes.push_back(std::stoi(item));
} catch (const std::exception&) {
// 忽略无效的代码
}
}
}
// 如果没有代码默认为0重置
if (codes.empty()) {
codes.push_back(0);
}
for (int colorCode : codes) {
switch (colorCode) {
case 0: // 重置
newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
newBold = false;
break;
case 1: // 粗体/亮色
newBold = true;
break;
case 22: // 正常强度
newBold = false;
break;
// 前景色 (30-37)
case 30: newColor = GetAnsiColor(0, newBold); break; // 黑色
case 31: newColor = GetAnsiColor(1, newBold); break; // 红色
case 32: newColor = GetAnsiColor(2, newBold); break; // 绿色
case 33: newColor = GetAnsiColor(3, newBold); break; // 黄色
case 34: newColor = GetAnsiColor(4, newBold); break; // 蓝色
case 35: newColor = GetAnsiColor(5, newBold); break; // 洋红
case 36: newColor = GetAnsiColor(6, newBold); break; // 青色
case 37: newColor = GetAnsiColor(7, newBold); break; // 白色
case 39: newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); break; // 默认前景色
// 亮色前景色 (90-97)
case 90: newColor = GetAnsiColor(8, false); break; // 亮黑色(灰色)
case 91: newColor = GetAnsiColor(9, false); break; // 亮红色
case 92: newColor = GetAnsiColor(10, false); break; // 亮绿色
case 93: newColor = GetAnsiColor(11, false); break; // 亮黄色
case 94: newColor = GetAnsiColor(12, false); break; // 亮蓝色
case 95: newColor = GetAnsiColor(13, false); break; // 亮洋红
case 96: newColor = GetAnsiColor(14, false); break; // 亮青色
case 97: newColor = GetAnsiColor(15, false); break; // 亮白色
// 背景色暂时忽略 (40-47, 100-107)
default:
// 处理256色和RGB色彩38;5;n 和 38;2;r;g;b
// 这里可以根据需要扩展
break;
}
}
return {newColor, newBold};
}
ImVec4 Manager::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: 白色
// 亮色 (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) {
ImVec4 color = ansiColors[colorIndex];
// 如果是粗体且是标准颜色0-7增加亮度
if (bright && colorIndex < 8) {
color.x = std::min(1.0f, color.x + 0.3f);
color.y = std::min(1.0f, color.y + 0.3f);
color.z = std::min(1.0f, color.z + 0.3f);
}
return color;
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
void Manager::RenderCommandHistory() { void Manager::RenderCommandHistory() {
const auto& history = m_app_state.GetCommandHistory(); const auto &history = m_app_state.GetCommandHistory();
if (!history.empty()) { if (!history.empty()) {
ImGui::Indent(); ImGui::Indent();
@ -1378,6 +1056,7 @@ bool Manager::InitializeTray() {
} }
#ifdef _WIN32 #ifdef _WIN32
HWND Manager::CreateHiddenWindow() { HWND Manager::CreateHiddenWindow() {
WNDCLASSEX wc = {0}; WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX); wc.cbSize = sizeof(WNDCLASSEX);
@ -1390,16 +1069,17 @@ HWND Manager::CreateHiddenWindow() {
} }
return CreateWindowEx( return CreateWindowEx(
0, 0,
wc.lpszClassName, wc.lpszClassName,
L"CLI Manager Tray Window", L"CLI Manager Tray Window",
0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
NULL, NULL, NULL, NULL,
wc.hInstance, wc.hInstance,
NULL NULL
); );
} }
#endif #endif
void Manager::HandleMessages() { void Manager::HandleMessages() {
@ -1446,8 +1126,8 @@ bool Manager::ShouldExit() const {
return m_should_exit; return m_should_exit;
} }
void Manager::ContentScaleCallback(GLFWwindow* window, float xscale, float yscale) { void Manager::ContentScaleCallback(GLFWwindow *window, float xscale, float yscale) {
if (auto* manager = static_cast<Manager*>(glfwGetWindowUserPointer(window))) { if (auto *manager = static_cast<Manager *>(glfwGetWindowUserPointer(window))) {
// 强制触发DPI更新 // 强制触发DPI更新
manager->m_last_dpi_scale = 0.0f; manager->m_last_dpi_scale = 0.0f;
} }
@ -1569,6 +1249,7 @@ void Manager::CleanupDirectX11() {
} }
} }
#else #else
bool Manager::InitializeGLFW() { bool Manager::InitializeGLFW() {
glfwSetErrorCallback(GlfwErrorCallback); glfwSetErrorCallback(GlfwErrorCallback);
if (!glfwInit()) if (!glfwInit())
@ -1597,14 +1278,16 @@ bool Manager::InitializeGLFW() {
#endif #endif
int screenWidth, screenHeight; int screenWidth, screenHeight;
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor(); GLFWmonitor *primaryMonitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor); const GLFWvidmode *mode = glfwGetVideoMode(primaryMonitor);
screenWidth = mode->width; screenWidth = mode->width;
screenHeight = mode->height; screenHeight = mode->height;
int windowWidth = static_cast<int>(screenWidth * 0.8); int windowWidth = static_cast<int>(screenWidth * 0.8);
int windowHeight = static_cast<int>(screenHeight * 0.8); int windowHeight = static_cast<int>(screenHeight * 0.8);
// glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);//窗口隐藏
m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr); m_window = glfwCreateWindow(windowWidth, windowHeight, "CLI程序管理工具", nullptr, nullptr);
if (!m_window) if (!m_window)
return false; return false;
@ -1628,9 +1311,10 @@ void Manager::CleanupGLFW() {
glfwTerminate(); glfwTerminate();
} }
void Manager::GlfwErrorCallback(int error, const char* description) { void Manager::GlfwErrorCallback(int error, const char *description) {
fprintf(stderr, "GLFW Error %d: %s\n", error, description); fprintf(stderr, "GLFW Error %d: %s\n", error, description);
} }
#endif #endif
void Manager::CleanupTray() { void Manager::CleanupTray() {
@ -1694,13 +1378,13 @@ void Manager::UpdateDPIScale() {
ReloadFonts(); ReloadFonts();
// 缩放ImGui样式 // 缩放ImGui样式
ImGuiStyle& style = ImGui::GetStyle(); ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(m_dpi_scale); style.ScaleAllSizes(m_dpi_scale);
} }
} }
void Manager::ReloadFonts() const { void Manager::ReloadFonts() const {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
// 清除现有字体 // 清除现有字体
io.Fonts->Clear(); io.Fonts->Clear();
@ -1716,7 +1400,8 @@ void Manager::ReloadFonts() const {
// 尝试加载系统字体,失败则使用默认字体 // 尝试加载系统字体,失败则使用默认字体
#ifdef _WIN32 #ifdef _WIN32
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", font_size, &font_config, io.Fonts->GetGlyphRangesChineseFull()); io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", font_size, &font_config,
io.Fonts->GetGlyphRangesChineseFull());
#elif __APPLE__ #elif __APPLE__
io.Fonts->AddFontFromFileTTF("/System/Library/Fonts/PingFang.ttc", font_size, &font_config, io.Fonts->GetGlyphRangesChineseFull()); io.Fonts->AddFontFromFileTTF("/System/Library/Fonts/PingFang.ttc", font_size, &font_config, io.Fonts->GetGlyphRangesChineseFull());
#else #else
@ -1794,7 +1479,6 @@ void Manager::ReloadFonts() const {
} }
#ifdef __APPLE__ #ifdef __APPLE__
// macOS 特定的辅助函数声明 // macOS 特定的辅助函数声明
extern "C" { extern "C" {

View File

@ -85,7 +85,11 @@ void TrayIcon::CreateMenu() {
m_menu = CreatePopupMenu(); m_menu = CreatePopupMenu();
AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口"); AppendMenu(m_menu, MF_STRING, 1001, L"显示主窗口");
AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr); AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
std::wstring statusText = L"状态:" + m_status;
AppendMenu(m_menu, MF_INSERT, 0, statusText.c_str());
std::wstring pidText = L"状态:" + m_pid;
AppendMenu(m_menu, MF_INSERT, 0, pidText.c_str());
AppendMenu(m_menu, MF_SEPARATOR, 0, nullptr);
// 添加Web地址菜单项如果有设置 // 添加Web地址菜单项如果有设置
if (!m_web_url.empty() && m_web_url != L"") { if (!m_web_url.empty() && m_web_url != L"") {
std::wstring webText = L"打开Web页面: " + m_web_url; std::wstring webText = L"打开Web页面: " + m_web_url;
@ -166,6 +170,15 @@ LRESULT CALLBACK TrayIcon::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
} }
return 0; return 0;
} }
void TrayIcon::UpdateStatus(const std::wstring &status, const std::wstring &pid) {
m_status = status;
m_pid = pid;
// 重新创建菜单以更新Status显示
DestroyMenu();
CreateMenu();
}
#else #else
// macOS 特定实现 // macOS 特定实现
void TrayIcon::ShowMacTrayIcon() { void TrayIcon::ShowMacTrayIcon() {
@ -223,4 +236,4 @@ extern "C" {
void DestroyMacMenuImpl(void* app_delegate); void DestroyMacMenuImpl(void* app_delegate);
void OpenWebPageMac(const char* url); void OpenWebPageMac(const char* url);
} }
#endif #endif

View File

@ -4,6 +4,8 @@
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <windows.h> #include <windows.h>
#include <sstream>
std::wstring StringToWide(const std::string& str) { std::wstring StringToWide(const std::string& str) {
if (str.empty()) return L""; if (str.empty()) return L"";
int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
@ -52,4 +54,239 @@ bool IsAutoStartEnabled() {
RegCloseKey(hKey); RegCloseKey(hKey);
return exists; return exists;
} }
ImVec4 GetLogLevelColor(const std::string &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 ImVec4(1.0f, 0.4f, 0.4f, 1.0f); // 红色
} 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 ImVec4(1.0f, 1.0f, 0.4f, 1.0f); // 黄色
} 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); // 绿色
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}
void RenderColoredLogLine(const std::string &log) {
auto segments = ParseAnsiColorCodes(log);
if (segments.empty()) {
// 如果没有ANSI代码使用简单的日志级别颜色
ImVec4 textColor = GetLogLevelColor(log);
ImGui::TextColored(textColor, "%s", log.c_str());
return;
}
// 渲染带颜色的文本段
bool first = true;
for (const auto &segment: segments) {
if (!first) {
ImGui::SameLine(0, 0); // 在同一行继续显示
}
first = false;
if (!segment.text.empty()) {
ImGui::TextColored(segment.color, "%s", segment.text.c_str());
}
}
}
std::vector<ColoredTextSegment> ParseAnsiColorCodes(const std::string &text) {
std::vector<ColoredTextSegment> segments;
if (text.empty()) {
return segments;
}
size_t pos = 0;
ImVec4 currentColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
bool isBold = false;
while (pos < text.length()) {
size_t escapePos = text.find('\033', pos);
if (escapePos == std::string::npos) {
// 没有更多转义序列,添加剩余文本
if (pos < text.length()) {
segments.push_back({text.substr(pos), currentColor});
}
break;
}
// 添加转义序列之前的文本
if (escapePos > pos) {
segments.push_back({text.substr(pos, escapePos - pos), currentColor});
}
// 解析ANSI转义序列
size_t codeStart = escapePos + 1;
if (codeStart < text.length() && text[codeStart] == '[') {
size_t codeEnd = text.find('m', codeStart);
if (codeEnd != std::string::npos) {
std::string colorCode = text.substr(codeStart + 1, codeEnd - codeStart - 1);
auto newColor = ParseAnsiColorCode(colorCode, currentColor, isBold);
currentColor = newColor.first;
isBold = newColor.second;
pos = codeEnd + 1;
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
} else {
// 无效的转义序列,跳过
pos = codeStart;
}
}
return segments;
}
std::pair<ImVec4, bool>
ParseAnsiColorCode(const std::string &code, const ImVec4 &currentColor, bool currentBold) {
ImVec4 newColor = currentColor;
bool newBold = currentBold;
// 分割多个颜色代码(用分号分隔)
std::vector<int> codes;
std::stringstream ss(code);
std::string item;
while (std::getline(ss, item, ';')) {
if (!item.empty()) {
try {
codes.push_back(std::stoi(item));
} catch (const std::exception &) {
// 忽略无效的代码
}
}
}
// 如果没有代码默认为0(重置)
if (codes.empty()) {
codes.push_back(0);
}
for (int colorCode: codes) {
switch (colorCode) {
case 0: // 重置
newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
newBold = false;
break;
case 1: // 粗体/亮色
newBold = true;
break;
case 22: // 正常强度
newBold = false;
break;
// 前景色 (30-37)
case 30:
newColor = GetAnsiColor(0, newBold);
break; // 黑色
case 31:
newColor = GetAnsiColor(1, newBold);
break; // 红色
case 32:
newColor = GetAnsiColor(2, newBold);
break; // 绿色
case 33:
newColor = GetAnsiColor(3, newBold);
break; // 黄色
case 34:
newColor = GetAnsiColor(4, newBold);
break; // 蓝色
case 35:
newColor = GetAnsiColor(5, newBold);
break; // 洋红
case 36:
newColor = GetAnsiColor(6, newBold);
break; // 青色
case 37:
newColor = GetAnsiColor(7, newBold);
break; // 白色
case 39:
newColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
break; // 默认前景色
// 亮色前景色 (90-97)
case 90:
newColor = GetAnsiColor(8, false);
break; // 亮黑色(灰色)
case 91:
newColor = GetAnsiColor(9, false);
break; // 亮红色
case 92:
newColor = GetAnsiColor(10, false);
break; // 亮绿色
case 93:
newColor = GetAnsiColor(11, false);
break; // 亮黄色
case 94:
newColor = GetAnsiColor(12, false);
break; // 亮蓝色
case 95:
newColor = GetAnsiColor(13, false);
break; // 亮洋红
case 96:
newColor = GetAnsiColor(14, false);
break; // 亮青色
case 97:
newColor = GetAnsiColor(15, false);
break; // 亮白色
// 背景色暂时忽略 (40-47, 100-107)
default:
// 处理256色和RGB色彩(38;5;n 和 38;2;r;g;b)
// 这里可以根据需要扩展
break;
}
}
return {newColor, newBold};
}
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: 白色
// 亮色 (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) {
ImVec4 color = ansiColors[colorIndex];
// 如果是粗体且是标准颜色(0-7),增加亮度
if (bright && colorIndex < 8) {
color.x = std::min(1.0f, color.x + 0.3f);
color.y = std::min(1.0f, color.y + 0.3f);
color.z = std::min(1.0f, color.z + 0.3f);
}
return color;
}
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // 默认白色
}