API 与集成
1. 结构
Ui 持有根控件、当前焦点、鼠标捕获、弹出层、通知和跨线程任务队列。创建 Ui 的线程也是其输入和绘制线程;其他线程调用 ui.post(Runnable)。
Widget 是保留式控件基类。add / remove / clear 维护父子关系,阻止祖先循环;find(id) 可以在树中查找控件。bounds(x,y,w,h) 是父级局部逻辑坐标,absoluteX/absoluteY 在布局后提供绝对位置。支持 visible、enabled、tooltip、onClick、onChange 和 context menu。
Panel 提供 ABSOLUTE / ROW / COLUMN / GRID 四种布局。COLUMN 使用容器可用宽度与各子项当前高度;ROW 使用子项当前宽度和容器可用高度;GRID 按列数等宽分列,每行取子项最大高度。不是完整的 flexbox、约束或自动文本排版系统。
Painter 是绘图接口。NativePainter 收集命令,NativeBackend 管理当前 OpenGL 实现。控件只使用 Painter,不自己创建 OpenGL context。
2. 事件
点击监听:
button.onClick(widget -> {
ui.notify("完成", "收到按钮事件");
});
状态监听:
checkBox.onChange(widget -> {
boolean selected = checkBox.selected();
});
不同控件提供更精确接口:ListBox.onSelection、Table.onEdit、PropertyGrid.onPropertyChange、FilePicker.onSelect、TextField.onSubmit。查看类源码或 examples 中完整用法。
UiEvent 包含 MOVE、DOWN、UP、WHEEL、KEY、TEXT、BLUR。传入鼠标坐标、button、clicks、wheel、modifiers、key 或 text。UiEvent.key(code, modifiers) 与 UiEvent.text(string) 提供工厂。Java AWT 主机负责从原生事件转换;已有宿主可以自行转换。
键盘码和 modifier 使用 Java KeyEvent/InputEvent 的数值约定。坐标必须已经转换为与 UI 一致的逻辑单位。文本应从系统文字输入/IME 提交事件输入,而不是单纯把按键码转成字符。
ui.input() 需要在 Ui 线程调用。输入可以在渲染前处理,窗口主机会先消费事件队列,再绘制。鼠标按下后的 move / up 交给捕获控件,因此拖动离开控件边界也能继续更新或正常取消。
3. 焦点、弹出层与模态
ui.focus(widget) 设置焦点;Tab / Shift+Tab 依照可见启用的树顺序遍历。ScrollPane 会尝试滚动到新焦点位置。隐藏/禁用祖先下的控件不参与通常的键盘导航。
ui.popup(content,x,y) 管理边缘收敛的弹出层;popupKeepFocus 用于输入建议。点击普通 popup 外侧会关闭它,该次点击被消费。Dialog 是非模态窗口,Modal 阻止底层输入;Esc 关闭最上层,关闭后恢复打开前焦点。Modal 的 Tab 范围限制在最上层。
ui.notify(title,message) 创建自动消失的通知,不抢焦点,同时最多保留四条。Widget.tooltip 是延迟悬停提示,不是可编辑窗口。
下拉菜单、右键菜单、上下文菜单共用 PopupMenu 的行为代码;这些是带语义名称的控件类型,不是重复实现三个菜单引擎。子菜单目前用新菜单替换父菜单,不提供桌面操作系统那样同时展开的多列级联。
4. 文本
TextField / TextArea 使用字符串、caret 和 selection;支持点击定位、双击选词、拖动选择、方向键、Home/End、Ctrl+A/C/X/V/Z/Y 等。删除按 Unicode codepoint 边界处理,选区索引仍采用 Java UTF-16。
TextArea 支持硬换行以及水平/垂直滚动,不自动软换行。不同平台的键盘修饰语、复杂连字、双向文本及组合光标不是本版完整覆盖范围。
系统窗口桥接包含 IME committed/preedit 处理与候选位置接口;普通中文字符串输入已测试,但没有在 Windows 原生中文输入法中进行端到端实机测试。候选位置是编辑控件附近的估计,不是全功能排版引擎的精确 caret 布局。
PasswordField 显示掩码并阻止复制/剪切密码文本,但模型仍是 Java String,不能当作安全擦除的密码存储容器。不要在真实应用日志中打印其 value。
5. 自定义绘制
继承 Widget,覆盖 paint(Painter)、layoutChildren()、event(UiEvent) 或 activate()。paint 中的 ax/ay 是已计算的绝对坐标。theme()、ink()、surface() 等帮助复用样式。
Painter 提供:glass、fill、stroke、line、ring、colorPlane、text、textWidth、clip、backdrop。色值使用 ARGB 整数。radius、坐标、字号使用逻辑 UI 单位;ring 的 progress 为 0~1,start 以圈为单位。
clip(Rect) 设置当前绝对矩形裁剪;控件树会在各节点绘制时恢复适用裁剪。自定义控件内部改变裁剪后应恢复,示例与内置 Table 就采用这个方式。不是任意路径裁剪 API。2.4.1 的 pushShapeClip / popShapeClip 另提供嵌套圆角/贝塞尔轮廓裁剪,见 裁剪修复说明。
详见 examples/CustomControlExample.java 的完整仪表控件。
6. 宿主渲染调用
NativeBackend backend = NativeBackend.attachCurrentContext();
NativePainter painter = new NativePainter(backend);
Ui ui = new Ui();
// 每帧,宿主场景已经画好:
painter.begin(logicalWidth, logicalHeight, pixelWidth, pixelHeight);
ui.render(painter, logicalWidth, logicalHeight, elapsedSeconds);
painter.flush(framebufferId, colorAttachment,
pixelWidth, pixelHeight,
ui.theme().sigma, mouseX, mouseY);
framebufferId=0、colorAttachment=0x0405 指双缓冲窗口 GL_BACK;宿主 FBO 通常是 framebufferId>0、colorAttachment=0x8CE0(GL_COLOR_ATTACHMENT0)。传的是 framebuffer ID,不是 texture ID。
逻辑画布对应整个 framebuffer;不带 viewport x/y 子区域。framebuffer 宽高是实际物理像素,例如逻辑 800×600 对应物理 1600×1200。UI/鼠标/边框尺寸使用逻辑单位,Gaussian sigma 使用物理像素。
最小化产生 0 大小时跳过绘制。调用前 context 必须保持在同一条线程。库不会在 attach 模式下 SwapBuffers 或销毁宿主 context。
painter.close() 先释放字形图集;backend.close() 再释放 shader/纹理/FBO 等,之后宿主才能销毁 context。二者关闭在原渲染线程完成。
7. 状态保护和范围
一次 flush 会保存并恢复修改过的 program、VAO、FBO 绑定与目标读写路由、viewport、scissor、polygon mode、颜色写入掩码、blend、纹理单元 0/1、sampler、active texture、pixel-unpack buffer,以及涉及的测试和裁剪开关。
支持 indexed blend 时只处理 draw buffer 0;无该扩展时用 OpenGL 3.3 的全局 blend 状态。文本图集上传另保存 pixel unpack 参数。
库不会接管宿主当前活动的 transform feedback、GPU query、条件渲染等外部过程。它应在普通的场景后 UI 阶段调用。宿主 stencil/depth 会暂时停用再恢复,不参与库内二维 UI 合成;库内裁剪采用矩形 scissor 与 SDF。
NativeBackend.checkError() 可用于测试或调试;它读取 glGetError 队列,不宜将其误认为只会报告本库的错误。示例程序会每帧调用以验证输出。
8. 参数和资源限制
Theme 是当前 Ui 的全局主题:ARGB 颜色、radius、fontSize、blur、sigma、refraction、shine 等可在渲染线程修改。不是完整的 CSS / selector / 每控件样式级联系统。
生产绘制命令缓冲上限为 65,536 条/帧。普通图元、玻璃和每个字形目前各发起一次 GL draw,虽然通过单次 JNI 提交一组命令,但这不等于整个 UI 只有一次 GPU draw 或已经实现 instancing。
字形图集采用 2048² 页,最多 12 页、18,000 个字符/字号/路径组合;超过上限明确抛出异常,没有隐藏的无限缓存。字体使用系统字体,缺失符号尝试系统 Dialog 字体回退。不支持完整复杂文字 shaping。
模糊中间结果按尺寸复用。每一背景层先进行一次场景复制;启用模糊时,再一次降采样和两次模糊 draw。增加显式 backdrop barrier 会增加对应成本。性能取决于实际显卡、分辨率和控件/文字数量;本次没有硬件显卡帧率承诺。
2.1 材质与动效
Theme.tint/hoverTint/pressTint/selectedTint/surfaceOpacity/dispersion 直接控制材质及状态增量;Theme.reducedMotion 控制状态过渡。Widget.appear() 触发内容切入;Label.weight(int) 选择400/500/600层级字重。Painter 增加的文本缩放/字重和颜色面板 alpha 方法提供 default 实现,以保留已有 Java 自定义 Painter 的源兼容。详情见 材质与动效 和 字体。
2.2 圆角与拖动补充
Theme.panelRadius 独立于普通控件的 radius。默认深色/浅色主题均为黑白灰;宿主仍可通过主题字段自定义配色。
Slider.continuous() 关闭指针量化,是默认行为;调用 step(n) 后才对指针取值离散化。键盘步进保持可用。RangeSlider 默认支持选中区间整体平移。
Panel.draggable(true) 允许从整个空白及普通标签区域拖动,不抢占交互子控件。Widget.smoothPosition(true) 用连续位置跟随绘制及命中,beginPositionDrag() 从当前显示位置接管拖动。DragBar 自动应用这两项行为。
SplitPane.ratio 的允许范围现为 0..1;调用方如需最小面板尺寸,可在自己的布局约束中收紧比例。新默认材质和算法说明见 材质与动效。
2.3 横向微调器与间距
参见 STEPPER-SPACING.md。Spinner 仍继承 NumberField,默认不画独立底板。Panel 的默认内容留白为 16、同级 gap 为 12;显式的 padding()/gap() 和绝对 bounds() 不受强制覆盖。
2.4 文本、AA、四角与配色
Ui.setTheme(Theme) 为现有 theme setter 的链式别名。Ui.antialiasing(Antialiasing) 全局设置 OFF / ANALYTIC / HIGH。Theme.textMode(TextMode) 选择 AUTO / COVERAGE / SDF;pixelSnap、textContrast、letterSpacing、fonts 管理字体。新增 begin(width,height,pixelWidth,pixelHeight) 在生成字形前提供实际 DPI。
Widget.radius(tl,tr,br,bl)、corner(Corner,BezierCorner)、corners(CornerRadii) 提供四角配置。Painter.configure(Theme) 和 corners(CornerRadii) 为新增 default 扩展点,内置 NativePainter 已实现。
主题新增背景、高光、边缘阴影和链式颜色配置。具体单位、默认值、GPU 数值距离求解与真实 SDF 文本缓存见 抗锯齿、字体、四角曲线 和 配色。
内置 native 指令协议版本为 24064,每条指令 64 个 float。它是内部二进制格式,不是稳定的第三方 ABI;不要将旧 DLL 与新 Java 拆开混装。
2.4.1:间距与嵌套圆角裁剪修复
本包实际修复了 2.4 中的列表左右不对称、无滚动条仍保留空槽、侧边栏重复底板,以及子内容仅按矩形剪裁的问题。它不是只调整截图,也不是在界面上新增一层着色遮罩。
Sidebar 默认只绘制选中项的玻璃,不再绘制额外的整块底板。ListBox 仍可以作为独立列表使用;需要嵌入现有玻璃容器时可调用 glass(false)。列表绘制、命中、滚动边界共用同一套 padding 和行范围;隐藏滚动条时归还宽度。
Panel 默认启用子内容裁剪,轮廓与自身表面的四角贝塞尔/圆角参数完全相同。填充、玻璃、文字、圆环、线段和颜色平面均参与 GPU 裁剪。多个父级轮廓取交集;动画中的轮廓也使用同一变换。父级 viewport 不再随子按钮按压被错误缩小。控件自身的边缘反光不会作为“内容”再裁一次。
旧 API 和两个 Java 目标保留。新增绘制接口、兼容性及限制见 裁剪修复说明。替换版本时使用本包完整的库/示例 JAR;Java、GLSL 和 native 协议已一起更新,不能混入旧 DLL 或旧 shader。
2.4.2 实时材质与背景设置
Theme.sigma/blur/refraction/shine/dispersion/opacity 新增 fluent setter,同时保留原有字段。sigma=0..32 物理像素,blur=0..1,refraction=0..30 屏幕空间强度(不是物理 IOR),shine=0..1.5,dispersion=0..2,opacity=0..1。
ui.background().grid(boolean).animated(boolean) 控制独立窗口的真实背景;暂停保留当前动画时间。背景状态与 Theme 分开,替换主题不必重置它。宿主 attach 模式仍由宿主自己绘制背景。
GlassSettingsPanel 绑定附着 Ui 的实时模型,preferredHeight(width) 支持双列/单列布局;GlassSettingsDialog 提供不抢占整个界面的非模态浮窗。它们进入 library JAR,不仅存在于 demo 中。完整示例和语义见 实时材质设置。
2.4.3 高光和色散
原有公开 API 和 native 协议不变。默认 Theme.shine=.82f、Theme.dispersion=.85f。shine(float) 接受 0..1.5,dispersion(float) 接受 0..2;后者现在按实际单通道逻辑像素偏移采样,不再偷偷乘 .35。GUI 中分别标为“边缘高光 · 强度”和“RGB 色散 · px”。
高光和色散可独立关闭。所有预设都保留非零色散,“低反光”与“恢复默认”不再指向同一动作。sigma(18).blur(.98f) 仍是默认强模糊,不需要关闭模糊才能让色散生效。详情及物理近似的边界见 高光与色散修复。
2.4.4 可复用场景组合
新增 polaris.sdk.ui.samples 包;它在 library JAR 中,不依赖展示程序的 Gallery。SceneExamples 提供六场景选择、缓存和状态保留;FoodScene / ChatScene / ClientScene / MusicScene / SettingsScene / FilesScene 本身就是 Panel,可加入已有 Widget 树。
SampleWindows 是可选的独立示例窗口便利入口。已有渲染器集成时不调用它,直接加入对应场景控件即可。组合场景不增加新的 native 命令,不修改 Painter API 或材质参数。
详细接口与边界见 场景示例。
2.4.5 命名材质模板
MaterialPreset.MORDEN、MaterialPreset.LIQUID_GLASS、MaterialPreset.NORMAL 是不可变预设。applyTo(Theme) 只写八项材质并返回同一个 Theme;在当前 UI 线程调用。GlassSettingsPanel.applyPreset(MaterialPreset) 还立即同步滑杆、横向数值编辑器及模糊开关,覆盖未提交的数字编辑。
模板名和具体数值见 材质模板。默认材质与原预设没有替换;六个场景仍使用同一全局设置。