Flipper Zero JS 应用开发:GUI 图标模块(gui/icon)中 getBuiltin 与 loadFxbm 的用法及底层实现解析
2026/9/14 11:21:45 网站建设 项目流程

Flipper Zero JS 应用开发:GUI 图标模块(gui/icon)中 getBuiltin 与 loadFxbm 的用法及底层实现解析

【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware

本文围绕 Flipper Zero 固件 JS 应用框架中的gui/icon模块(模块名gui__icon),讲解如何通过getBuiltin()获取固件内置图标、通过loadFxbm()从 SD 卡加载.fxbm精灵图标,并解析两种图标在 C 侧的实现、.fxbm文件格式、IconData返回类型以及 Widget/Menu/ButtonPanel 等视图对图标的消费方式。读完后你可以在自己的 JS 应用中正确加载并摆放图标,并理解其内存生命周期。

模块定位与加载方式

gui/icon是 JS SDK GUI 子模块之一,其职责按官方文档 js_gui__icon.md 的概括是:获取并加载图标,供 Dialog 等 GUI 视图使用(参见 js_gui__dialog.md)。

图标本身不是"视图",而是一种可被视图引用的图形资源:它不直接出现在屏幕上,而是作为 Widget 元素、Menu 条目、ButtonPanel 按钮等组件的属性传入。模块在 JS 侧的加载顺序遵循 GUI 模块的依赖约定(先event_loop,再gui,最后具体子模块):

let eventLoop = require("event_loop"); let gui = require("gui"); let icon = require("gui/icon");

仓库中完整的实战示例脚本是 gui.js,其中大量演示了图标在各视图中的用法(详见后文"典型使用示例"一节)。

API 一:getBuiltin(icon) —— 获取固件内置图标

参数与返回值

  • 参数icon:图标名称(字符串)
  • 返回:一个IconData对象
  • 行为:按名称查找固件内嵌的位图资源并返回其句柄;找不到时抛出错误。

TypeScript 声明见 icon.d.ts:

export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px" | "off_19x20" | "off_hover_19x20" | "power_19x20" | "power_hover_19x20" | "Settings_14"; export declare function getBuiltin(icon: BuiltinIcon): IconData;

当前可用的内置图标清单

文档中提到"目前仅DolphinWait_59x54js_script_10px可用",而从源码结构看,当前固件支持的内置图标实际上已经扩充到 7 个。C 侧实现 icon.c 中的注册表为:

图标名称尺寸/类型用途示例
DolphinWait_59x54静态位图等待画面的海豚动画
js_script_10px10px 小图标JS 脚本标识
off_19x20/off_hover_19x2019x20 位图电源键"关闭"常态/高亮态
power_19x20/power_hover_19x2019x20 位图电源键"开启"常态/高亮态
Settings_14动画图标设置齿轮旋转动画

注意两点实现细节:

  1. 静态图标通过ICON_DEF宏展开为固件assets中编译进去的I_<name>符号,而Settings_14通过ANIM_ICON_DEF宏映射到A_<name>符号——它是多帧动画图标,这也是 gui.js 中它能直接用作 Menu 图标并呈现旋转效果的原因。
  2. 查找是简单的线性strcmp遍历(icon.c#L61-L68),名称不存在时返回MJS_BAD_ARGS_ERROR,错误信息为no such built-in icon

返回值通过mjs_mk_foreign包装成 JS "foreign" 对象,内部实际是一个指向 CIcon结构体指针——即IconData本质上是一个不透明符号(nominal type),只能传给接受它的视图 API,不能直接操作其像素。

API 二:loadFxbm(path) —— 从文件加载 .fxbm 图标

参数与返回值

  • 参数path.fxbm文件路径(相对或绝对路径,如"/ext/demo.fxbm"
  • 返回IconData对象
  • 生命周期:文档明确说明"脚本退出时会自动卸载",这一行为在源码中有直接对应(详见"生命周期与内存管理"一节)。

.fxbm全称为 "XBM Flipper sprite",格式源自flipperzero-game-engine项目。从 icon.c 的解析逻辑可以确认其文件布局:

struct { uint32_t size; // 紧随其后的数据总大小(含 width/height 两个字段的值) uint32_t width; // 位图宽度(像素) uint32_t height; // 位图高度(像素) } fxbm_header; // 之后是 frame_size = size - 8 字节的未压缩位图像素数据

即:4 字节总大小、4 字节宽、4 字节高,其余全部是单帧未压缩的 1-bit 位图数据。加载函数会依次校验:文件能否以只读方式打开 → 头部 12 字节是否完整读出 → 剩余像素数据是否完整读出,任一环节失败都会报could not load .fxbm icon

解析成功后,函数将位图包装为固件 GUI 子系统可直接渲染的Icon结构(见 icon_i.h:widthheightframe_countframe_rateframes),其中frame_count固定为 1、frame_rate为 1,即.fxbm只承载单帧静态精灵。

IconData 与固件 Icon 结构的衔接

C 侧有一个精巧的内存布局:固件的Icon结构要求一个 frames 数组,JS 侧的实现用FxbmIconWrapper在一次malloc中把Icon、frames 指针数组和像素数据放在一起(icon.c#L36-L43),并借助CompressHeader的首字节is_compressed = false标记告知 GUI 渲染层该帧是未压缩数据。这样做的好处是整块内存可一次性free,避免多处分配/释放。

JS 类型定义上,IconData声明为symbol & { "__tag__": "icon" }(icon.d.ts#L6-L7),注释坦承这是"用 hack 方式引入名义类型"——__tag__属性并不真实存在,仅用于让 TypeScript 在编译期区分图标与其他值。

典型使用示例:图标在视图中的三种消费方式

以下示例均摘自仓库示例脚本 gui.js,覆盖了当前 SDK 中三种接收IconData的组件形态。

1. Widget 视图的 icon 元素

Widget 是自定义布局的画布,{ element: "icon" }元素通过iconData字段接收图标,支持绝对坐标定位:

let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54"); let jsLogo = icon.getBuiltin("js_script_10px"); let stopwatchWidgetElements = [ { element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" }, { element: "rect", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false }, { element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch }, { element: "icon", x: 64, y: 13, iconData: jsLogo }, { element: "button", button: "right", text: "Back" }, ]; let stopwatchWidget = widget.makeWith({}, stopwatchWidgetElements);

类型约束见 widget.d.ts#L45:IconElement = { element: "icon", iconData: IconData } & Position。C 侧对应处理在 widget.c#L203-L211:取出iconData字段、校验其为 foreign 对象后,解出const Icon*指针交给widget_add_icon_element渲染。注意iconData字段名与 Menu/ButtonPanel 中的icon不同。

2. Menu 视图的条目图标

Menu 条目由"图标 + 文本"组成(menu.d.ts#L32:Child = { icon: IconData, label: string }),示例中用Settings_14动画图标:

let settingsIcon = icon.getBuiltin("Settings_14"); let menu = menuView.makeWith({}, [ { label: "One", icon: settingsIcon }, { label: "Two", icon: settingsIcon }, { label: "three", icon: settingsIcon }, ]);

3. ButtonPanel 视图的按钮图标(常态/高亮两态)

ButtonPanel 的按钮元素支持icon(常态)与iconSelected(高亮/选中态)两个图标,这对"off/power"内置图标正是为此设计的(button_panel.d.ts#L38):

let offIcons = [icon.getBuiltin("off_19x20"), icon.getBuiltin("off_hover_19x20")]; let powerIcons = [icon.getBuiltin("power_19x20"), icon.getBuiltin("power_hover_19x20")]; let buttonPanel = buttonPanelView.makeWith({ matrixSizeX: 2, matrixSizeY: 2, }, [ { type: "button", x: 0, y: 0, matrixX: 0, matrixY: 0, icon: offIcons[0], iconSelected: offIcons[1] }, { type: "button", x: 30, y: 30, matrixX: 1, matrixY: 1, icon: powerIcons[0], iconSelected: powerIcons[1] }, ]);

除上述三类外,Dialog 等视图同样可以接收IconData(原文档即以 Dialog 为主要消费场景之一,对应类型声明位于 fz-sdk/gui 目录)。

生命周期与内存管理(源码级)

模块以插件(plugin)形式挂载,描述符名称为gui__icon(icon.c#L145-L150)。创建实例时初始化一个FxbmIconWrapperList(基于 mlib 的链表),所有loadFxbm分配的FxbmIconWrapper都会push_back进该列表(icon.c#L117-L119)。

脚本退出(模块销毁)时,js_gui_icon_destroy遍历链表逐个free并清理(icon.c#L135-L143)。这正是文档所说".fxbm图标会在脚本退出时自动卸载"的实现保障:开发者无需手动释放loadFxbm的返回值,但也意味着图标数据只在本次脚本运行期间有效,不应跨脚本复用。

getBuiltin返回的图标指向固件内置资源区(assets编译产物),不占用该列表,无释放负担。

版本与可用性说明

根据类型声明中的@version标注(icon.d.ts):

  • getBuiltin:JS SDK 0.2 引入,需额外特性"gui-widget";自 JS SDK 1.0 起为基线功能(无需额外声明特性)。
  • loadFxbm:JS SDK 0.3 引入,需额外特性"gui-widget-extras";自 JS SDK 1.0 起为基线功能。
  • 内置图标集合会随固件版本扩充,本文所列 7 个名称以当前仓库 icon.c#L22-L30 注册表为准;使用时若名称拼写错误或固件版本不含该图标,会收到no such built-in icon错误。

小结

gui/icon模块用两个 API 覆盖了 JS 应用的图标需求:getBuiltin零成本获取固件内置位图/动画图标(线性查找注册表,失败即抛错),loadFxbm读取 12 字节头部 + 单帧 1-bit 位图的.fxbm精灵文件并自动管理其生命周期。两者都返回不透明的IconData,可直接作为 Widgeticon元素(iconData字段)、Menu 条目或 ButtonPanel 按钮(icon/iconSelected字段)的图标源。仓库内最完整的调用示范见 gui.js,实现细节则以 icon.c 为权威参考。

【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware

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

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

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

立即咨询