From 0f2f2720fcca80639235a17a6c5f140b90873f95 Mon Sep 17 00:00:00 2001 From: LuChiChick <1084116302@qq.com> Date: Mon, 26 Jan 2026 20:57:13 +0800 Subject: [PATCH] Adjusted UI layout && Tested dynamic window expansion effects && Added node editor area (experimental, currently only one simple test node available with no practical functionality) --- Inc/Global_Variables.hpp | 4 + Inc/Workflow-Editor.hpp | 89 ++++++++++++++ Src/Global_Variables.cpp | 5 +- Src/UI_Layout.cpp | 52 ++++++-- Src/Workflow-Editor.cpp | 250 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 Inc/Workflow-Editor.hpp create mode 100644 Src/Workflow-Editor.cpp diff --git a/Inc/Global_Variables.hpp b/Inc/Global_Variables.hpp index 33e7dbc..52bd9c0 100644 --- a/Inc/Global_Variables.hpp +++ b/Inc/Global_Variables.hpp @@ -12,6 +12,7 @@ extern "C" } #include "Type_Descriptions.hpp" +#include "Workflow-Editor.hpp" // 主窗体句柄 extern HWND Main_Window_hWnd; @@ -29,4 +30,7 @@ extern ID3D11RenderTargetView *g_mainRenderTargetView; // D3D11 渲染目标 extern Input_File_Node *input_dlt_file_list_head; extern size_t input_dlt_file_count; +// 节点编辑器 +extern Workflow_Editor_Class Workflow_Editor; + #endif \ No newline at end of file diff --git a/Inc/Workflow-Editor.hpp b/Inc/Workflow-Editor.hpp new file mode 100644 index 0000000..4d09d85 --- /dev/null +++ b/Inc/Workflow-Editor.hpp @@ -0,0 +1,89 @@ +#ifndef __WORKFLOW_NODES_HPP__ +#define __WORKFLOW_NODES_HPP__ + +#include "imgui.h" + +extern "C" +{ +#include "stdint.h" +} + +#include + +// // 并行工作链 +// class Parallel_Working_List +// { +// private: +// // 节点计数 +// size_t node_count; + +// // 内部迭代器 +// class iterator +// { +// }; + +// public: +// iterator begin() { return iterator(arr); } +// iterator end() { return iterator(arr + SIZE); } +// }; + +// 节点类型枚举 +typedef enum +{ + NODE_TYPE_MSG_LINE_INPUT, // 消息输入节点 + NODE_TYPE_CSV_EXPORTER, // CSV输出节点 +} NodeType; + +// 节点基类 +class Node_Class +{ +protected: + int id; // 节点ID + bool initial_state; // 初始状态 + ImVec2 initial_position; // 初始位置 + +public: + // 初始化列表构造 + explicit Node_Class(int id, ImVec2 initial_position = ImVec2(0, 0)) : id(id), initial_state(true), initial_position(initial_position) {} + int getID() { return this->id; } + + /** + * 显示节点并获取状态(子类必须实现) + * @param null + * @return 0 -> normal : 1-> close + */ + virtual bool show_and_get_state(void) = 0; + + virtual ~Node_Class() = default; // 抽象基类必须有虚析构函数 +}; + +// 消息输出节点 +class MsgLine_Input_Node_Class : public Node_Class +{ +public: + explicit MsgLine_Input_Node_Class(int id, ImVec2 initial_position = ImVec2(0, 0)) : Node_Class(id, initial_position) {}; + bool show_and_get_state(void) override; +}; + +// 节点编辑器类 +class Workflow_Editor_Class +{ +protected: + // 节点列表 + std::vector Node_List; + +public: + // 构造函数 + Workflow_Editor_Class(); + + // 析构函数 + ~Workflow_Editor_Class(); + + // 显示工作流节点编辑器 + void Show(void); + + // 新增节点 + void Add_Node(NodeType type, ImVec2 initial_position = ImVec2(0, 0)); +}; + +#endif \ No newline at end of file diff --git a/Src/Global_Variables.cpp b/Src/Global_Variables.cpp index 03c1577..c8064f5 100644 --- a/Src/Global_Variables.cpp +++ b/Src/Global_Variables.cpp @@ -14,4 +14,7 @@ ID3D11RenderTargetView *g_mainRenderTargetView = nullptr; // D3D11 渲染目标 // 文件输入相关 Input_File_Node *input_dlt_file_list_head; -size_t input_dlt_file_count; \ No newline at end of file +size_t input_dlt_file_count; + +// 节点编辑器 +Workflow_Editor_Class Workflow_Editor; \ No newline at end of file diff --git a/Src/UI_Layout.cpp b/Src/UI_Layout.cpp index aa0a375..ac5542c 100644 --- a/Src/UI_Layout.cpp +++ b/Src/UI_Layout.cpp @@ -36,24 +36,62 @@ void UI_Layout() // 设置窗体相关属性,不允许停靠 window_flags |= ImGuiWindowFlags_NoDocking; + // 临时设置一下窗体大小,动态扩张窗体 + { + static size_t Width = MAIN_FRAME_SIZE_WIDTH_MIN; + static size_t Height = MAIN_FRAME_SIZE_HEIGHT_MIN; + if (!(Width >= 1400 && Height >= 700)) + { + SetWindowPos( + Main_Window_hWnd, + NULL, + (GetSystemMetrics(SM_CXSCREEN) - (Width)) / 2, + (GetSystemMetrics(SM_CYSCREEN) - (Height)) / 2, + Width, + Height, + SWP_NOZORDER); + + if (Height < 700) + Height += 5; + if (Width < 1400) + Width += 20; + } + } + ImGui::Begin("Main Panel", NULL, window_flags); // 创建主面板 ImGui::PopStyleVar(2); // 退出绘制风格栈中的设置项 { const ImGuiStyle &style = ImGui::GetStyle(); float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY)); - // 拖拽输入区域 - Widgets::Drag_Input_Area(ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().x / 2 > MAIN_FRAME_SIZE_WIDTH_MIN / 2 ? MAIN_FRAME_SIZE_WIDTH_MIN / 2 : ImGui::GetContentRegionAvail().x / 2)); + // 左半部分 + ImGui::BeginChild("Left Part", ImVec2(MAIN_FRAME_SIZE_WIDTH_MIN, ImGui::GetContentRegionAvail().y)); + { - // 文件列表组件 - Widgets::File_List_Area(ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - (ImGui::GetTextLineHeightWithSpacing() + 20 * main_scale + style.ItemSpacing.y))); + // 拖拽输入区域 + Widgets::Drag_Input_Area(ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().x / 2 > MAIN_FRAME_SIZE_WIDTH_MIN * main_scale / 2 ? MAIN_FRAME_SIZE_WIDTH_MIN * main_scale / 2 : ImGui::GetContentRegionAvail().x / 2)); - // 导出文件 - Widgets::Control_Panel(ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() + 20 * main_scale)); + // 文件列表组件 + Widgets::File_List_Area(ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - (ImGui::GetTextLineHeightWithSpacing() + 20 * main_scale + style.ItemSpacing.y))); + + // 导出文件 + Widgets::Control_Panel(ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() + 20 * main_scale)); + } + ImGui::EndChild(); + + ImGui::SameLine(); + + // 右半部分 + ImGui::BeginChild("Right Part", ImVec2(0, 0)); + { + // 工作流节点编辑器 + Workflow_Editor.Show(); + } + ImGui::EndChild(); } ImGui::End(); - // ImGui::ShowDemoWindow(nullptr); + ImGui::ShowDemoWindow(nullptr); } void Widgets::Drag_Input_Area(const ImVec2 &size) diff --git a/Src/Workflow-Editor.cpp b/Src/Workflow-Editor.cpp new file mode 100644 index 0000000..be91b45 --- /dev/null +++ b/Src/Workflow-Editor.cpp @@ -0,0 +1,250 @@ +#include "Workflow-Editor.hpp" +#include "imgui.h" +#include "imgui_internal.h" +#include "imnodes.h" +#include + +// 构造函数 +Workflow_Editor_Class::Workflow_Editor_Class() {} + +// 析构函数 +Workflow_Editor_Class::~Workflow_Editor_Class() +{ + // 释放资源 + for (auto &pNode : this->Node_List) + delete pNode; +} + +// 客制化圆形带X按钮 +bool CircleButtonWithX(const char *id, float radius) +{ + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + // 创建ID + ImGuiID button_id = window->GetID(id); + + // 计算按钮位置和大小 + ImVec2 pos = window->DC.CursorPos; + ImVec2 size(radius * 2, radius * 2); + ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y)); + + // 处理交互 + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, button_id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, button_id, &hovered, &held); + + // 绘制 + ImU32 col = ImGui::GetColorU32( + held ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button); + + // 获取绘制列表 + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + ImVec2 center = ImVec2(pos.x + radius, pos.y + radius); + + // 绘制圆形 + draw_list->AddCircleFilled(center, radius, col, 32); + + // 绘制边框(可选) + draw_list->AddCircle(center, radius, ImGui::GetColorU32(ImGuiCol_Border), 32, 1.0f); + + // 计算X的线条 + float cross_radius = radius * 0.5f; // X的大小 + float thickness = radius * 0.15f; // X的粗细 + + // 绘制X的两条线 + draw_list->AddLine( + ImVec2(center.x - cross_radius, center.y - cross_radius), + ImVec2(center.x + cross_radius, center.y + cross_radius), + ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)), // 白色X + thickness); + draw_list->AddLine( + ImVec2(center.x + cross_radius, center.y - cross_radius), + ImVec2(center.x - cross_radius, center.y + cross_radius), + ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)), // 白色X + thickness); + + return pressed; +} + +// 消息行输入节点绘制函数 +bool MsgLine_Input_Node_Class::show_and_get_state() +{ + // 节点宽度 + const float node_width = 200.0f; + bool delete_flag = false; + + // 初始化位置设定 + if (this->initial_state) + { + ImNodes::SetNodeScreenSpacePos(this->id, this->initial_position); + this->initial_state = false; + } + + // 绘制节点 + ImNodes::BeginNode(this->id); + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ImGui::GetStyle().ItemSpacing.y)); + // 标题部分 + ImNodes::BeginNodeTitleBar(); + { + ImGui::TextUnformatted(u8"DLT-信息输入节点"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(node_width - ImGui::CalcTextSize(u8"DLT-信息输入节点").x - ImGui::GetTextLineHeight(), 0.0f)); + ImGui::SameLine(); + // 绘制关闭按钮 + if (CircleButtonWithX("#X", ImGui::GetTextLineHeight() / 2)) + delete_flag = true; + } + ImNodes::EndNodeTitleBar(); + + // // 正文部分 + // ImGui::Text(u8"DLT消息输入流\n每次输出一条消息"); + // ImGui::Text(u8"(节点ID : %d)", this->id); + + // 链接点 + ImNodes::BeginOutputAttribute(0, 1); + // const float label_width = ImGui::CalcTextSize(u8"DLT消息行->").x; + // ImGui::Indent(label_width); + ImGui::Dummy(ImVec2(node_width - ImGui::CalcTextSize("(?) DLT MSG ->").x, 0.0f)); + ImGui::SameLine(); + + // Help Mark + { + ImGui::TextDisabled("(?) "); + if (ImGui::BeginItemTooltip()) + { + ImGui::TextUnformatted(u8"每个处理流程开始时,输出一条DLT消息;\n每个此类节点输出的消息均会独立遍历所有输入文件;"); + ImGui::EndTooltip(); + } + } + + ImGui::SameLine(); + ImGui::TextUnformatted("DLT MSG ->"); + ImNodes::EndOutputAttribute(); + + ImGui::PopStyleVar(); + } + ImNodes::EndNode(); + + // 返回节点状态 + return delete_flag; +} + +// 显示工作流节点编辑器 +void Workflow_Editor_Class::Show(void) +{ + // 浅色主题 + ImNodes::StyleColorsLight(); + ImNodes::BeginNodeEditor(); + ImGui::PopStyleVar(); + { + + // 显示所有节点 + for (auto iterator = this->Node_List.begin(); iterator != this->Node_List.end(); iterator++) + { + if ((*iterator)->show_and_get_state()) + { + delete *iterator; + + // 迭代器删除后会返回下一个位置的迭代器 + iterator = this->Node_List.erase(iterator); + iterator--; + } + } + + // 右键菜单 + { + const bool open_popup = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && + ImNodes::IsEditorHovered() && + ImGui::IsMouseReleased(ImGuiMouseButton_Right); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f)); + if (!ImGui::IsAnyItemHovered() && open_popup) + { + ImGui::OpenPopup("RightClick_Popup"); + } + + if (ImGui::BeginPopup("RightClick_Popup")) + { + ImVec2 click_pos = ImGui::GetMousePosOnOpeningCurrentPopup(); + // 节点添加菜单 + if (ImGui::BeginMenu("Add Nodes")) + { + if (ImGui::MenuItem("Msg input node.")) + this->Add_Node(NODE_TYPE_MSG_LINE_INPUT, click_pos); + ImGui::EndMenu(); + } + + ImGui::EndPopup(); + } + } + + { + // ImNodes::BeginNode(1); + // ImNodes::BeginNodeTitleBar(); + + // // ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f); // 绘制风格栈压入窗口圆角为0 + // // ImGui::Button("X"); + // // ImGui::PopStyleVar(); + // if (CircleButtonWithX("#X", ImGui::GetTextLineHeight() / 2)) + // { + // }; + + // ImGui::SameLine(); + // ImGui::TextUnformatted("Output Node"); + + // ImNodes::EndNodeTitleBar(); + + // ImGui::Text("Test Format %%d :%d", 123); + + // ImGui::Button("Click"); + + // ImGui::SameLine(); + // static bool check = false; + // ImGui::Checkbox("", &check); + + // ImNodes::EndNode(); + } + } + ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_TopRight); + ImNodes::EndNodeEditor(); +} + +// 添加指定类型节点 +void Workflow_Editor_Class::Add_Node(NodeType type, ImVec2 initial_position) +{ + // 获取可用ID + auto get_id = [&]() -> int + { + int target_id = 0; + for (auto &pNode : this->Node_List) + if (target_id == pNode->getID()) + target_id++; + else + break; + return target_id; + }; + + // 记录节点 + switch (type) + { + case NODE_TYPE_MSG_LINE_INPUT: // 消息行输入 + this->Node_List.push_back(new MsgLine_Input_Node_Class(get_id(), initial_position)); + break; + case NODE_TYPE_CSV_EXPORTER: // CSV输出器 + default: + break; + } + + // 重新排序确保后续ID分配正常 + std::sort(this->Node_List.begin(), + this->Node_List.end(), + [](Node_Class *a, Node_Class *b) -> bool + { return a->getID() < b->getID(); }); +} \ No newline at end of file