跳到主要内容

分割条 Splitter

想让用户自己拖动调整左右面板宽度?Splitter 就是那个细细的分隔线,按住拖拽就能改变布局比例。

Splitter 本身不创造任何可见的"面板",它只负责两件事:

  1. 画一条分割线(垂直或水平)。
  2. 拦截拖拽事件,实时修改你指定的 Size 变量。

所以它总是和 PanelBeginHorizontal / BeginVertical 配合使用。


函数原型

// 渲染一个分割条,并处理拖拽交互
// 返回 true 表示当前帧正在拖拽中
bool Splitter(const HXString &id, HXGInt &Size, SplitterProfile &Profile);

参数详解

SplitterProfile

成员类型默认值说明
Horizontalboolfalsefalse = 垂直分割条(左右分栏);true = 水平分割条(上下分栏)
Thicknessint4分割条的视觉粗细(像素)
MinSizeint20允许的最小尺寸,拖拽不会被压缩到比这个更小
MaxSizeint1000允许的最大尺寸
InDragbool[输出] 当前是否正在拖拽(内部状态,Profile 需 static)
DragStartint[输出] 拖拽开始时的鼠标位置(内部状态)
OnHoverbool[输出] 鼠标是否悬停在分割条上(内部状态)
为什么 InDrag / DragStart / OnHover 也在 Profile 里?

这些是框架需要跨帧保持的状态。因为 Splitter 是 IMGUI 风格的无状态控件,所有记忆都靠你的 Profile 对象携带。所以 SplitterProfile 必须是 static 或全局变量

参数 Size

  • Horizontal = false(垂直)时:Size 表示左侧面板的宽度。拖拽会改变这个值。
  • Horizontal = true(水平)时:Size 表示顶部面板的高度。拖拽会改变这个值。
右侧/底部面板怎么办?

通常让另一侧面板的大小设为 -1(自动填充剩余空间),这样只要左侧/顶部的 Size 变了,整体布局就自然跟着变了。配合 BeginHorizontal / BeginVertical 使用非常方便。


返回值

函数返回值说明
Splitterbooltrue 表示本帧正在拖拽中。可以用来做拖拽时的视觉反馈(比如改变光标或高亮边框)

最小示例:左右分栏

#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 改成 trueSize 就变成了顶部高度

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 + PanelDockSpace
布局复杂度简单,一对一或手动组合复杂,五区 + MDI TabBar
用户调整拖拽实时改变拖拽实时改变
布局持久化需要自己保存 Size 变量SaveDockLayout / LoadDockLayout 一键搞定
Tab 切换不支持原生支持
适用场景固定两栏/三栏编辑器IDE 级复杂多面板
什么时候用 Splitter?

如果你只是想要两个面板中间拖一下调整大小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();
}

常见坑与注意事项

Profile 必须是 static / 全局

SplitterProfile 里存了 InDragDragStartOnHover 等跨帧状态。如果你在栈上定义且不加 static,每次调用都是全新的 Profile,拖拽会在按下第二帧就中断。

Size 变量也建议 static

虽然 Splitter 只要求 Size 是引用,但如果你传一个局部变量,这帧拖完了下帧值就丢了。用 static 或类成员变量保持持久。

分割条会自动占满交叉轴

垂直分割条(Horizontal = false)会自动和父容器一样高;水平分割条会自动和父容器一样宽。你不需要手动设置分割条的长度。

多个 Splitter 注意 ID 唯一

同一个窗口里如果有多个 Splitter,给它们不同的 id(比如 HXStr("split_left")HXStr("split_right")),否则内部状态会互相覆盖。