mcpField
G
glasses-ui

glasses-ui

github.com/even-realitiesデザイン・UI79143 回インストール1 週間前に更新
リポジトリ

glasses-ui について

glasses-uiは、SDKを使ってEven Realities G2グラス向けのUIを作るためのスキル資料です。テキスト、リスト、グレースケール画像の画面を実装する開発者に、レイアウト制約や更新方法、操作パターンを説明します。

主な機能

  1. 01576×288ピクセルのキャンバスと階調規則
  2. 02テキスト・リスト・画像コンテナの制約
  3. 03ページ作成・再構築・テキスト更新
  4. 04画像データ転送と直列化の指針
  5. 05イベント取得とコンテナ重ね合わせ規則
  6. 06ページ分割と簡易コントロールのパターン

活用シーン

  1. 01G2グラス向けの複数行画面を設計
  2. 02画面を再描画せずにステータス文字を更新
  3. 03選択・スクロール可能なリストを表示
  4. 04入力取得レイヤーを備えたビットマップ画像の表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/even-realities/everything-evenhub --skill glasses-ui

インストール後、Claude Code で/glasses-uiを入力して読み込まれたか確認します。

詳細

Canvas

G2 グラスのディスプレイは 576x288 px のキャンバスで、次の特徴があります。

  • 原点:左上隅(0, 0)
  • X 軸:右方向に増加(0〜576)
  • Y 軸:下方向に増加(0〜288)
  • 色深度:4 ビットグレースケール — 16 段階の緑(値 0〜15)
  • 白いピクセルは、ハードウェア上では明るい緑色で表示されます
  • 黒いピクセルは消灯します(透明 — 現実世界が透けて見えます)
  • 背景色や塗りつぶしはありません — キャンバスにデフォルトの背景はなく、描画されていない領域は透明です

コンテナーのルール

各ページはコンテナーで構成されます。ページごとに次の制限が適用されます。

  • コンテナーは合計最大 12 個(すべての種類の合計)
  • テキスト/リストコンテナーは最大 8 個
  • 画像コンテナーは最大 4 個
  • isEventCapture: 1 を持つコンテナーが必ず 1 個必要です — このコンテナーがユーザー入力イベントを受け取ります
  • zOrderIndex による重なり順(SDK 0.0.12 以降) — ページごとに、すべてのリスト/テキスト/画像コンテナーで一意の zOrderIndex を設定するか、どのコンテナーにも設定しないかのどちらかにしてください。値が大きいほど手前に表示されます。どこにも設定しない場合は、宣言順で重なり順が決まり、後に宣言されたものが上に表示されます
  • containerID はページごとに一意である必要があります(数値)
  • containerName はページごとに一意である必要があります(文字列、最大 16 文字)

共通プロパティ

すべてのコンテナータイプで、次のプロパティを共有します。

プロパティ型範囲備考
xPositionnumber0–576コンテナーの左端
yPositionnumber0–288コンテナーの上端
widthnumber0–576コンテナーの幅
heightnumber0–288コンテナーの高さ(ピクセル)
containerIDnumber—ページごとに一意
containerNamestringmax 16 charsページごとに一意
isEventCapturenumber0 or 11 に設定するコンテナーが必ず 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—ページごとに一意
containerNamestringmax 16 charsページごとに一意
isEventCapturenumber0 or 11 に設定するコンテナーが必ず 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 はリスト項目を定義します。

プロパティ型範囲備考
itemCountnumber1–20リスト内の項目数
itemWidthnumber0–576項目の幅。0 = コンテナーの幅に合わせて自動調整
isItemSelectBorderEnnumber0 or 1選択枠のハイライトを有効化
itemNamestring[]最大20項目、各項目最大64文字項目ラベル文字列の配列

リストコンテナーの動作:

  • ファームウェアがスクロールをネイティブに処理するため、手動でスクロールを実装する必要はありません
  • リストはインプレースで更新できません。リストの内容を変更するには、ページ全体を再構築する必要があります
  • 項目ごとのカスタムスタイルには対応していません
  • 項目の高さは制御できません
  • 項目間に区切り線はありません

ImageContainerProperty

画像コンテナーは、4ビットグレースケールのビットマップ画像を描画します。

プロパティ型範囲備考
xPositionnumber0–576左端
yPositionnumber0–288上端
widthnumber20–288コンテナーの幅(ピクセル)
heightnumber20–144コンテナーの高さ(ピクセル)
containerIDnumber—ページごとに一意
containerNamestring最大16文字ページごとに一意
zOrderIndexnumber—重なり順。値が大きいほど前面(SDK 0.0.12+)

画像コンテナーは isEventCapture に対応していません。画像と組み合わせる場合は、イベントキャプチャレイヤーとしてテキストコンテナーを使用してください(下記の画像ベースのアプリのパターンを参照)。

画像データ形式:

  • 受け付ける形式:number[] | Uint8Array | ArrayBuffer | base64 文字列
  • 色深度:4ビットグレースケール(ピクセルあたり0~15の値)

前処理は任意です。 送信前にグレースケール化やディザリングを行う必要はありません。SDK が一般的な形式を内部で変換し、変換できない場合にのみ imageToGray4Failed を返します。線画、アイコン、QRコードは通常、そのままでも問題なく描画できます。写真やグラデーションは、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 経由の画像フレームは1枚あたり約0.5~2秒かかります — SDK 0.0.12以降では生データを内部でLZ4圧縮するため転送時間が短縮されますが、差分エンコーディングには対応していません。ターン制を前提に設計し、複数FPSを前提とするループは避けてください
  • テキストの更新は画像の更新よりはるかに高速です — 即時性が必要なものにはテキストを使用し、画像コンテナの更新は完了するまで待ちます
  • 画像に限らず、すべてのブリッジ呼び出しを直列化します — 次の処理を開始する前に、それぞれを await します。レンダリングとストレージの呼び出しを並行すると、接続がクラッシュすることがあります
  • BLE 呼び出しには呼び出しごとのタイムアウトを設定します — 不安定なホップが1つあるだけで約30秒停止することがあります。Promise.race で数秒の上限を設定してください
  • 永続状態への書き込みはデバウンスします — setLocalStorage は同じ BLE リンクを共有するため、ティック/ページ切り替え時にデバウンスし、終了時にフラッシュします
  • createStartUpPageContainer は必ず1回だけ呼び出します — 以降のすべてのレンダリングでは、rebuildPageContainer または *Upgrade 呼び出しを使用します

一般的なUIパターン

偽のボタン

テキスト項目の先頭に > を付け、カーソルインジケーターとして使用すると、ボタンや選択状態をシミュレートできます。

> Start
  Settings
  Exit
選択ハイライト

個々のテキストコンテナの borderWidth を、0(未選択)と0以外の値(選択)の間で切り替えて、フォーカスを示します。rebuildPageContainer が必要です。

複数行レイアウト

複数のテキストコンテナを縦に積み重ねます。288pxのキャンバスに3つの等しい行を配置する場合:

// 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

コメント