UI 控件
控件工厂用 UI 组件模型(UINode + Interactable +
$interaction 控制器与属性绑定)构建常见控件,
免得你逐个手动接线。每个工厂接收一个选项对象,返回一个句柄(handle)——
总是带 entity 和 dispose(),外加读取和驱动它的命令式方法。
交互式控件需要 world 和 UI 事件队列——world 来自 GetWorld(),events 来自
Res(UIEvents),和 UI 指南里一样。它们都接受 parent?、
一个 node?(UINode 布局——默认:填满父级)
和视觉覆盖;视觉默认值取自当前主题的角色,
主题热切换时会重新解析。交互式控件默认键盘可达:它们自带 Focusable,
Tab 能到达、Enter/Space 能点击、方向键能驱动滑块——用 focusable: false 退出。

内置控件(取自 ui-controls 示例):一个 Button(按钮)、一个 Toggle(开关)、一个
Slider(滑块)、一个 Progress(进度条),以及打开 Dialog(对话框) 和切换强调色的
按钮——每个都只是挂了 UI 组件的普通实体树。
Button 按钮
Section titled “Button 按钮”createButton(opts) 生成一个可点击实体(带可选子标签),在各视觉状态间过渡。返回 ButtonHandle。
一次点击是在按钮上按下再松开——或在它持有键盘焦点时按 Enter/Space。
import { createButton } from 'esengine';
const btn = createButton({ world, events, parent, text: 'Play', node: { width: px(160), height: px(44) }, states: { normal: { color: { r: 0.20, g: 0.55, b: 1.0, a: 1 } }, hover: { color: { r: 0.30, g: 0.65, b: 1.0, a: 1 } }, pressed: { color: { r: 0.12, g: 0.45, b: 0.9, a: 1 } }, disabled:{ color: { r: 0.3, g: 0.3, b: 0.3, a: 1 } }, }, onClick: (entity) => { /* … */ },});| 选项 | 默认 | 说明 |
|---|---|---|
node |
填满父级 | 按钮的 UINode 布局。 |
text |
无 | 标签——字符串或 TextInit。省略则无标签。标签颜色/字号默认取主题的 text 角色与 label 字号。 |
states |
主题 control 角色 | 状态名(normal / hover / pressed / disabled / focused)→ 颜色 / 精灵 / 缩放覆盖的 map。允许自定义名(如 'loading')。 |
background |
纯白 | 背景视觉(UIVisualInit)。 |
fadeDuration |
0(瞬切) |
颜色/缩放状态切换的补间时长;精灵切换总是瞬切。 |
disabled |
false |
以禁用态启动。 |
focusable / tabIndex |
true / 0 |
键盘可达性与 Tab 顺序。 |
onClick(entity) |
— | 点击回调(受启用态门控)。 |
句柄: entity、setDisabled(disabled)、dispose()。
主题默认状态也以 themeButtonStates() 导出——展开再覆盖,不必五个状态全写一遍。
之后可用 setButtonState(world, handle.entity, 'loading') 切到自定义状态;
指针驱动会放过任何非规范页,直到你切回去。
Toggle 开关
Section titled “Toggle 开关”createToggle(opts) 由一个按钮加一个开/关指示器组合而成。返回 ToggleHandle。
import { createToggle } from 'esengine';
const toggle = createToggle({ world, events, parent, interactionStates: { normal: { color: /* … */ }, hover: { /* … */ }, pressed: { /* … */ } }, check: { color: { r: 0.2, g: 0.8, b: 0.4, a: 1 } }, // the "on" indicator isOn: true, onChange: (isOn, entity) => { /* … */ },});
toggle.setValue(false); // programmatic change (fires onChange)| 选项 | 默认 | 说明 |
|---|---|---|
node / background |
填满父级 / 纯白 | 外框布局 + 背景,同 createButton。 |
interactionStates |
主题 control 角色 | 外框的视觉状态(同按钮的 states)。 |
check |
主题 primary 填充 |
开启态指示器:{ node?, color?, sprite? },开时显示的子实体。默认填满外框——用 check.node 内缩。 |
isOn |
false |
初始状态。 |
disabled |
false |
以禁用态启动。 |
focusable / tabIndex |
true / 0 |
键盘可达性。 |
onChange(isOn, entity) |
— | 切换时触发。 |
句柄: getValue()、setValue(value)、setDisabled(disabled)、entity、dispose()。
Slider 滑块
Section titled “Slider 滑块”createSlider(opts) 构建轨道 + 填充 + 手柄。返回 SliderHandle。
输入内置:在轨道任意处拖拽(按下即捕获直到松开),或聚焦后用方向键
(step,连续滑块为量程的 1%)、Home/End。
import { createSlider } from 'esengine';
const slider = createSlider({ world, events, parent, min: 0, max: 100, value: 50, step: 5, onChange: (value) => { /* … */ },});
slider.setValue(75);| 选项 | 默认 | 说明 |
|---|---|---|
node |
填满父级 | 轨道的 UINode 布局。 |
min / max |
0 / 1 |
量程。 |
value |
min |
初始值(会收敛并按步长量化)。 |
step |
0(连续) |
量化步长。同时是键盘步进量。 |
handleWidth |
12 |
手柄宽度(像素)。 |
disabled / focusable / tabIndex |
false / true / 0 |
交互性 + 键盘可达性。 |
trackVisual / fillVisual / handleVisual |
主题 track / primary / onPrimary |
各部分视觉。 |
onChange(value, entity) |
— | 值变化时触发——无论来自输入、setValue 还是任何其他写入者。 |
句柄: getValue()、setValue(v)、dispose(),外加 entity /
trackEntity(同一实体)/ fillEntity / handleEntity。
Progress 进度条
Section titled “Progress 进度条”createProgress(opts) 是不可交互的填充条。返回 ProgressHandle。
它不接 events——没有可交互的东西。
import { createProgress } from 'esengine';
const bar = createProgress({ world, parent, direction: 'right', fill: { color: { r: 0.2, g: 0.8, b: 0.4, a: 1 } }, value: 0,});
bar.setValue(0.6); // 0..1
// Radial gauge (cooldown / ring meter) — a clockwise wedge from 12 o'clock:const ring = createProgress({ world, parent, radial: true, value: 0.75 });| 选项 | 默认 | 说明 |
|---|---|---|
node |
填满父级 | 轨道的 UINode 布局。 |
value |
0 |
初始进度,0..1(收敛)。 |
direction |
'right' |
线性填充增长方向:'right'、'left'、'up'、'down'。 |
radial |
false |
径向仪表而非条——从 12 点顺时针扫满一圈的扇形。忽略 direction。 |
background |
主题 track |
轨道视觉(UIVisualInit)。 |
fill |
主题 primary |
填充条:{ color?, sprite? }。 |
句柄: getValue()、setValue(v)、dispose(),外加 entity / fillEntity。
Dropdown 下拉框
Section titled “Dropdown 下拉框”createDropdown(opts) 显示当前选中项,并打开一个选项行弹窗。对选项类型泛型。
返回 DropdownHandle<T>。
import { createDropdown } from 'esengine';
const dd = createDropdown({ world, events, parent, options: ['Low', 'Medium', 'High'], selectedIndex: 1, onSelect: (index, option) => { /* … */ },});
// Objects need a label mapper:createDropdown({ world, events, parent, options: resolutions, // e.g. { w, h }[] optionToLabel: (r) => `${r.w}×${r.h}`, onSelect: (i, r) => applyResolution(r),});| 选项 | 默认 | 说明 |
|---|---|---|
node |
填满父级 | 主按钮的 UINode 布局。 |
options |
— | 可选值列表。必填。 |
selectedIndex |
0 |
初始选中。 |
optionToLabel(option, i) |
String(option) |
把非字符串选项映射为显示文本。 |
buttonStates |
主题 control 角色 | 主按钮的 { normal?, hover?, pressed? } 颜色(选项行用主题角色)。 |
optionHeight |
32 |
行高(像素)。 |
disabled / focusable / tabIndex |
false / true / 0 |
交互性 + 键盘可达性。 |
onSelect(index, option, entity) |
— | 选中时触发。 |
句柄: getSelected()、getSelectedIndex()、setSelectedIndex(i)、
open()、close()、isOpen()、dispose(),外加 entity / labelEntity
(显示当前选中项的子 Text)。
Dialog 对话框
Section titled “Dialog 对话框”createDialog(opts) 是模态框:挡点击的背板 + 居中面板,打开前隐藏。返回 DialogHandle。
把你的内容挂为 panelEntity 的子级——关闭时在背板根上写 display: none,
整棵子树(含你的内容)一次写入就退出布局、渲染和输入。
import { createDialog, spawnUIEntity } from 'esengine';
const dialog = createDialog({ world, events, parent, panelNode: { width: px(420), height: px(260) },});
spawnUIEntity({ world, parent: dialog.panelEntity, text: { content: 'Are you sure?' } });
dialog.open();// …laterdialog.close();| 选项 | 默认 | 说明 |
|---|---|---|
backdropNode / backdropVisual |
填满父级 / 主题 backdrop 幕布 |
面板背后的全视口幕布。它拦截指针命中但不可聚焦。 |
panelNode / panelVisual |
400×300 居中 / 主题 surface |
模态面板。面板吞掉点击,只有真正点在幕布上才触发关闭。 |
startHidden |
true |
以关闭态启动。 |
closeOnEscape / closeOnBackdrop |
true / true |
标准关闭方式——Escape,以及点击幕布(非面板)。 |
onOpenChange(open) |
— | 每次开/关都触发,无论谁发起。 |
对话框打开期间还会困住 Tab 环:只有打开的对话框子树内的可聚焦项参与 (幕布本就挡住了外部的指针聚焦)。
句柄: open()、close()、isOpen()、dispose(),外加 entity(背板根)/
panelEntity。
createTextInput(opts) 构建可编辑输入框——背景、光标、占位符与 IME 管线齐备——
颜色取主题角色,主题切换时重新解析。返回 TextInputHandle:
getValue() / setValue(v) / entity / dispose()。
import { createTextInput } from 'esengine';
const name = createTextInput({ world, events, parent, placeholder: 'Your name', maxLength: 20, onChange: (value) => { /* value edited */ }, onSubmit: (value) => save(value), // Enter});| 选项 | 默认 | 说明 |
|---|---|---|
node |
220×32 | 输入框的 UINode 布局(唯一不默认填满父级的工厂)。 |
value / placeholder |
'' |
内容 / 为空时的提示。 |
maxLength |
0 |
字符上限(0 = 不限)。 |
multiline |
false |
Enter 插入换行而不是提交。 |
password |
false |
掩码显示字符。 |
readOnly |
false |
可聚焦但不可编辑。 |
fontSize / fontFamily |
主题 label 字号 / 'Arial' |
文本样式。 |
color / backgroundColor / placeholderColor |
主题 text / control / 淡化 text |
颜色覆盖——省略时用主题角色。 |
padding |
6 |
内部水平内边距(px)。 |
renderMode |
Auto |
输入框文字的字形管线——Auto(未缩放时清晰位图,缩放后 SDF)、恒 Bitmap、或恒 Sdf。 |
disabled / tabIndex |
false / 0 |
交互性 + Tab 顺序。文本框永远可聚焦——它就是干这个的。 |
onChange(value, entity) |
— | 每次编辑触发。 |
onSubmit(value, entity) |
— | Enter 触发(单行)。 |
工厂是 TextInput 组件之上的语法糖——直接把组件插到任意 UI 实体上可获得完全的
手动控制(字段同上,外加实时的 focused / cursorPos 状态):
import { spawnUIEntity, TextInput, px } from 'esengine';
const field = spawnUIEntity({ world, parent, node: { width: px(240), height: px(32) } });world.insert(field, TextInput, { placeholder: 'Your name', maxLength: 20 });
events.on(field, 'change', () => { /* value edited */ });events.on(field, 'submit', () => save(world.get(field, TextInput).value)); // Enter点击(或 Tab 到)输入框即聚焦;Escape 失焦,单行框 Enter 触发 submit。
超出盒宽的内容裁剪到盒内并横向滚动,输入时光标始终可见。
虚拟化的数据驱动列表与网格(createListView)、自由滚动面板(createScrollView)、
数据源、布局提供者、自动行高以及惯性滚动 / 滚动条机制,单独成章:
列表与滚动。
控件值就是普通组件字段(UISlider.value、UIToggle.isOn……),信号层可直接绑定——
bindWidgetValue 更把它做成双向。见 UI 数据绑定。
给任意可交互 UI 实体加 Draggable,指针拖拽即可移动它,附带
drag_start / drag_move / drag_end 事件和实时的 DragState 组件。
轴锁定与范围约束齐备。见 UI 交互。
带 Focusable 的实体参与键盘焦点:Tab / Shift+Tab 按 tabIndex 轮换,
Enter/Space 激活,滑块接管方向键,聚焦的控件显示其 focused 交互页。
控件工厂自动加 Focusable(focusable: false 退出);FocusManager 资源提供
程序化控制。详见 UI 交互。
安全区(移动端刘海)
Section titled “安全区(移动端刘海)”给全屏、绝对定位的 UINode 容器加 SafeArea,其 inset 会跟随平台安全区——
刘海、圆角、微信胶囊——并支持按边退出。见
屏幕与分辨率。
控件颜色来自共享的 token 集,角色如 surface、control、primary、track;
switchTheme(world, tokens) 实时重刷屏上一切,项目在 Project Settings → UI
一次声明调色板。见 UI 主题。
编辑器预制体
Section titled “编辑器预制体”同一批控件也作为编辑器预制体提供,Create… → UI 即可把现成的 Button、Toggle、 Slider、Dialog、TextInput 等丢进场景,再在 Details 里编辑。活在组件里的行为—— 滑块的拖拽/按键、对话框的 Escape/幕布关闭——在摆放的预制体上无需代码即可工作。
从调色板拖放会嵌套:落点会认指针下的布局容器为父级(拖拽时虚线框预览将要成为的 父级),空画布落点则照旧落在根上。锚点选择器按轴编辑并保持控件原位—— 改水平锚点绝不扰动垂直布局,反之亦然。见编辑器。
- 优先状态组件而不是攥着句柄。
UIToggle.isOn、UISlider.value、UIDropdown.selectedIndex是唯一真相——写它们(或绑定它们),视觉自动跟上; 句柄方法只是同一写入的带类型语法糖。 - 让主题拥有颜色。 省略
states/ 视觉即可获得随主题热切换重新解析的角色色; 只为刻意脱离主题的美术硬编码颜色。 - 对话框内容放在
panelEntity之下,绝不放在旁边——这才让开/关成为整棵子树的 单次display写入。 - 运行时创建的句柄要
dispose()——它销毁控件实体并解除事件订阅。 - 保持键盘可达。 只为装饰性或重复的控件传
focusable: false; Tab 环就是你的无障碍故事。