跳到主要内容

布局系统

如果你写过传统 GUI,一定经历过这种痛苦:先算坐标、再调像素、最后在不同分辨率下全部重来。HiEasyX 说:别算了,交给我

HiEasyX 的布局哲学非常简单——基于 BaseLine 的自动流式布局。你只需要按顺序写下控件,框架会自动帮你排好位置。就像Word里的"流式排版",控件一个接一个地"流"下去,而不是你逐个指定 (x, y) 坐标。

核心思想

忘掉绝对坐标。在 HiEasyX 中,你关心的是顺序关系,而不是像素位置


垂直布局(默认)

HiEasyX 的默认布局方向是垂直的。也就是说,你每放一个控件,下一个就会自动出现在它的下方:

HX::Window(HXStr("示例窗口"), wp);
HX::Text(HXStr("第一行文本"));
HX::Button(HXStr("按钮"), bp); // 自动排在文本下方
HX::Slider1f(HXStr("滑块"), val, sp); // 自动排在按钮下方

这背后的功臣是 BaseLine——当前布局的基准线。每放入一个控件,BaseLine 就会自动向下移动,为下一个控件腾出空间。


水平布局

想让控件横着排?用 BeginHorizontal() / EndHorizontal()(详见 [水平布局 Horizontal](./水平布局 Horizontal)),或者更简单的方式——BeginSameLine() / EndSameLine()(详见 [同行布局 SameLine](./同行布局 SameLine))。

HX::BeginHorizontal();
HX::Button(HXStr("按钮A"), bp1);
HX::Button(HXStr("按钮B"), bp2);
HX::EndHorizontal();

间距三兄弟:ControlGap、LeftMargin、Padding

理解这三个概念,你就掌握了 HiEasyX 布局的精髓:

概念作用对象通俗解释
ControlGap控件之间"下一个控件离我多远"
LeftMargin左侧留白"整行内容离左边界多远"
Padding容器内部"盒子内壁和内容之间的缓冲距离"
一张图看懂三者关系

想象你在写日记:

  • LeftMargin 是纸张左边距,整页都生效
  • ControlGap 是段落间距,段与段之间的空隙
  • Padding 是某一页的边框装饰线到文字的距离

SetControlGap

设置当前布局上下文中,控件与控件之间的默认间距

void SetControlGap(HXGInt Gap);
参数类型说明
GapHXGInt控件之间的像素间距。默认通常为 5。
HX::SetControlGap(10);  // 后续控件之间间隔 10 像素
HX::Button(HXStr("按钮1"), bp1);
HX::Button(HXStr("按钮2"), bp2); // 与按钮1相距 10px

SetBaseLine

手动设置当前布局的基准线 Y 坐标。这相当于"把光标跳到某个高度"。

void SetBaseLine(HXGInt BaseLine);
参数类型说明
BaseLineHXGInt新的基准线 Y 坐标(相对于当前布局上下文)。
HX::SetBaseLine(100);   // 下一个控件从 Y=100 开始排
HX::Text(HXStr("从100像素处开始"));
谨慎使用

除非你真的需要跳过一段空间(比如手动分页),否则不建议随意修改 BaseLine。正常流式布局会自动管理它。


布局辅助函数

LayoutNextControl

在放置控件之前,预占一块指定大小的布局空间。这常用于你需要手动绘制某些内容,但又想让框架知道"这里已经被占用了"。

void LayoutNextControl(HXPoint Size);
参数类型说明
SizeHXPoint要预占的宽高。
HX::LayoutNextControl({200, 100});  // 告诉框架:我要用 200x100 的空间
// 然后你可以在这里用 CurrentPainter() 手动绘制自定义内容

CurrentLayoutPosition

获取当前布局光标的位置。返回值是当前上下文中,下一个控件将被放置的左上角坐标

HXPoint CurrentLayoutPosition();
返回值说明
HXPoint当前布局位置 {X, Y},相对于当前布局上下文。
HXPoint pos = HX::CurrentLayoutPosition();
// pos.X, pos.Y 就是下一个控件的起点

下一项预设:NextItemXxx

有时候你想对某一个控件做特殊处理,而不想改变整个布局的默认设置。这时候 NextItemXxx 系列就是你的救星——它们只影响紧接着的下一个控件

SetNextItemSize

void SetNextItemSize(HXPoint Size);
参数类型说明
SizeHXPoint下一个控件的尺寸。{-1, -1} 表示使用控件自身默认值。

SetNextItemWidth

void SetNextItemWidth(HXGInt Width);

SetNextItemHeight

void SetNextItemHeight(HXGInt Height);

SetNextItemMargin

void SetNextItemMargin(HXGInt Margin);

SetNextItemPadding

void SetNextItemPadding(HXGInt Padding);
经典用法

比如你想让一个输入框占满整行宽度,但其他控件保持自动大小:

static HX::TextInputProfile tip;
HX::SetNextItemWidth(300);
HX::TextInput(tip); // 这个输入框宽 300

HX::Button(HXStr("后面的按钮"), bp); // 按钮恢复正常自动大小

可见性计算:只渲染看得见的

当控件很多、窗口有滚动条、或者被折叠起来时,没必要每一帧都绘制看不见的控件。HiEasyX 提供了一套可见性判断 API,帮你做优化。

CurrentVisibility

判断一个指定尺寸的控件,在当前布局上下文中是否可能可见

bool CurrentVisibility(HXPoint Size);
参数类型说明
SizeHXPoint控件的尺寸。
返回值说明
booltrue —— 控件在当前可见区域内,应该渲染。
false —— 控件在视野外,可以跳过。

RoughVisibility

粗略判断当前窗口是否被折叠或最小化。如果窗口被折叠了,里面所有控件都不用画了。

bool RoughVisibility();
返回值说明
boolfalse 表示窗口被折叠或不可见,可以整批跳过。

AccVisibility

CurrentVisibility 更精确的判断,考虑了更多边界条件。

bool AccVisibility(HXPoint Size);

NeedLayoutCal

判断当前控件是否需要进行布局计算。如果返回 false,你甚至可以跳过逻辑更新。

bool NeedLayoutCal();
什么时候需要用这些?

对于 HiEasyX 内置的控件(ButtonTextSlider 等),框架内部已经自动调用了这些优化,你不需要手动处理。

但如果你在写自定义绘制控件(比如自己用 CurrentPainter() 画一张复杂图表),建议在绘制前检查一下 CurrentVisibility(),能省下不少 GPU/CPU 时间:

HXPoint mySize = {400, 300};
if (!HX::CurrentVisibility(mySize)) {
// 看不见,跳过!
HX::LayoutNextControl(mySize); // 但还是要占位置
return;
}
// 执行复杂的自定义绘制...

布局方向与对齐枚举

enum class HXLayoutDirection {
Vertical, // 垂直堆叠(默认)
Horizontal // 水平排列
};

enum class HXLayoutAlign {
Start, // 靠左/靠上
Center, // 居中
End, // 靠右/靠下
Stretch // 拉伸填满
};

这些枚举在 PanelGroup 等容器中使用,控制子控件的整体排布方式。


完整示例代码

下面这个示例展示了 BaseLine 控制、间距调整、NextItem 预设、以及可见性检测的综合用法:

#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>

int main() {
initgraph(800, 600);
setbkcolor(WHITE);
cleardevice();

HX::HXInitForEasyX();
HX::SetBuffer(GetWorkingImage());

BeginBatchDraw();

static HX::WindowProfile wp;
wp.Size = {500, 450};

static HX::ButtonProfile bp;
static HX::SliderProfile1f sp;
static float val = 0.5f;

while (true) {
HX::HXBegin();

ExMessage msg;
while (peekmessage(&msg)) {
HX::PushMessage(HX::GetHXMessage(&msg));
}

HX::Window(HXStr("布局系统演示"), wp);

// 1. 默认垂直流式布局
HX::Text(HXStr("=== 默认垂直布局 ==="));
HX::Button(HXStr("按钮 A"), bp);
HX::Button(HXStr("按钮 B"), bp);

// 2. 加大控件间距
HX::SetControlGap(15);
HX::Text(HXStr("=== 间距加大到 15 ==="));
HX::Button(HXStr("按钮 C"), bp);
HX::SetControlGap(5); // 恢复默认

// 3. NextItem 预设:只影响下一个控件
HX::Text(HXStr("=== NextItem 预设宽度 200 ==="));
HX::SetNextItemWidth(200);
HX::Button(HXStr("我很宽"), bp);
HX::Button(HXStr("我恢复正常"), bp);

// 4. 手动调整 BaseLine
HX::Text(HXStr("=== 手动下移 30 像素 ==="));
HXPoint pos = HX::CurrentLayoutPosition();
HX::SetBaseLine(pos.Y + 30);
HX::Button(HXStr("我在下面远一些"), bp);

HX::End();
HX::Render();
FlushBatchDraw();
Sleep(16);
}

closegraph();
return 0;
}
给新手的建议

刚开始不要纠结 BaseLineLayoutNextControl。对于 90% 的场景,垂直流式布局 + occasional 的 Horizontal/SameLine 已经足够用了。等到你需要做复杂自定义控件时,再回来翻这一页。