Adjusted UI layout && Tested dynamic window expansion effects && Added node editor area (experimental, currently only one simple test node available with no practical functionality)

This commit is contained in:
LuChiChick 2026-01-26 20:57:13 +08:00
parent cfb1be0efd
commit 0f2f2720fc
5 changed files with 392 additions and 8 deletions

View File

@ -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

89
Inc/Workflow-Editor.hpp Normal file
View File

@ -0,0 +1,89 @@
#ifndef __WORKFLOW_NODES_HPP__
#define __WORKFLOW_NODES_HPP__
#include "imgui.h"
extern "C"
{
#include "stdint.h"
}
#include <vector>
// // 并行工作链
// 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_Class *> Node_List;
public:
// 构造函数
Workflow_Editor_Class();
// 析构函数
~Workflow_Editor_Class();
// 显示工作流节点编辑器
void Show(void);
// 新增节点
void Add_Node(NodeType type, ImVec2 initial_position = ImVec2(0, 0));
};
#endif

View File

@ -15,3 +15,6 @@ ID3D11RenderTargetView *g_mainRenderTargetView = nullptr; // D3D11 渲染目标
// 文件输入相关
Input_File_Node *input_dlt_file_list_head;
size_t input_dlt_file_count;
// 节点编辑器
Workflow_Editor_Class Workflow_Editor;

View File

@ -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)

250
Src/Workflow-Editor.cpp Normal file
View File

@ -0,0 +1,250 @@
#include "Workflow-Editor.hpp"
#include "imgui.h"
#include "imgui_internal.h"
#include "imnodes.h"
#include <algorithm>
// 构造函数
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(); });
}