Unleashed 固件 JS SDK 开发指南:用 `gui/dialog` 模块构建三按钮交互对话框
2026/9/13 11:34:42 网站建设 项目流程

Unleashed 固件 JS SDK 开发指南:用gui/dialog模块构建三按钮交互对话框

【免费下载链接】unleashed-firmwareFlipper Zero Unleashed Firmware项目地址: https://gitcode.com/GitHub_Trending/un/unleashed-firmware

Flipper Zero Unleashed 固件内置了完整的 JavaScript 运行时与 JS SDK(见 documentation/js/js_about.md),开发者可以在设备上直接运行.js脚本实现图形界面。gui/dialog是 GUI 模块中负责"询问用户"的视图组件:它在一屏内显示一段粗体标题、一段正文,以及最多三个按钮,用于确认、取消、三选一等典型交互场景。本文将围绕该模块的导入方式、属性(Props)、事件(Event)与事件循环订阅机制展开,并结合仓库内 C 语言胶水实现与 TypeScript 类型声明,给出可直接运行的实战示例。

模块依赖与导入顺序

gui/dialoggui模块的子模块,而gui又依赖event_loop模块,因此导入顺序必须严格遵守,且需要依次先理解这几个模块的职责:

let eventLoop = require("event_loop"); let gui = require("gui"); let dialogView = require("gui/dialog");
  • event_loop:事件循环,提供定时器、订阅等基础机制,是 GUI 视图事件(如按钮按下)的转发通道;
  • gui:GUI 模块,持有全局唯一的viewDispatcher(视图调度器)单例,负责视图切换与输入事件分发;
  • gui/dialog:dialog 视图的工厂模块,dialogView即一个ViewFactory

在源码层面,这一依赖关系体现在 js_gui.c 的js_gui_create中:gui模块创建时会通过js_module_get(modules, "event_loop")取出事件循环对象,并基于它创建view_dispatcher_alloc_ex(loop);而 dialog 模块的声明则位于 dialog.c,由JS_GUI_VIEW_DEF(dialog, &view_descriptor)宏展开为名为gui__dialog的 JS 模块描述符,这也就是require("gui/dialog")最终命中的注册入口。

建议:在深入 dialog 之前,先阅读 documentation/js/js_event_loop.md 与 documentation/js/js_gui.md,理解 View / ViewFactory / ViewDispatcher 与事件订阅的协作方式。

View 属性(Props)

dialog 视图的全部可配置属性都接收字符串值,通过makeWith(props)在创建时注入,或通过view.set(name, value)在运行期修改:

Prop类型说明
headerstring屏幕顶部加粗显示的标题文本
textstring屏幕中部显示的正文文本
leftstring左按钮文本;不设置则不渲染左按钮
centerstring中按钮文本;不设置则不渲染中按钮
rightstring右按钮文本;不设置则不渲染右按钮

三个按钮属性均为可选:任一按钮未设置文本,对应按钮就不会出现。这在实现"仅确认"或"确认/取消"等非对称布局时非常实用。

这些属性在 C 侧由 dialog.c 的view_descriptor声明,prop_cnt = 5与五个JsViewPropDescriptorheadertextleftcenterright)一一对应,每个属性类型均为JsViewPropTypeString。属性赋值会经js_gui_view_assign(见 js_gui.c)做类型校验:如果传入的不是字符串,会抛出view prop "xxx" requires string value错误。

底层实现上,dialog 视图包装的是 GUI 服务中的DialogEx模块(见 dialog_ex.h)。每个属性的赋值回调直接对应一个 C API 调用:

  • headerdialog_ex_set_header(dialog, text, 64, 0, AlignCenter, AlignTop):标题锚定屏幕顶部水平居中;
  • textdialog_ex_set_text(dialog, text, 64, 32, AlignCenter, AlignCenter):正文锚定屏幕垂直居中位置;
  • left/center/rightdialog_ex_set_left_button_text/dialog_ex_set_center_button_text/dialog_ex_set_right_button_text

对齐与坐标由胶水代码固定,开发者只需要关心文本内容本身。同时,DialogEx的原生 API 还支持 icon(dialog_ex_set_icon)与扩展按下/释放事件(dialog_ex_enable_extended_events),这些能力目前未在 JS 侧暴露。

View 事件(Event)

dialog 视图只有一个事件源input

Item类型说明
inputstring用户按下三个按钮中的任意一个时触发,事件值为"left""center""right"之一,对应所按按钮

该事件是事件循环中的Contract对象,创建于 dialog.c 的ctx_make:dialog 视图被创建时分配一个容量为 2 的FuriMessageQueue,注册DialogExResultCallback回调;原生按钮按下事件通过input_callback写入消息队列,再由input_transformerDialogExResultLeft/Center/Right枚举映射为 JS 字符串"left"/"center"/"right"后交付给 JS 侧。这个契约对象被挂载为视图对象的input属性,因此可以在eventLoop.subscribe(views.xxx.input, ...)中使用。

注意:DialogEx原生回调运行在 GUI 线程(见 dialog_ex.h 的注释警告),跨线程数据传递正是通过消息队列完成的,这也是该实现能够与事件循环安全协作的原因。

完整示例:从事件循环订阅到按钮分发

文档推荐的完整示例见仓库自带的gui.js演示脚本(gui.js)。该脚本同时展示了 dialog 的两种典型用法。

1. 创建对话框并动态设置文本与按钮

let helloDialog = dialogView.make(); // 运行期更新正文与按钮 views.helloDialog.set("text", "Hi " + name + "! :)"); views.helloDialog.set("center", "Hi Flipper! :)"); gui.viewDispatcher.switchTo(views.helloDialog);

这里只设置center按钮,因此屏幕上只出现一个居中按钮——典型的一键确认框。

2. 订阅input事件分发按钮逻辑

eventLoop.subscribe(views.helloDialog.input, function (_sub, button, gui, views) { if (button === "center") gui.viewDispatcher.switchTo(views.demos); }, gui, views);

eventLoop.subscribe的第二个参数是回调函数,第一个参数_sub为订阅对象,第二个参数button"left"/"center"/"right"之一;从第三个参数开始是调用时透传的附加参数(此处为guiviews,由 subscribe 尾部实参传入),回调据此可以访问gui.viewDispatcher进行视图切换。

在此基础上,一个完整的三按钮对话框可以这样写:

let eventLoop = require("event_loop"); let gui = require("gui"); let dialogView = require("gui/dialog"); let views = { main: dialogView.makeWith({ header: "Delete file?", text: "This action cannot be undone", left: "Cancel", center: "Retry", right: "Delete", }), done: dialogView.makeWith({ text: "Done", center: "OK" }), }; eventLoop.subscribe(views.main.input, function (_sub, button, gui, views) { if (button === "left") { gui.viewDispatcher.switchTo(views.done); } else if (button === "center") { // 重试逻辑 } else if (button === "right") { // 删除逻辑 } gui.viewDispatcher.switchTo(views.done); }, gui, views); gui.viewDispatcher.switchTo(views.main); eventLoop.run();

从 JS 到原生视图的完整调用链

综合上述分析,gui/dialog的完整实现链路可以概括为:

  1. 注册JS_GUI_VIEW_DEF(dialog, &view_descriptor)宏(js_gui.h)将 dialog 注册为gui__dialog插件模块;
  2. 工厂:模块加载时js_gui_make_view_factory生成带make/makeWith方法的ViewFactory对象(js_gui.c);
  3. 实例化make()/makeWith()调用js_gui_make_view,执行descriptor->alloc()(即dialog_ex_alloc)创建原生DialogEx,将其加入viewDispatcher并分配视图 id;makeWith再逐条执行属性赋值(js_gui.c);
  4. 渲染与输入dialog_ex_get_view返回的View被调度器接管,按钮按下经DialogExResultCallback→ 消息队列 →input_transformer→ JS 字符串事件;
  5. 销毁:视图被释放时,js_gui_view_destructor依次调用custom_destroy(释放队列、退订事件循环)与descriptor->free(即dialog_ex_free),见 js_gui.c。

对应当前仓库中 JS SDK 0.1 版本,TypeScript 类型声明也完整描述了该模块的契约(dialog.d.ts):Dialog类继承View<Props, Child>,其中Child = never(dialog 不支持子元素,调用addChild/setChildren会报错 "this View can't have children"),input: Contract<"left" | "center" | "right">与本文描述的事件语义完全一致。使用 JS SDK(documentation/js/js_developing_apps_using_js_sdk.md)开发时,这些类型定义可以提供完整的代码补全与静态校验。

小结

  • dialog 视图适合作为确认框、提示框、三选一选择器,最多支持三个按钮,且按钮可按需省略;
  • 导入顺序必须是event_loopguigui/dialog
  • 属性在创建时通过makeWith({...})注入,运行期通过view.set("prop", "value")更新;
  • 按钮点击通过view.input事件订阅消费,事件值即"left"/"center"/"right"
  • 底层由 GUI 服务的DialogEx视图实现(dialog_ex.c),JS 胶水代码负责属性映射与线程安全的队列事件传递。

【免费下载链接】unleashed-firmwareFlipper Zero Unleashed Firmware项目地址: https://gitcode.com/GitHub_Trending/un/unleashed-firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询