分割条 Splitter
想让用户自己拖动调整左右面板宽度?Splitter 就是那个细细的分隔线,按住拖拽就能改变布局比例。
Splitter 本身不创造任何可见的"面板",它只负责两件事:
- 画一条分割线(垂直或水平)。
- 拦截拖拽事件,实时修改你指定的
Size变量。
所以它总是和 Panel、BeginHorizontal / BeginVertical 配合使用。
函数原型
// 渲染一个分割条,并处理拖拽交互
// 返回 true 表示当前帧正在拖拽中
bool Splitter(const HXString &id, HXGInt &Size, SplitterProfile &Profile);
参数详解
SplitterProfile
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Horizontal | bool | false | false = 垂直分割条(左右分栏);true = 水平分割条(上下分栏) |
Thickness | int | 4 | 分割条的视觉粗细(像素) |
MinSize | int | 20 | 允许的最小尺寸,拖拽不会被压缩到比这个更小 |
MaxSize | int | 1000 | 允许的最大尺寸 |
InDrag | bool | — | [输出] 当前是否正在拖拽(内部状态,Profile 需 static) |
DragStart | int | — | [输出] 拖拽开始时的鼠标位置(内部状态) |
OnHover | bool | — | [输出] 鼠标是否悬停在分割条上(内部状态) |
这些是框架需要跨帧保持的状态。因为 Splitter 是 IMGUI 风格的无状态控件,所有记忆都靠你的 Profile 对象携带。所以 SplitterProfile 必须是 static 或全局变量。
参数 Size
Horizontal = false(垂直)时:Size表示左侧面板的宽度。拖拽会改变这个值。Horizontal = true(水平)时:Size表示顶部面板的高度。拖拽会改变这个值。
通常让另一侧面板的大小设为 -1(自动填充剩余空间),这样只要左侧/顶部的 Size 变了,整体布局就自然跟着变了。配合 BeginHorizontal / BeginVertical 使用非常方便。
返回值
| 函数 | 返回值 | 说明 |
|---|---|---|
Splitter | bool | true 表示本帧正在拖拽中。可以用来做拖拽时的视觉反馈(比如改变光标或高亮边框) |
最小示例:左右分栏
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void TwoColumnLayout() {
static HXGInt leftWidth = 250; // 左侧初始宽度
static HX::SplitterProfile sp{}; // 必须是 static
sp.Horizontal = false; // 垂直分割(左右)
sp.MinSize = 100;
sp.MaxSize = 600;
HX::BeginHorizontal();
// ---- 左侧面板 ----
HX::PanelProfile leftPanel{};
leftPanel.Size = {leftWidth, 400};
HX::BeginPanel(HXStr("left"), leftPanel);
HX::Text(HXStr("左侧工具栏"));
HX::Button(HXStr("工具 A"), HX::ButtonProfile{});
HX::EndPanel(leftPanel);
// ---- 分割条 ----
bool dragging = HX::Splitter(HXStr("vsplit"), leftWidth, sp);
if (dragging) {
// 可选:拖拽时做点什么,比如改光标
}
// ---- 右侧面板(-1 表示占满剩余宽度)----
HX::PanelProfile rightPanel{};
rightPanel.Size = {-1, 400};
HX::BeginPanel(HXStr("right"), rightPanel);
HX::Text(HXStr("右侧主内容区"));
HX::EndPanel(rightPanel);
HX::EndHorizontal();
}
水平分割:上下分栏
把 Horizontal 改成 true,Size 就变成了顶部高度:
static HXGInt topHeight = 200;
static HX::SplitterProfile sp{};
sp.Horizontal = true; // 水平分割(上下)
sp.MinSize = 50;
sp.MaxSize = 500;
HX::BeginHorizontal();
HX::PanelProfile topPanel{};
topPanel.Size = {-1, topHeight};
HX::BeginPanel(HXStr("top"), topPanel);
HX::Text(HXStr("顶部信息栏"));
HX::EndPanel(topPanel);
HX::Splitter(HXStr("hsplit"), topHeight, sp);
HX::PanelProfile bottomPanel{};
bottomPanel.Size = {-1, -1}; // 占满剩余高度
HX::BeginPanel(HXStr("bottom"), bottomPanel);
HX::Text(HXStr("底部详情"));
HX::EndPanel(bottomPanel);
HX::EndHorizontal();
与 DockSpace 的区别
| 特性 | Splitter + Panel | DockSpace |
|---|---|---|
| 布局复杂度 | 简单,一对一或手动组合 | 复杂,五区 + MDI TabBar |
| 用户调整 | 拖拽实时改变 | 拖拽实时改变 |
| 布局持久化 | 需要自己保存 Size 变量 | SaveDockLayout / LoadDockLayout 一键搞定 |
| Tab 切换 | 不支持 | 原生支持 |
| 适用场景 | 固定两栏/三栏编辑器 | IDE 级复杂多面板 |
如果你只是想要两个面板中间拖一下调整大小,Splitter 是最轻量的选择。如果你要的是 VS Code 那种五个方向 + TabBar + 自动保存恢复,请直接上 DockSpace。
完整示例:三栏编辑器(左-中-右)
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void ThreeColumnEditor() {
static HXGInt leftWidth = 200;
static HXGInt rightWidth = 250;
static HX::SplitterProfile spLeft{};
spLeft.Horizontal = false;
spLeft.MinSize = 100;
spLeft.MaxSize = 400;
static HX::SplitterProfile spRight{};
spRight.Horizontal = false;
spRight.MinSize = 100;
spRight.MaxSize = 500;
HX::BeginHorizontal();
// ===== 左侧面板 =====
HX::PanelProfile p1{};
p1.Size = {leftWidth, 600};
HX::BeginPanel(HXStr("panel_left"), p1);
HX::Text(HXStr("文件树"));
static std::vector<HX::HXTreeNode> nodes = {
{HXStr("src"), true, false, {
{HXStr("main.cpp"), false, false, {}},
}},
};
HX::TreeViewProfile tvp{};
HX::TreeView(HXStr("tree"), nodes, tvp);
HX::EndPanel(p1);
// ===== 左分割条 =====
HX::Splitter(HXStr("split_left"), leftWidth, spLeft);
// ===== 中间主编辑区 =====
HX::PanelProfile p2{};
p2.Size = {-1, 600}; // 占满剩余宽度
HX::BeginPanel(HXStr("panel_center"), p2);
HX::Text(HXStr("代码编辑器"));
static std::vector<HXString> lines = {
HXStr("void hello() {"),
HXStr(" printf(\"HiEasyX!\");"),
HXStr("}"),
};
HX::TextEditorProfile ep{};
HX::TextEditor(HXStr("code"), lines, ep);
HX::EndPanel(p2);
// ===== 右分割条 =====
HX::Splitter(HXStr("split_right"), rightWidth, spRight);
// ===== 右侧面板 =====
HX::PanelProfile p3{};
p3.Size = {rightWidth, 600};
HX::BeginPanel(HXStr("panel_right"), p3);
HX::Text(HXStr("属性"));
HX::Checkbox(HXStr("Visible"), HX::CheckboxProfile{true});
HX::Checkbox(HXStr("Locked"), HX::CheckboxProfile{false});
HX::EndPanel(p3);
HX::EndHorizontal();
}
常见坑与注意事项
SplitterProfile 里存了 InDrag、DragStart、OnHover 等跨帧状态。如果你在栈上定义且不加 static,每次调用都是全新的 Profile,拖拽会在按下第二帧就中断。
虽然 Splitter 只要求 Size 是引用,但如果你传一个局部变量,这帧拖完了下帧值就丢了。用 static 或类成员变量保持持久。
垂直分割条(Horizontal = false)会自动和父容器一样高;水平分割条会自动和父容器一样宽。你不需要手动设置分割条的长度。
同一个窗口里如果有多个 Splitter,给它们不同的 id(比如 HXStr("split_left")、HXStr("split_right")),否则内部状态会互相覆盖。