mcpField
G
glasses-ui

glasses-ui

github.com/even-realities设计与 UI79143 次安装1 周前更新
代码仓库

关于 glasses-ui

glasses-ui 是一项技能参考,介绍如何使用 SDK 为 Even Realities G2 眼镜显示屏构建界面。它面向实现文本、列表和灰度图像页面的开发者,说明布局限制、更新方式和交互模式。

主要功能

  1. 01576×288 像素画布与灰度规则
  2. 02文本、列表和图像容器限制
  3. 03页面创建、重建和文本更新
  4. 04图像数据传输与串行化建议
  5. 05事件捕获与容器层叠规则
  6. 06分页和简单控件模式

适用场景

  1. 01为 G2 眼镜设计多行显示界面
  2. 02无需重绘页面即可更新状态文本
  3. 03显示可选择、可滚动的列表
  4. 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 个字符)

共享属性

所有容器类型都具有以下属性:

属性类型范围说明
xPositionnumber0–576容器左边缘
yPositionnumber0–288容器上边缘
widthnumber0–576容器宽度(像素)
heightnumber0–288容器高度(像素)
containerIDnumber—每页唯一
containerNamestring最多 16 个字符每页唯一
isEventCapturenumber0 或 1必须恰好有一个容器设为 1
zOrderIndexnumber—堆叠顺序,数值越大越靠前;每页必须全有或全无,且数值唯一(SDK 0.0.12+)

边框属性

边框属性仅适用于文本和列表容器(不适用于图像容器):

属性类型范围说明
borderWidthnumber0–50 = 无边框
borderColornumber0–15灰度级别(0 = 黑色,15 = 白色/亮绿色)
borderRadiusnumber0–10圆角半径
paddingLengthnumber0–32均匀应用于所有边的内边距

TextContainerProperty

文本容器用于显示可滚动或静态文本内容。

属性类型范围说明
xPositionnumber0–576左边缘
yPositionnumber0–288上边缘
widthnumber0–576容器宽度
heightnumber0–288容器高度
containerIDnumber—每页唯一
containerNamestring最多 16 个字符每页唯一
isEventCapturenumber0 或 1必须恰好有一个容器设为 1
zOrderIndexnumber—堆叠顺序,数值越大越靠前(SDK 0.0.12+)
borderWidthnumber0–50 = 无边框
borderColornumber0–15灰度级别
borderRadiusnumber0–10圆角
paddingLengthnumber0–32均匀内边距
contentstring—要显示的文本

内容限制:

  • createStartUpPageContainer:最多 1000 个字符
  • textContainerUpgrade:最多 2000 个字符
  • rebuildPageContainer:最多 1000 个字符

文本行为:

  • 文本会根据容器宽度自动换行
  • 使用 \n 插入显式换行
  • 大约 400–500 个字符即可填满整个屏幕
  • 支持固件字体集中的 Unicode 字符
  • 没有文本对齐选项(始终左对齐)
  • 无法控制字体大小
  • 不支持粗体或斜体样式

ListContainerProperty 和 ListItemContainerProperty

列表容器用于显示由原生固件管理、可滚动且可选择的条目列表。

ListItemContainerProperty 用于定义列表条目:

PropertyTypeRangeNotes
itemCountnumber1–20列表中的条目数
itemWidthnumber0–576条目宽度;0 = 自动填满容器宽度
isItemSelectBorderEnnumber0 or 1启用选中边框高亮
itemNamestring[]max 20 items, max 64 chars each条目标签字符串数组

列表容器行为:

  • 固件原生处理滚动,无需手动实现滚动
  • 列表无法原地更新——必须重建整个页面才能更改列表内容
  • 不支持为每个条目自定义样式
  • 无法控制条目高度
  • 条目之间没有分隔线

ImageContainerProperty

图像容器用于渲染 4 位灰度位图图像。

PropertyTypeRangeNotes
xPositionnumber0–576左边缘
yPositionnumber0–288上边缘
widthnumber20–288容器宽度,单位为像素
heightnumber20–144容器高度,单位为像素
containerIDnumber—每个页面内唯一
containerNamestringmax 16 chars每个页面内唯一
zOrderIndexnumber—堆叠顺序,值越大越靠前(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

评论

支持 Markdown