跳转到内容

UI 控件

控件工厂用 UI 组件模型(UINode + Interactable + $interaction 控制器与属性绑定)构建常见控件, 免得你逐个手动接线。每个工厂接收一个选项对象,返回一个句柄(handle)—— 总是带 entitydispose(),外加读取和驱动它的命令式方法。

交互式控件需要 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 组件的普通实体树。

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) 点击回调(受启用态门控)。

句柄: entitysetDisabled(disabled)dispose()

主题默认状态也以 themeButtonStates() 导出——展开再覆盖,不必五个状态全写一遍。 之后可用 setButtonState(world, handle.entity, 'loading') 切到自定义状态; 指针驱动会放过任何非规范页,直到你切回去。

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)entitydispose()

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

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

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

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();
// …later
dialog.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.valueUIToggle.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 交互

给全屏、绝对定位的 UINode 容器加 SafeArea,其 inset 会跟随平台安全区—— 刘海、圆角、微信胶囊——并支持按边退出。见 屏幕与分辨率

控件颜色来自共享的 token 集,角色如 surfacecontrolprimarytrack; switchTheme(world, tokens) 实时重刷屏上一切,项目在 Project Settings → UI 一次声明调色板。见 UI 主题

同一批控件也作为编辑器预制体提供,Create… → UI 即可把现成的 Button、Toggle、 Slider、Dialog、TextInput 等丢进场景,再在 Details 里编辑。活在组件里的行为—— 滑块的拖拽/按键、对话框的 Escape/幕布关闭——在摆放的预制体上无需代码即可工作。

从调色板拖放会嵌套:落点会认指针下的布局容器为父级(拖拽时虚线框预览将要成为的 父级),空画布落点则照旧落在根上。锚点选择器按轴编辑并保持控件原位—— 改水平锚点绝不扰动垂直布局,反之亦然。见编辑器

  • 优先状态组件而不是攥着句柄。 UIToggle.isOnUISlider.valueUIDropdown.selectedIndex 是唯一真相——写它们(或绑定它们),视觉自动跟上; 句柄方法只是同一写入的带类型语法糖。
  • 让主题拥有颜色。 省略 states / 视觉即可获得随主题热切换重新解析的角色色; 只为刻意脱离主题的美术硬编码颜色。
  • 对话框内容放在 panelEntity 之下,绝不放在旁边——这才让开/关成为整棵子树的 单次 display 写入。
  • 运行时创建的句柄要 dispose()——它销毁控件实体并解除事件订阅。
  • 保持键盘可达。 只为装饰性或重复的控件传 focusable: false; Tab 环就是你的无障碍故事。
  • UI —— 组件模型:UINodeTextUIVisual 与交互。
  • 列表与滚动 —— createListViewcreateScrollView 与虚拟化。
  • UI 控制器 —— 共享“页”状态 + 声明式每页字段绑定。
  • UI 数据绑定 —— 信号与控件双向绑定。
  • UI 交互 —— 焦点、拖放、指针事件。
  • UI 主题 —— token、角色、主题热切换与项目主题。