imgui 运行时面板
imgui 运行时面板
imgui 是 JSXHook 里最像“运行时调试面板框架”的一层。它不是单个控件 API,而是一整套窗口 builder、状态容器、布局指令和控件回调系统。
如果你想直接翻应用内置手册里的 ImGui 示例,可以看这里:[内置手册全量示例 - ImGui 调试面板](/reference/manual-full.html imgui-调试面板)。
如果你只记一件事,先记这个:
imgui.window(id, options?)会拿到一个窗口 builder。同一个
id会复用同一份窗口模型,包含配置、控件状态和已添加的内容。builder.clear()只清空内容,不清空状态。builder.close()只是隐藏窗口,不会自动重置状态。想稳定地
set/get/state,尽量给交互控件显式传key。顶层 API
| API | 返回 | 说明 |
|---|---|---|
imgui.window(id, options?) | builder | 创建或获取一个窗口 builder。 |
imgui.close(id) | boolean | 关闭指定窗口。 |
imgui.closeAll() | number | 关闭全部窗口。 |
imgui.isShown(id) | boolean | 判断指定窗口当前是否显示。 |
imgui.themes() | string[] | 返回内置主题列表。 |
最小例子
imgui
.window("debug")
.title("JSXHook Debug")
.size(360, 520)
.text(`pkg=${lpparam.packageName}`)
.button("Dump Activity", state => {
printFields(activity, "\n");
})
.show();
内置主题
当前源码里的主题列表是:
[
"modern_dark",
"vibrant_night",
"crystal_clear",
"cyan_dusk",
"orange_dark",
"purple_dream",
"blue",
"pink",
"imgui_dark",
"imgui_light",
"imgui_classic",
"rose_prism",
"rose_glass",
"sakura_night",
"pearl_mist",
"petal_dawn",
"velvet_rose",
"crystal_rose",
"midnight_petal",
"milk_tea_rose"
]
可以直接这样看:
log(JSON.stringify(imgui.themes()));
imgui.close(id)
关闭指定 id 对应的窗口,成功关闭时通常返回 true。
imgui.close("debug");
适合什么时候用:
- 你在别的按钮回调里主动关某个面板
- 你做了“主面板 + 子面板”这种联动
- 你想通过固定
id精确关闭某一块 UI
imgui.closeAll()
一次性关闭当前已显示的全部 ImGui 窗口,返回值通常是本次关闭的窗口数量。
const closedCount = imgui.closeAll();
log(`closed=${closedCount}`);
适合什么时候用:
- 你在脚本结束前做统一清理
- 你准备整套面板重新绘制
- 你想做“全部关闭”按钮
imgui.isShown(id)
按窗口 id 判断某个面板当前是否正在显示。
if (!imgui.isShown("debug")) {
imgui.window("debug").text("hello").show();
}
这个方法和 builder.isShown() 的区别是:
imgui.isShown(id)是全局按id查builder.isShown()是对当前 builder 对应窗口做判断
imgui.themes()
返回当前内置主题名列表。
const themes = imgui.themes();
log(JSON.stringify(themes, null, 2));
最常见用途:
先把所有可用主题打印出来
给
combo/listBox做主题选择器在
imgui.window(id, options?)或builder.theme(name)前先确认名字写对先理解两个核心概念
- 窗口
id是窗口实例的名字
- 窗口
imgui.window("debug") 的 "debug" 不只是标题标识,它还决定:
- 你拿到的是不是同一个窗口实例
- 当前状态是不是复用旧值
imgui.close("debug")操作的是哪一个窗口
只要 id 不变,窗口模型就会被复用。
2. 交互控件的 `key` 是状态槽位名字
绝大多数交互控件都支持两种写法:
builder.checkbox("Enable", true);
builder.checkbox("Enable", "enable_flag", true);
第一种会自动生成内部 key。
第二种会显式把状态绑到 "enable_flag" 上,后面你就能稳定地:
builder.set("enable_flag", false);
const enabled = builder.get("enable_flag", true);
const allState = builder.state();
结论很简单:只要你准备读写状态,就给控件显式 key。
imgui.window(id, options?)
基本用法
const panel = imgui.window("network");
带初始 options
const panel = imgui.window("network", {
title: "Network Panel",
width: 380,
height: 560,
x: 30,
y: 120,
theme: "rose_glass",
windowCollapsible: true,
resizable: true
});
`options` 支持的字段
这一段我尽量按“第一次写就能填对”的标准写死。
布尔字段通用规则
imgui 这一层的布尔字段,不只是严格认 JS true/false,源码当前还会接受:
| 你传的值 | 实际布尔结果 |
|---|---|
true | true |
false | false |
1 | true |
0 | false |
"true" | true |
"1" | true |
| 其他字符串 | 一般按默认值处理 |
所以最稳妥的写法还是老老实实传 true / false。
窗口 options 总表
| 字段 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
title | string | 任意文本 | 窗口 id | 初始标题 |
width / w | number | 整数 | 约 332dp 换算后的运行时像素值 | 初始宽度 |
height / h | number | 整数 | 约 500dp 换算后的运行时像素值 | 初始高度 |
minWidth | number | 整数 | 280 | 最小宽度 |
minHeight | number | 整数 | 180 | 最小高度 |
maxWidth | number | 整数,0 或正数 | 0 | 0 表示不限制最大宽度 |
maxHeight | number | 整数,0 或正数 | 0 | 0 表示不限制最大高度 |
minSize | number[] | [width, height] | 无 | 同时设置最小宽高 |
maxSize | number[] | [width, height] | 无 | 同时设置最大宽高 |
x / posX | number | 整数 | 约 20dp 换算后的运行时像素值 | 初始 X 坐标 |
y / posY | number | 整数 | 约 100dp 换算后的运行时像素值 | 初始 Y 坐标 |
alpha | number | 浮点数 | 1.0 | 最终会被限制在 0.2 ~ 1.0 |
theme | string | imgui.themes() 返回值之一 | rose_prism | 主题名,传空字符串会回到默认主题 |
fontScale | number | 浮点数 | 1.40 | 最终限制在 1.0 ~ 3.2 |
uiScale / widgetScale | number | 浮点数 | 1.14 | 最终限制在 0.85 ~ 2.4 |
scale / zoom | number | 浮点数 | 无 | 同时设置 fontScale + uiScale |
closable | boolean | 见上面的布尔规则 | true | 是否允许关闭 |
windowCollapsible / collapsibleWindow | boolean | 见上面的布尔规则 | false | 是否允许标题栏折叠 |
resizable | boolean | 见上面的布尔规则 | true | 是否允许调整大小 |
displayMode | string | 推荐填 in_app / system_overlay / accessibility_overlay,也兼容多组别名,见下方“显示模式完整说明” | in_app | 控制窗口显示模式。它不只是“显示在哪”,还会影响窗口宿主、权限要求和触摸行为 |
outsideApp / showOutsideApp / systemOverlay | boolean | 见上面的布尔规则 | false | true 时切到系统悬浮层,false 时回到应用内 |
accessibilityOverlay / a11yOverlay | boolean | 见上面的布尔规则 | false | true 时切到无障碍悬浮层,false 时回到应用内 |
secure / antiCapture | boolean | 见上面的布尔规则 | false | 安全窗口,防截屏 |
touchPassthrough / passthrough / clickThrough / touchThrough | boolean | 见上面的布尔规则 | false | 触摸穿透 |
onClose | function | () => {} | 无 | 关闭回调 |
一个更完整的初始化例子
const panel = imgui.window("network", {
title: "Network Panel",
width: 380,
height: 560,
minWidth: 320,
minHeight: 220,
maxWidth: 900,
maxHeight: 0,
x: 30,
y: 120,
alpha: 0.96,
theme: "rose_glass",
fontScale: 1.5,
uiScale: 1.2,
windowCollapsible: true,
resizable: true,
displayMode: "in_app"
});
窗口 builder 的生命周期
builder.show()
真正把窗口显示出来。
imgui
.window("debug")
.text("hello")
.show();
builder.close()
关闭当前窗口视图,但不自动清状态。
const panel = imgui.window("debug");
panel.close();
builder.isShown()
判断当前 builder 对应的窗口是否显示。
if (!panel.isShown()) {
panel.show();
}
builder.clear()
只清空当前窗口里已经添加的内容,不清空这个窗口 id 对应的状态槽位。
const panel = imgui.window("debug");
panel
.text("old content")
.show();
panel.clear();
panel.text("new content").show();
这一点一定要记住:
clear()清的是内容set/get/state绑定的状态不会因为clear()自动消失
所以它特别适合“保留状态,重画布局”的场景。
状态复用规则
这个地方非常重要:
- 同一运行时里,同一个窗口
id的状态会复用。 clear()只清内容,不清状态。close()只隐藏,不清状态。- 控件的
defaultValue只在第一次创建该 key 时生效。
例如:
const panel = imgui.window("demo");
panel.checkbox("Enable", "enabled", true);
panel.show();
panel.set("enabled", false);
panel.close();
// 再次拿到同一个窗口
const panel2 = imgui.window("demo");
log(panel2.get("enabled", true)); // 仍然是 false
如果你想“重新搭 UI 但保留状态”,用 clear()。
如果你想“整个状态空间都换掉”,最简单的方法是换一个新的窗口 id。
窗口配置 API
标题、尺寸、位置
| API | 说明 |
|---|---|
title(text) | 设置标题 |
size(width, height) | 设置宽高 |
resize(width, height) | size() 别名 |
resizeTo(width, height) | size() 别名 |
width(value) / w(value) | 设置宽度 |
height(value) / h(value) | 设置高度 |
minSize(width, height) | 设置最小尺寸 |
maxSize(width, height) | 设置最大尺寸 |
minWidth(value) / minHeight(value) | 单独设置最小尺寸 |
maxWidth(value) / maxHeight(value) | 单独设置最大尺寸 |
position(x, y) / pos(x, y) / moveTo(x, y) | 设置位置 |
x(value) / y(value) | 单独设置坐标 |
例子:
imgui
.window("layout")
.title("Layout")
.size(420, 600)
.minSize(320, 220)
.maxSize(900, 0)
.position(24, 100)
.show();
透明度与缩放
| API | 说明 |
|---|---|
alpha(value) | 透明度,内部会限制在 0.2 ~ 1.0 |
fontScale(value) | 字体缩放,内部限制 1.0 ~ 3.2 |
uiScale(value) | UI 缩放,内部限制 0.85 ~ 2.4 |
widgetScale(value) | uiScale() 别名 |
scale(value) / zoom(value) | 同时设置字体和 UI 缩放 |
imgui
.window("scale-demo")
.alpha(0.92)
.fontScale(1.5)
.uiScale(1.2)
.show();
主题与关闭行为
| API | 说明 |
|---|---|
theme(name) | 设置主题 |
closable(flag) | 是否允许关闭 |
windowCollapsible(flag) | 是否启用标题栏折叠 |
collapsibleWindow(flag) | windowCollapsible() 别名 |
resizable(flag) | 是否允许缩放窗口 |
onClose(fn) | 注册关闭回调 |
imgui
.window("theme-demo")
.theme("rose_glass")
.windowCollapsible(true)
.onClose(() => {
log("panel closed");
})
.show();
显示模式
displayMode 不是一个随便填的字符串。当前源码现在只认 3 大类模式,而且每一类都有别名。
先记一个最实用的结论:
in_app:应用内显示,默认模式system_overlay:系统悬浮窗显示accessibility_overlay:无障碍悬浮层显示推荐写法与完整别名
如果你只是正常写业务脚本,优先用下面左边这 3 个标准值。右边这些别名主要是源码兼容输入时认得,但不建议你在文档、示例和正式脚本里混着写。
| 推荐写法 | 内部模式名 | 还能识别的别名 |
|---|---|---|
"in_app" | IN_APP | "app", "inapp", "inside", "inside_app", "within_app" |
"system_overlay" | SYSTEM_OVERLAY | "overlay", "system", "application_overlay", "outside", "outside_app", "global" |
"accessibility_overlay" | ACCESSIBILITY_OVERLAY | "accessibility", "a11y", "a11y_overlay", "service_overlay" |
三种模式到底差在哪
| 模式 | 窗口挂载位置 | 额外条件 | 最适合的场景 |
|---|---|---|---|
in_app | 当前 Activity 窗口 | 需要当前有可用 Activity / window token | 当前应用内调试,最稳 |
system_overlay | 系统悬浮窗 | 需要悬浮窗权限 | 想跨页面悬浮显示 |
accessibility_overlay | 无障碍悬浮层 | 需要无障碍服务和对应宿主准备好 | 需要更可信的覆盖层、无障碍配合的场景 |
可以把它们理解成:
in_app:最普通,最省事,跟着当前应用窗口走。system_overlay:视觉上像一个系统悬浮窗,适合“切页面也想挂着”。accessibility_overlay:本质上是借助无障碍宿主去托管面板,更偏“可信覆盖宿主”。
如果你并没有很明确的需求,优先顺序通常可以这么选:
先试
in_app需要跨页面悬浮,再试
system_overlay需要无障碍宿主能力,再试
accessibility_overlay默认值
如果你完全不写 displayMode,默认就是:
in_app
写错会怎样
这里跟很多 API 不一样,源码当前不会因为写错 displayMode 字符串而报错。
规则是:
builder.displayMode("未知值"):保留当前模式imgui.window(id, { displayMode: "未知值" }):回退到当前 / 默认模式,通常是in_app
也就是说,下面这种写法不会抛异常,但也不会按你想的工作:
panel.displayMode("fullscreen_overlay");
显示模式速查表
| 你想要的效果 | 推荐写法 | 是否需要额外条件 |
|---|---|---|
| 应用内显示 | displayMode("in_app") | 不需要 |
| 系统悬浮窗显示 | displayMode("system_overlay") | 需要悬浮窗权限 |
| 无障碍悬浮层显示 | displayMode("accessibility_overlay") | 需要无障碍悬浮层服务 |
相关快捷方法:
| API | 作用 |
|---|---|
displayMode(mode) | 显式指定显示模式,适合要写死具体值时用 |
outsideApp(flag) | true 时切到系统悬浮层,false 时回到应用内 |
showOutsideApp(flag) | 同上 |
systemOverlay(flag) | 同上 |
accessibilityOverlay(flag) | true 时切到无障碍悬浮层,false 时回到应用内 |
a11yOverlay(flag) | 同上 |
注意这里的“回到应用内”很关键:
systemOverlay(false)不是“恢复上一个模式”,而是直接切回in_appaccessibilityOverlay(false)也一样,直接切回in_app
如果你想从无障碍悬浮层直接切到系统悬浮层,最明确的写法是:
panel.displayMode("system_overlay");
imgui
.window("overlay")
.displayMode("system_overlay")
.show();
相关快捷写法示例
imgui.window("a").displayMode("in_app");
imgui.window("b").systemOverlay(true);
imgui.window("c").outsideApp(true);
imgui.window("d").accessibilityOverlay(true);
imgui.window("e").a11yOverlay(true);
权限与自动提升规则
这一段很重要,很多“为什么没显示出来”都卡在这里。
`in_app`
不需要悬浮窗权限
不需要无障碍服务
如果当前没有可用 Activity / window token,显示可能会延后重试
`system_overlay`需要系统悬浮窗权限
如果没有权限,显示会失败
`accessibility_overlay`需要无障碍悬浮层服务已经准备好
如果服务还在等待,会先跳过这次显示并重试
如果服务不可用,会直接失败
`system_overlay + touchPassthrough(true)` 的特殊联动
这个组合不是普通系统悬浮层就能稳定做的。
当前源码逻辑是:
- 你请求的是
system_overlay - 同时又打开了
touchPassthrough - 运行时会把它当成“需要 trusted accessibility overlay”
- 如果无障碍服务准备好了,实际显示模式会提升到
ACCESSIBILITY_OVERLAY - 如果服务没准备好,显示会等待或失败
所以你看到:
imgui
.window("overlay-tools")
.displayMode("system_overlay")
.touchPassthrough(true)
.show();
不能简单理解成“只是系统悬浮窗 + 穿透”,它实际上会进入更严格的无障碍覆盖逻辑。
这里再补一句很实用的理解:
- 你请求的是
system_overlay - 但为了让穿透和可信触摸链路成立,交互宿主会被提升到无障碍 overlay
- 只要系统悬浮窗权限还在,渲染宿主仍然可能保持系统 overlay
所以“面板看起来像系统悬浮窗”不代表它内部交互链路还是纯 system_overlay。
当前实现还有一个运行时限制
当前源码不允许不同 displayMode 的面板同时混开。
也就是说,如果已经有一个 system_overlay 面板在显示,这时你再去开一个 accessibility_overlay 面板,运行时会直接拒绝,直到前面的面板被关闭。写多面板脚本时最好统一模式,不要一半 in_app 一半 accessibility_overlay 混着挂。
什么时候该选哪一种
| 场景 | 推荐模式 |
|---|---|
| 当前应用内调试面板 | in_app |
| 想在应用外也挂着一个面板 | system_overlay |
| 明确需要可信覆盖 / 某些触摸穿透能力 | accessibility_overlay |
安全与触摸穿透
| API | 说明 |
|---|---|
secure(flag) | 安全窗口,常用于防截屏 |
antiCapture(flag) | secure() 别名 |
touchPassthrough(flag) | 触摸穿透 |
passthrough(flag) | 别名 |
clickThrough(flag) | 别名 |
touchThrough(flag) | 别名 |
imgui
.window("overlay-tools")
.systemOverlay(true)
.secure(true)
.touchPassthrough(false)
.show();
内容与布局 API
基础文本
| API | 说明 |
|---|---|
text(text) | 普通文本 |
textWrapped(text) / wrappedText(text) | 自动换行文本 |
textColored(text, color) / coloredText(text, color) | 彩色文本 |
bullet(text) | 项目符号文本 |
textDisabled(text) / disabledText(text) | 灰态文本 |
separator() | 分隔线 |
separatorText(text) | 带标题分隔线 |
panel
.text("Normal")
.textWrapped("This is a long line that should wrap automatically.")
.textColored("Warning", " FFFF8844")
.bullet("Item A")
.separatorText("Section");
空间与游标
| API | 说明 |
|---|---|
spacing(count?) | 增加空白行 |
newLine() | 换行 |
sameLine(spacing?) | 同行继续排 |
sameLine(offsetX, spacing) | 指定起始偏移和间距 |
dummy(width, height) | 占位块 |
indent(width?) | 增加缩进 |
unindent(width?) | 减少缩进 |
cursorPos(x, y) | 设置游标位置 |
cursorX(x) / cursorY(y) | 单独设置游标轴 |
itemWidth(width) | 设置后续控件宽度 |
panel
.text("A")
.sameLine(12)
.text("B")
.spacing(2)
.itemWidth(180)
.inputText("Name", "name", "jsxhook");
容器与结构
group
panel.group(ui => {
ui.text("Line 1");
ui.text("Line 2");
});
对应 API:
beginGroup()endGroup()group(fn)
disabled
panel.disabled(true, ui => {
ui.button("Disabled button");
ui.inputText("Readonly-like", "x");
});
对应 API:
beginDisabled(flag?)endDisabled()disabled(flag, fn?)
child
适合做一个独立滚动区域或子内容块。
panel.child("logs", {
width: 0,
height: 220,
border: true
}, ui => {
ui.textWrapped("Long content...");
});
对应 API:
beginChild(id, width?, height?, options?)endChild()child(id, width?, height?, options?, fn)
options 关键字段:
labelwidthheightborderbuildcollapsible/tree/treeNode
这几组底层都走同一个折叠容器实现,只是命名不同。
panel.collapsible("HTTP Tools", {
defaultOpen: true
}, ui => {
ui.button("Ping");
ui.button("POST");
});
等价族:
beginCollapsible()/endCollapsible()/collapsible()beginTree()/endTree()/tree()beginTreeNode()/endTreeNode()/treeNode()collapsingHeader()
常用字段:
labeldefaultOpenbuild
columns
panel.columns("cols", 2, { border: true }, ui => {
ui.text("Left");
ui.nextColumn();
ui.text("Right");
});
对应 API:
beginColumns(id?, count, options?)nextColumn()endColumns()columns(id?, count, options?, fn)
table
panel.table("users", ["Name", "Role"], {
showHeaders: true,
border: true,
rowBg: true,
resizable: true,
stretch: true
}, ui => {
ui.text("Alice");
ui.nextRow();
ui.text("Admin");
});
对应 API:
beginTable(id, headersOrCount, options?)nextRow()tableNextRow()endTable()table(id, headersOrCount, options?, fn)
options 常用字段:
headerscolumnCount/countshowHeadersheadersRowborderrowBgresizablestretchbuild
tabs
tabs() 比较特别:最外层回调拿到的是一个只负责建 tab 的对象,真正的窗口 builder 会在每个 tab(...) 回调里传回来。
panel.tabs(tabs => {
tabs.tab("General", ui => {
ui.text("General page");
});
tabs.tab("Network", ui => {
ui.text("Network page");
});
});
对应 API:
beginTabBar(id?)endTabBar()beginTab(label, options?)endTab()tabs(id?, fn)交互控件
手册式速查
这一段专门按“这个控件到底存什么值、怎么读、怎么改”的角度来写,方便查手册时快速定位。
`button` / `smallButton`
- 作用:触发动作。
- 回调:
(state) => {} - 状态行为:按钮自己不会写入“是否按下”的持久状态。
- 适合做的事:点击后去改别的控件值,或者执行脚本逻辑。
常见写法:
panel.button("Ping", state => {});
panel.button("Ping", "ping_btn", 120, 40, state => {});
panel.smallButton("Refresh", state => {});
最常见的按钮用法不是 get(buttonKey),而是:
panel.button("Use Test Env", state => {
panel.set("base_url", "https://test.example.com");
panel.set("enable_hook", true);
});
`checkbox` / `toggle` / `switch` / `selectable`
- 作用:保存一个布尔值。
- 回调:
(value, state) => {} - 写进
state的值类型:boolean
获取当前值:
const enabled = panel.get("enable_hook", false);
主动修改:
panel.set("enable_hook", true);
panel.set("enable_hook", !panel.get("enable_hook", false));
说明:
toggle/switch本质也是布尔开关。selectable保存的是“这一项是否被选中”,不是索引。
inputText
- 作用:保存一个字符串。
- 回调:
onChange(text, state)、onSubmit(text, state) - 写进
state的值类型:string
获取当前值:
const host = panel.get("host_input", "127.0.0.1");
主动修改:
panel.set("host_input", "https://example.com");
panel.set("host_input", `${panel.get("host_input", "")}/v1`);
常见场景:
password: true做密码输入multiline: true做多行文本readOnly: true做只读展示submitEnter: true配合onSubmit做回车提交inputInt/inputFloat作用:保存数值输入结果。
回调:
(value, state) => {}inputInt写入整数,inputFloat写入浮点数。
获取当前值:
const retryCount = panel.get("retry_count", 3);
const alphaValue = panel.get("alpha_value", 0.8);
主动修改:
panel.set("retry_count", retryCount + 1);
panel.set("alpha_value", 0.95);
补充说明:
inputInt默认step=1、stepFast=100inputFloat如果需要明确步进,建议自己显式传step/stepFastsliderInt/sliderFloat/sliderAngle作用:保存范围受限的数值。
回调:
(value, state) => {}sliderInt写入整数。sliderFloat写入浮点数。sliderAngle写入角度值,本质仍然是浮点数。
获取和修改:
const level = panel.get("level_key", 0);
panel.set("level_key", level + 10);
const degree = panel.get("angle_key", 0);
panel.set("angle_key", degree + 15);
说明:
如果最小值和最大值写反了,源码会自动归一化。
sliderAngle可以额外用minDegrees/maxDegrees指定范围。dragInt/dragFloat作用:通过拖拽手势细调数值。
回调:
(value, state) => {}dragInt写入整数。dragFloat写入浮点数。
获取和修改:
panel.set("offset_x", 12);
panel.set("opacity_drag", 0.65);
说明:
dragInt默认speed=1dragFloat默认speed=0.01常用
options是minValue、maxValue、defaultValue、speed、onChangecombo/listBox/radioGroup作用:从一组选项里选一个。
回调:
(index, state) => {}写进
state的值类型:当前选中项索引,不是文本本身。
获取当前索引:
const processIndex = panel.get("process_index", 0);
const processName = ["main", "push", "tool"][processIndex];
主动切换选中项:
panel.set("process_index", 2);
说明:
listBox额外支持visibleItemsdefaultIndex会被限制在有效范围内如果你要拿到文案,记得用自己的数组
items[index]colorEdit/colorPicker作用:保存颜色值。
回调:
(hex, rgba, state) => {}写进
state的常见值类型:标准化后的 hex 字符串。
获取当前颜色:
const accent = panel.get("accent_color", " FFFFFFFF");
主动修改颜色:
panel.set("accent_color", " FF46BD87");
说明:
建议在
set()时优先传 hex 字符串。支持的输入格式包括
RRGGBB、AARRGGBB、0xRRGGBB、0xAARRGGBB、[r,g,b]、[r,g,b,a]数组支持
0~1和0~255两套范围progress/progressBar作用:显示进度。
状态行为:不适合作为可读写状态槽位。
数值范围:内部会限制在
0 ~ 1。
常见写法:
panel.progress(0.68, "68%");
panel.progressBar(0.9, "sync");
如果你想“修改它显示的值”,通常做法是下一轮按新数据重新构建,而不是依赖 set/get。
`plotLines` / `plotHistogram`
- 作用:显示折线 / 柱状数据。
- 状态行为:不写入稳定状态值。
- 输入要求:第二个参数必须是非空数值数组。
常见写法:
panel.plotLines("CPU", [0.1, 0.4, 0.2, 0.8, 0.5], { height: 96 });
panel.plotHistogram("FPS", [58, 60, 61, 57, 62], { height: 100 });
常用 options:
overlayminScalemaxScaleheight一条通用规则
大多数交互控件都推荐用这类写法:
panel.checkbox("Enable Hook", "enable_hook", true, (value, state) => {
log(value);
});
参数结构通常是:
label, key?, defaultValue?, options?, callback?
其中:
label是 UI 上显示的名字key是状态槽位名字defaultValue只在这个key第一次出现时生效callback拿到当前值和状态快照按钮
button
panel.button("Ping", "ping_btn", 140, 42, state => {
log(JSON.stringify(state));
});
支持:
button(label, onClick?)button(label, key, onClick?)button(label, key, width, height, onClick?)button(label, key, { width, height, onClick })
smallButton
panel.smallButton("Refresh", state => {
log("refresh");
});
它是紧凑按钮,不单独使用宽高参数。
布尔类控件
checkbox
panel.checkbox("Enable Hook", "enable_hook", true, (value, state) => {
log(`enable=${value}`);
});
`toggle` / `switch`
panel.toggle("Overlay", "overlay_enabled", false, (value, state) => {
log(`overlay=${value}`);
});
selectable
panel.selectable("Select Me", "selected_flag", false, (value, state) => {
log(`selected=${value}`);
});
这三组回调签名都是:
(value, state) => {}
其中 value 是 boolean。
文本输入
inputText
panel.inputText("Host", "host_input", "127.0.0.1", {
hint: "Input host",
submitEnter: true,
onChange(text, state) {
log(`change=${text}`);
},
onSubmit(text, state) {
log(`submit=${text}`);
}
});
常用字段:
hintdefaultValuepasswordmultilinedecimalreadOnlysubmitEnteronChangeonSubmit
注意点:
onChange(text, state)在文本变化时触发。onSubmit(text, state)只在提交时触发。multiline: true会开启多行输入。readOnly: true会禁止编辑。数字输入
inputInt
panel.inputInt("Retry Count", "retry_count", 3, {
step: 1,
stepFast: 10,
onChange(value, state) {
log(value);
}
});
inputFloat
panel.inputFloat("Alpha", "alpha_value", 0.8, {
step: 0.1,
stepFast: 0.5,
onChange(value, state) {
log(value);
}
});
滑块
sliderInt
panel.sliderInt("Level", "level_key", 0, 100, 20, (value, state) => {
log(value);
});
sliderFloat
panel.sliderFloat("Scale", "scale_key", 0.5, 2.0, 1.1, (value, state) => {
log(value);
});
sliderAngle
panel.sliderAngle("Rotate", "angle_key", -180, 180, 0, (value, state) => {
log(value);
});
sliderAngle 支持的 options 字段里还可以写:
minDegreesmaxDegreesdefaultValueonChange拖拽数值
dragInt
panel.dragInt("Offset X", "offset_x", -100, 100, 0, 1, (value, state) => {
log(value);
});
dragFloat
panel.dragFloat("Opacity", "opacity_drag", 0, 1, 0.7, 0.01, (value, state) => {
log(value);
});
常用 options:
minValuemaxValuedefaultValuespeedonChange选项类控件
combo
const items = ["main", "push", "tool"];
panel.combo("Process", "process_index", items, 0, (index, state) => {
log(`index=${index}`);
log(`label=${items[index]}`);
});
listBox
const langs = ["zh-CN", "en-US", "ja-JP", "ko-KR"];
panel.listBox("Language", "lang_index", langs, 0, {
visibleItems: 4,
onChange(index, state) {
log(langs[index]);
}
});
radioGroup
const modes = ["Off", "Lite", "Full"];
panel.radioGroup("Mode", "mode_index", modes, 1, (index, state) => {
log(modes[index]);
});
这三组的状态值都是“当前选中项的索引”,不是文本本身。
颜色控件
colorEdit
panel.colorEdit("Accent", "accent_color", " FF46BD87", {
alpha: true,
alphaBar: true,
inputs: true,
pickerHueWheel: true,
onChange(hex, rgba, state) {
log(hex);
log(JSON.stringify(Array.from(rgba)));
}
});
colorPicker
panel.colorPicker("Background", "bg_color", [255, 32, 32, 220], {
alpha: true,
onChange(hex, rgba, state) {
log(`hex=${hex}`);
}
});
颜色值支持的常见输入格式:
" RRGGBB"" AARRGGBB""0xRRGGBB""0xAARRGGBB"[r, g, b][r, g, b, a]
数组既支持 0~1 浮点,也支持 0~255 数值。
颜色回调签名是:
(hex, rgba, state) => {}
其中:
hex是标准化后的十六进制字符串rgba是四元素数组只读展示型控件
progress/progressBar
panel.progress(0.68, "68%");
panel.progressBar(0.9, "sync");
进度值会被限制在 0 ~ 1。
plotLines
panel.plotLines("CPU", [0.1, 0.4, 0.2, 0.8, 0.5], {
overlay: "avg",
minScale: 0,
maxScale: 1,
height: 96
});
plotHistogram
panel.plotHistogram("FPS", [58, 60, 61, 57, 62], {
overlay: "fps",
minScale: 0,
maxScale: 90,
height: 100
});
set / get / state 怎么用
如果你是带着“我要设置值”“我要读值”“我要在旧值基础上修改”的目标来查文档,先看下面这张表。
状态类型总表
| 控件类型 | 写进 state 的值 | 常见 set() 写法 |
|---|---|---|
checkbox / toggle / switch / selectable | boolean | panel.set("enable_hook", true) |
inputText | string | panel.set("base_url", "https://example.com") |
inputInt / sliderInt / dragInt | 整数 | panel.set("retry_count", 3) |
inputFloat / sliderFloat / sliderAngle / dragFloat | 浮点数 | panel.set("alpha_value", 0.85) |
combo / listBox / radioGroup | 选中项索引 | panel.set("process_index", 2) |
colorEdit / colorPicker | 常见为 hex 字符串 | panel.set("accent_color", " FF46BD87") |
button / smallButton | 不保存按下状态 | 不建议作为状态源 |
progress / plotLines / plotHistogram | 不作为稳定状态槽位 | 用新数据重新构建显示 |
最常见的 10 种读改写法
1. 读取布尔开关
const enabled = panel.get("enable_hook", false);
2. 反转布尔开关
panel.set("enable_hook", !panel.get("enable_hook", false));
3. 读取文本
const host = panel.get("host_input", "127.0.0.1");
4. 覆盖文本
panel.set("host_input", "https://example.com");
5. 在旧文本基础上拼接
const host = panel.get("host_input", "https://example.com");
panel.set("host_input", `${host}/v1`);
6. 读取整数 / 浮点
const retryCount = panel.get("retry_count", 3);
const opacity = panel.get("opacity_drag", 1.0);
7. 在旧数值基础上增减
panel.set("retry_count", panel.get("retry_count", 0) + 1);
panel.set("opacity_drag", panel.get("opacity_drag", 1.0) - 0.1);
8. 读取选项类控件
const index = panel.get("process_index", 0);
const name = ["main", "push", "tool"][index];
9. 切换选中的项
panel.set("process_index", 1);
10. 查看完整状态快照
log(JSON.stringify(panel.state(), null, 2));
这一组是 imgui 真正的状态 API。
builder.set(key, value)
强制写入某个状态槽位。
panel.set("enable_hook", true);
panel.set("host_input", "127.0.0.1");
panel.set("accent_color", " FF46BD87");
适合:
- 初始化某个已有 key
- 外部脚本逻辑主动改控件值
- 按按钮批量修改多个状态
builder.get(key, fallback?)
读取状态槽位,不存在时返回备用值。
const enabled = panel.get("enable_hook", false);
const host = panel.get("host_input", "localhost");
builder.state(key?)
state("key"):读取单个值state():读取完整状态快照对象
const allState = panel.state();
log(JSON.stringify(allState, null, 2));
一个完整的“读改写”例子
const panel = imgui.window("config-demo");
panel
.checkbox("Enable Hook", "enable_hook", true)
.inputText("Base URL", "base_url", "https://httpbin.org")
.button("Disable + Rewrite URL", state => {
panel.set("enable_hook", false);
panel.set("base_url", "https://example.com");
log(JSON.stringify(panel.state(), null, 2));
})
.show();
默认值什么时候生效
这个行为经常被误解:
panel.checkbox("Enable", "enable_key", true);
panel.set("enable_key", false);
// 再次构建同一控件
panel.checkbox("Enable", "enable_key", true);
第二次不会把值改回 true。
因为源码里用的是 ensureStateDefault(key, defaultValue),意思是:
如果这个 key 之前不存在,写默认值
如果之前已经存在,保留已有值
clear()不会清状态
panel.clear();
log(panel.get("enable_key", null));
上面这句仍然能读到旧状态。clear() 只移除 items,不移除 state。
回调签名速查
| 控件 | 回调签名 |
|---|---|
button / smallButton | (state) => {} |
checkbox / toggle / switch / selectable | (value, state) => {} |
inputInt / inputFloat | (value, state) => {} |
sliderInt / sliderFloat / sliderAngle | (value, state) => {} |
dragInt / dragFloat | (value, state) => {} |
combo / listBox / radioGroup | (index, state) => {} |
inputText.onChange | (text, state) => {} |
inputText.onSubmit | (text, state) => {} |
colorEdit / colorPicker | (hex, rgba, state) => {} |
onClose | () => {} |
一个稍完整的面板例子
const panel = imgui.window("network-panel", {
title: "Network Debug",
width: 400,
height: 620,
x: 24,
y: 96,
theme: "rose_glass",
windowCollapsible: true,
resizable: true
});
const processes = ["main", "push", "tool"];
panel.clear();
panel
.text(`pkg=${lpparam.packageName}`)
.text(`proc=${lpparam.processName}`)
.separatorText("Request")
.checkbox("Enable Hook", "enable_hook", true)
.inputText("Base URL", "base_url", "https://httpbin.org", {
hint: "Input request base url"
})
.combo("Process", "process_index", processes, 0)
.sliderFloat("Alpha", "alpha_value", 0.2, 1.0, 0.92, value => {
panel.alpha(value);
})
.colorEdit("Accent", "accent_color", " FF46BD87", {
alpha: true
})
.button("Ping", state => {
if (!state.enable_hook) {
log("hook disabled");
return;
}
const url = panel.get("base_url", "https://httpbin.org");
const res = http.get(`${url}/get`);
log(res.body.string());
})
.sameLine(12)
.button("Reset", state => {
panel.set("enable_hook", true);
panel.set("base_url", "https://httpbin.org");
panel.set("process_index", 0);
panel.set("accent_color", " FF46BD87");
})
.separatorText("State Snapshot")
.button("Dump State", state => {
log(JSON.stringify(panel.state(), null, 2));
})
.show();
实战建议
- 只要准备长期读写状态,就显式传
key。 - 每次重建 UI 前先
clear(),避免重复叠加控件。 close()后如果还要复用旧状态,继续用同一个窗口id。- 想做调试工具面板,优先组合
tabs、child、collapsible、set/get/state。 - 想做真正“外悬浮”的工具,再考虑
systemOverlay或accessibilityOverlay。
