关于 glasses-ui
glasses-ui 是一项技能参考,介绍如何使用 SDK 为 Even Realities G2 眼镜显示屏构建界面。它面向实现文本、列表和灰度图像页面的开发者,说明布局限制、更新方式和交互模式。
主要功能
- 01576×288 像素画布与灰度规则
- 02文本、列表和图像容器限制
- 03页面创建、重建和文本更新
- 04图像数据传输与串行化建议
- 05事件捕获与容器层叠规则
- 06分页和简单控件模式
适用场景
- 01为 G2 眼镜设计多行显示界面
- 02无需重绘页面即可更新状态文本
- 03显示可选择、可滚动的列表
- 04通过输入捕获层呈现位图图像
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ even-realities/ everything-evenhub --skill glasses-ui安装后在 Claude Code 中输入/glasses-ui确认已加载。
详细介绍
画布
G2 眼镜显示屏的画布尺寸为 576x288 px,具有以下特性:
- 原点:左上角 (0, 0)
- X 轴:向右递增(0 到 576)
- Y 轴:向下递增(0 到 288)
- 色深:4 位灰度——16 级绿色(数值 0–15)
- 硬件上的白色像素显示为亮绿色
- 黑色像素为关闭状态(透明——显示真实世界)
- 没有背景色或填充——画布没有默认背景;未绘制的区域为透明
容器规则
每个页面都由容器组成。每页适用以下限制:
- 最多 12 个容器(所有类型合计)
- 最多 8 个文本/列表容器
- 最多 4 个图像容器
- 必须恰好有一个容器的
isEventCapture: 1——该容器接收用户输入事件 - 通过
zOrderIndex控制堆叠顺序(SDK 0.0.12+)——每页必须全有或全无:所有列表/文本/图像容器都设置唯一的zOrderIndex(数值越大越靠前),或者都不设置;如果全部省略,则声明顺序决定重叠关系(后声明的容器显示在上层) - 每页的
containerID必须唯一(数字) - 每页的
containerName必须唯一(字符串,最多 16 个字符)
共享属性
所有容器类型都具有以下属性:
| 属性 | 类型 | 范围 | 说明 |
|---|---|---|---|
xPosition | number | 0–576 | 容器左边缘 |
yPosition | number | 0–288 | 容器上边缘 |
width | number | 0–576 | 容器宽度(像素) |
height | number | 0–288 | 容器高度(像素) |
containerID | number | — | 每页唯一 |
containerName | string | 最多 16 个字符 | 每页唯一 |
isEventCapture | number | 0 或 1 | 必须恰好有一个容器设为 1 |
zOrderIndex | number | — | 堆叠顺序,数值越大越靠前;每页必须全有或全无,且数值唯一(SDK 0.0.12+) |
边框属性
边框属性仅适用于文本和列表容器(不适用于图像容器):
| 属性 | 类型 | 范围 | 说明 |
|---|---|---|---|
borderWidth | number | 0–5 | 0 = 无边框 |
borderColor | number | 0–15 | 灰度级别(0 = 黑色,15 = 白色/亮绿色) |
borderRadius | number | 0–10 | 圆角半径 |
paddingLength | number | 0–32 | 均匀应用于所有边的内边距 |
TextContainerProperty
文本容器用于显示可滚动或静态文本内容。
| 属性 | 类型 | 范围 | 说明 |
|---|---|---|---|
xPosition | number | 0–576 | 左边缘 |
yPosition | number | 0–288 | 上边缘 |
width | number | 0–576 | 容器宽度 |
height | number | 0–288 | 容器高度 |
containerID | number | — | 每页唯一 |
containerName | string | 最多 16 个字符 | 每页唯一 |
isEventCapture | number | 0 或 1 | 必须恰好有一个容器设为 1 |
zOrderIndex | number | — | 堆叠顺序,数值越大越靠前(SDK 0.0.12+) |
borderWidth | number | 0–5 | 0 = 无边框 |
borderColor | number | 0–15 | 灰度级别 |
borderRadius | number | 0–10 | 圆角 |
paddingLength | number | 0–32 | 均匀内边距 |
content | string | — | 要显示的文本 |
内容限制:
createStartUpPageContainer:最多 1000 个字符textContainerUpgrade:最多 2000 个字符rebuildPageContainer:最多 1000 个字符
文本行为:
- 文本会根据容器宽度自动换行
- 使用
\n插入显式换行 - 大约 400–500 个字符即可填满整个屏幕
- 支持固件字体集中的 Unicode 字符
- 没有文本对齐选项(始终左对齐)
- 无法控制字体大小
- 不支持粗体或斜体样式
ListContainerProperty 和 ListItemContainerProperty
列表容器用于显示由原生固件管理、可滚动且可选择的条目列表。
ListItemContainerProperty 用于定义列表条目:
| Property | Type | Range | Notes |
|---|---|---|---|
itemCount | number | 1–20 | 列表中的条目数 |
itemWidth | number | 0–576 | 条目宽度;0 = 自动填满容器宽度 |
isItemSelectBorderEn | number | 0 or 1 | 启用选中边框高亮 |
itemName | string[] | max 20 items, max 64 chars each | 条目标签字符串数组 |
列表容器行为:
- 固件原生处理滚动,无需手动实现滚动
- 列表无法原地更新——必须重建整个页面才能更改列表内容
- 不支持为每个条目自定义样式
- 无法控制条目高度
- 条目之间没有分隔线
ImageContainerProperty
图像容器用于渲染 4 位灰度位图图像。
| Property | Type | Range | Notes |
|---|---|---|---|
xPosition | number | 0–576 | 左边缘 |
yPosition | number | 0–288 | 上边缘 |
width | number | 20–288 | 容器宽度,单位为像素 |
height | number | 20–144 | 容器高度,单位为像素 |
containerID | number | — | 每个页面内唯一 |
containerName | string | max 16 chars | 每个页面内唯一 |
zOrderIndex | number | — | 堆叠顺序,值越大越靠前(SDK 0.0.12+) |
图像容器不支持 isEventCapture。与图像搭配时,请使用文本容器作为事件捕获层(参见下文的基于图像的应用模式)。
图像数据格式:
- 接受:
number[] | Uint8Array | ArrayBuffer | base64字符串 - 色深:4 位灰度(每像素取值为 0–15)
**预处理为可选操作。**发送前无需预先转换为灰度或抖动处理——SDK 会在内部转换常见格式,只有在无法转换时才会返回 imageToGray4Failed。线稿、图标和二维码通常可以直接正常显示;照片和渐变图像在 16 阶灰度显示屏上使用客户端增强对比度并执行 Floyd–Steinberg 抖动处理会有更好的效果,但请先尝试直接发送。
重要行为:
- 图像容器创建时只是占位符——调用
updateImageRawData前不会显示任何内容 - 创建图像容器后,务必调用
updateImageRawData以显示内容 - 不要并发发送图像数据——应将图像更新排队,并等待每次更新完成后再发送下一张
页面生命周期方法
createStartUpPageContainer(container)
在应用启动时调用一次,用于创建初始页面布局。
createStartUpPageContainer(container: CreateStartUpPageContainer): Promise<StartUpPageCreateResult>
返回代码:
0— 成功1— 参数无效2— 超出大小限制(内容过大)3— 内存不足
rebuildPageContainer(container)
完整重绘页面。更改布局结构(添加或移除容器、切换容器类型)时使用。
rebuildPageContainer(container: RebuildPageContainer): Promise<boolean>
- 成功时返回
true - 会导致硬件上短暂闪烁(完整重绘)
- 每个文本容器的文本内容上限为 1000 个字符
textContainerUpgrade(container)
原地更新文本内容,无需重建页面。不会闪烁。
textContainerUpgrade(container: TextContainerUpgrade): Promise<boolean>
- 成功时返回
true - 不会闪烁——仅更新目标文本容器
containerID和containerName必须与现有容器完全一致- 文本内容上限为 2000 个字符
- 完整替换内容时使用
contentOffset: 0, contentLength: 0
updateImageRawData({ containerID, containerName, imageData })
向图像容器发送像素数据。
updateImageRawData(params: {
containerID: number
containerName: string
imageData: number[] | Uint8Array | ArrayBuffer | string
}): Promise<string>
返回状态字符串:
'success'— 图像已显示'imageException'— 一般图像错误'imageSizeInvalid'— 图像尺寸与容器不匹配'imageToGray4Failed'— 颜色转换失败'sendFailed'— 传输错误
不要并发调用——发送另一张图像前,请等待上一次调用完成。
shutDownPageContainer(exitMode?)
退出当前页面。
shutDownPageContainer(exitMode?: number): Promise<boolean>
exitMode: 0— 立即退出,不显示确认提示exitMode: 1— 显示退出确认对话框
最佳实践
- 频繁更新时使用
textContainerUpgrade(计数器、状态行、实时数据流)——它会原地更新,不会闪烁 - 更改布局时使用
rebuildPageContainer——例如添加或移除容器、切换容器类型或更新列表项 - 调用
textContainerUpgrade时,始终确保containerID和containerName完全匹配——不匹配会静默失败 - 不要并发调用
updateImageRawData——将更新排入队列,并等待每次更新完成后再发送下一次 - 将长文本预先分页,每页约 400–500 个字符,并在滚动事件中使用
rebuildPageContainer - 通过 BLE 传输图像帧每帧约需 0.5 秒至 2 秒——SDK 0.0.12+ 会在内部对原始数据进行 LZ4 压缩,以缩短传输时间,但不支持增量编码;请按回合设计,避免使用假定支持多 FPS 的循环
- 文本更新比图像更新快得多——需要即时响应的内容请使用文本;图像容器可以自行追赶
- 串行执行所有桥接调用,不仅限于图像调用——每次调用完成后再开始下一次;并发渲染和存储调用可能导致连接崩溃
- 为 BLE 调用添加单次调用超时——一次不稳定的跳转可能导致约 30 秒的挂起;请使用
Promise.race将超时设为几秒 - 对持久状态写入进行防抖——
setLocalStorage共用同一条 BLE 链路;请在每次刻度或翻页时进行防抖,并在退出时刷新 createStartUpPageContainer只能调用一次——之后的每次渲染都使用rebuildPageContainer或*Upgrade调用
常见 UI 模式
模拟按钮
在文本项前添加 > 作为光标指示符,以模拟按钮或选中状态:
> Start
Settings
Exit
选中高亮
将各文本容器的 borderWidth 在 0(未选中)和非零值(已选中)之间切换,以指示焦点。需要使用 rebuildPageContainer。
多行布局
垂直堆叠多个文本容器。在 288px 画布上创建三行等高布局:
// Row 1: y=0, height=96
// Row 2: y=96, height=96
// Row 3: y=192, height=96
进度条
使用 Unicode 块字符在文本容器中绘制进度条:
const filled = Math.round((progress / 100) * barWidth)
const bar = '━'.repeat(filled) + '─'.repeat(barWidth - filled)
content = `Progress: ${bar} ${progress}%`
翻页
按每页约 400–500 个字符预先分页。在滚动或导航事件中,使用新的页面片段调用 rebuildPageContainer:
const pages = paginateText(fullText, 450)
let currentPage = 0
async function showPage(index: number) {
currentPage = index
await bridge.rebuildPageContainer({
containerTotalNum: 1,
textObject: [{ ...containerConfig, content: pages[index] }],
})
}
完整代码示例
import {
waitForEvenAppBridge,
type TextContainerProperty,
type ImageContainerProperty,
} from '@evenrealities/even_hub_sdk'
const bridge = await waitForEvenAppBridge()
// Create initial page with text and image containers
const textContainer: TextContainerProperty = {
xPosition: 0, yPosition: 0, width: 576, height: 200,
borderWidth: 0, borderColor: 5, paddingLength: 4,
containerID: 1, containerName: 'main',
content: 'Hello from G2!',
isEventCapture: 1,
}
const imageContainer: ImageContainerProperty = {
xPosition: 200, yPosition: 210, width: 100, height: 60,
containerID: 2, containerName: 'icon',
}
const result = await bridge.createStartUpPageContainer({
containerTotalNum: 2,
textObject: [textContainer],
imageObject: [imageContainer],
})
if (result === 0) {
// Update image content after creation
await bridge.updateImageRawData({
containerID: 2,
containerName: 'icon',
imageData: [/* pixel data */],
})
// Flicker-free text update
await bridge.textContainerUpgrade({
containerID: 1,
containerName: 'main',
content: 'Updated text!',
contentOffset: 0,
contentLength: 0,
})
}
基于图像的应用模式
构建以图像为主的应用(例如,将画布或位图作为主要显示内容)时,请在图像容器后方使用全屏文本容器作为事件捕获层:
// Full-screen transparent text container — receives events, invisible to user
const eventLayer: TextContainerProperty = {
xPosition: 0, yPosition: 0, width: 576, height: 288,
containerID: 1, containerName: 'eventLayer',
content: ' ', // single space — required, cannot be empty
isEventCapture: 1, // this layer catches all input events
borderWidth: 0, borderColor: 0, paddingLength: 0,
zOrderIndex: 1, // back layer (SDK 0.0.12+; if one container sets zOrderIndex, all must)
}
// Image container renders on top of the event layer
const imageLayer: ImageContainerProperty = {
xPosition: 0, yPosition: 0, width: 200, height: 100,
containerID: 2, containerName: 'display',
zOrderIndex: 2, // front layer — larger = closer to front
// isEventCapture: 0 (default) — image containers do not capture events
}
await bridge.createStartUpPageContainer({
containerTotalNum: 2,
textObject: [eventLayer],
imageObject: [imageLayer],
})
// Always send image data after creation
await bridge.updateImageRawData({
containerID: 2,
containerName: 'display',
imageData: pixelData,
})
文本容器接收事件;图像容器绘制在其上方。在低于 0.0.12 的 SDK 中,移除两个 zOrderIndex 字段,并依靠声明顺序(后声明的内容绘制在上方)。
任务
$ARGUMENTS