プロトコル V1

APIリファレンス

Glance Widgets が描画できるエンドポイントを設計するために必要なことをすべて。ウィジェットがどのようなリクエストを送るか、あなたの API がどのような形式のレスポンスを返すべきか、そしてレンダラーが従うルールを解説します。このページのライブ例はすべて、アプリと同じルールで描画されています。

概要#

Glance Widgets のウィジェットは、あなたが管理するエンドポイントへの1回のHTTPS GETリクエストで動作します。エンドポイントは、描画内容を記述する1つのJSONオブジェクト(フレーム)を返します。アプリがフレームを検証・正規化し、WidgetKitがホーム画面に描画します。

フレームには、重ね合わせて合成される2つの表現チャンネルがあります。

  • ルートピクセルグリッド(pixels):最大4,096セルの長方形のドットマトリクスで、ウィジェットのキャンバス全体を配置領域とします。すべてのドットにスタイルを指定することも、大半を default と batch に任せることもできます。
  • レイヤー:位置とサイズを持つコンテンツ要素。テキスト、曲線、画像、アイコン、線、図形、局所ドットマトリクスが使えます。
コンテナ(Liquid Glass、ガラス、単色、グラデーション、アンビエント、画像)はアプリ側で設定するもので、APIでは指定しません。レスポンスが担うのは、その上に描く内容だけです。例外は画像マテリアルで、data の画像を読み込み、サーバーで描画した画面全体を表示できます。サーバー側で描画した画像を参照してください。

クイックスタート#

有効な最小のレスポンスは、グリッドひとつです。ピクセルスタイル配列の要素が null の場合は default を継承し、明示的なオブジェクトはそれを上書きします。

例
{
  "protocol": 1,
  "default": { "color": "#8EC8FB", "opacity": 0.12, "cornerRadius": 0.5 },
  "pixels": [
    [null, null, null, null, null],
    [
      null,
      { "color": "#7EDDB5", "opacity": 1 },
      null,
      { "color": "#7EDDB5", "opacity": 1 },
      null
    ],
    [null, null, null, null, null],
    [
      { "color": "#DFFF70", "opacity": 1 },
      null,
      null,
      null,
      { "color": "#DFFF70", "opacity": 1 }
    ],
    [
      null,
      { "color": "#DFFF70", "opacity": 1 },
      { "color": "#DFFF70", "opacity": 1 },
      { "color": "#DFFF70", "opacity": 1 },
      null
    ]
  ]
}
小サイズのウィジェット上の5 × 5グリッド
  • HTTPSを提供できる場所に、エンドポイントをデプロイします。
  • アプリでウィジェットを作成し、APIフィールドにURLを貼り付けます。
  • ホーム画面と同じサイズでプレビューしてから、ウィジェットをホーム画面に追加します。

ウィジェットのコンテンツ構造#

カードの素材とコンテンツ

ウィジェットの表現は、カード自体のスタイル・素材とカード内のコンテンツで構成されます。素材はアプリ内のカスタムウィジェットエディタ、または将来提供される他のプラットフォームのエディタで設定します。API から素材を指定・動的更新することはできません。API が扱うのはコンテンツとそのスタイルです。

カード上のレイヤーには2つの出どころがあります。1つ目はエディタのレイヤーです。エディタの視覚的な操作画面で追加・配置・設定し、ウィジェット設定に保存します。完全に静的なままでもよく、データパスを指定すれば、API がレスポンスごとに位置・内容・スタイルを更新できます。このときエディタの設定は、最初のレスポンス前の初期状態であり、レスポンスにないフィールドの代替値でもあります。

2つ目はリモートレイヤーです。エディタでは何も設定せず、API がレスポンスごとに宣言します。保存済みのウィジェット設定にまったく存在しない内容でもよく、定義はすべて API から来ます。2種類のレイヤーは併用できます。具体的な書き方は「レスポンスボディ」と「レイヤー」で説明します。

基本レイヤーとドットマトリクス

Glance Widgets では、テキスト・直線・曲線・画像・アイコン・図形などのレイヤーを追加して、基本的な内容を表現できます。レイヤーは重ねて配置でき、専用の zIndex フィールドで重なり順を制御します。

表現力を広げつつ、記述と解析を簡単に保つため、ドットマトリクスも提供しています。サーバーのプログラムで自由な表現を生成し、二次元配列として記述できます。基本レイヤーで表す内容も、好みや用途に応じてピクセルアートとして表現できます。細かさはセル数の上限に制約されます。

マトリクスには2つの形があります。マトリクスレイヤー(type: "matrix")は、他のレイヤーと同じく指定位置・サイズの領域に描画し、その content が1つのマトリクスです。ルートマトリクス(トップレベルの pixels)はレイヤーシステムに属さず、コンテンツキャンバス全体を配置領域とします。セルのスタイル、描画ルール、API 構造は共通です。ルートを別に設けた目的は、統一したスタイルのピクセル表現だけで1つのテーマを作る際に、レイヤーの宣言を省いて簡潔に実現することです。layout に従って縦横比を維持して中央に収めるため、実際にカード全体を埋めるかどうかは配列の縦横比、間隔、余白に依存します。

マトリクスはセルの格子です。各点のスタイルを指定するだけでなく、特定の点を非表示にもできます。非表示にしても論理的な位置はなくなりません。黒地に赤い文字の古い電光掲示板のように一部を空け、模様を作ったり、別のレイヤーを重ねたりできます。{"empty": true} の詳細は「ピクセルと優先順位」で説明します。

最終的な3層構造

  • 最下層:カードのスタイルと素材。背景とコンテナとしてエディタで設定します。
  • 中間層:ルートマトリクス。キャンバス全体を配置領域とし、一部またはすべてのセルを空白にできます。
  • 最上層:コンテンツレイヤー。エディタのレイヤーと API が宣言したリモートレイヤーを zIndex 順で合成します。局所的なマトリクスもここに含まれます。

リクエスト#

ウィジェットはHTTPSの GET を送信します。あなた自身のクエリパラメータはそのまま保持され、アプリは描画先の領域を示す予約済みの gw_* パラメータを追加します。HTTPS以外のURLは拒否されます。

gw_protocol
例1
説明クライアントが話すプロトコルのバージョン。
gw_platform
例ios
説明クライアントのプラットフォーム。
gw_family
例small · medium · large
説明ウィジェットのサイズファミリー。
gw_canvas_width_pt
例158.00
説明使用可能なコンテンツキャンバスの幅。単位は iOS の論理ピクセル(pt)、小数点以下2桁。
gw_canvas_height_pt
例158.00
説明使用可能なコンテンツキャンバスの高さ。単位は iOS の論理ピクセル(pt)、小数点以下2桁。
gw_aspect_ratio
例1.0000
説明幅 ÷ 高さ(小数点以下4桁)。
gw_context
例app_preview
説明レンダリングコンテキスト。app_home:アプリのホーム、app_preview:編集プレビュー、widget:ホーム画面のウィジェット。
gw_state_<key>
例—
説明将来の操作状態の拡張用に予約されており、現在のリクエスト処理では未使用です。gw_ で始まるパラメータ名はクライアント専用のため、独自のクエリパラメータには使用しないでください。

pt は iOS の論理的なレイアウト単位で、アプリの Flutter 側では論理ピクセルに対応します。画面の物理ピクセル数や格子のセル数ではありません。例えば表示倍率が3倍なら、158 pt は474物理ピクセルの長さに相当します。両パラメータはカードの内側余白を差し引いた値です。幅 = ウィジェット外枠の幅 − 2 × contentInset、高さも同様で、最小値は1 ptです。

キャンバスサイズはデバイスによって異なります。幅390〜393 ptのiPhoneでは、各サイズはおおよそ 158 × 158(小)、338 × 158(中)、338 × 354(大)です。

リクエスト例
GET https://api.example.com/widget?team=core&gw_protocol=1&gw_platform=ios&gw_family=medium&gw_canvas_width_pt=338.00&gw_canvas_height_pt=158.00&gw_aspect_ratio=2.1392&gw_context=app_preview

タイムアウトは短めで、アプリでは送信に約 8秒、受信に 12秒 です。2xx のステータスはすべて成功とみなされます。UTF-8のJSONで、できれば Content-Type: application/json を付けて応答してください。

レスポンスボディ#

本リクエストで表示する1フレームを、最大 512 KB の UTF-8 JSON オブジェクトとして返します。protocol は省略可能ですが 1 を推奨し、それ以外の null でない値は未対応です。ルートマトリクス、データのルート data、リモートレイヤーの宣言 layers の3つに分けて理解すると構造が明確になります。未知のトップレベルフィールドは無視されます。

ルート:pixels・default・layout・batch

pixels は格子の m*n の大きさを決め、各点のスタイルを記述します。layout は全体の配置を決めます。複雑な配列をサーバー側で1点ずつ組み立てる負担を減らすため、batch で同じスタイルの点をまとめ、default で個別指定のない点を補います。

pixels
型 / 値(Pixel | null)[][]
用途とルール必須のルートマトリクス。各要素は pixels[row][column] のセルのスタイルです。左上が原点。長方形、最小 1 × 1、最大4,096セル。
default
型 / 値Pixel
用途とルールセルの既定スタイル。明示セルや各 batch のスタイルで省略したプロパティの補完にも使います。
layout
型 / 値MatrixLayout
用途とルールマトリクス全体の間隔、セルの縦横比、端の余白を定義します。各点の色や形状とは別です。
batch
型 / 値{ pixel, coordinates }[]
用途とルール同じ pixel スタイルを、0始まりの [row, column] 座標で選んだ点に適用します。規則的な模様を繰り返し記述せずに生成できます。

優先順位は default → 配列順の batch → pixels の明示オブジェクトです。null は default / batch の結果を保持します。明示オブジェクトで省略したプロパティは default に戻り、batch のプロパティとは合成しません。セルのスタイルを省略するときは null を置き、要素の削除や行の短縮はしないでください。例は「ピクセルと優先順位」にあります。

データのルート:data

任意の data オブジェクト(省略時は空)には、レイヤーが読み取る内容を置きます。フィールド名や入れ子の構造は自由で、たとえば1つのパネルのレイヤーを data.orders の下にまとめられます。画像マテリアルのデータパスも、ここから画像を読み取ります(サーバー側で描画した画像を参照)。

data 自体は自由に構成できるオブジェクトにすぎず、その値だけでは表示されません。エディタで作った動的なコンテンツや、API が返すリモートレイヤーは、dataPath で指定した data 内のフィールドを読み取り、ルールに従って解釈・描画します。エディタのレイヤーやリモートレイヤーを動かすフィールドは、必ずオブジェクトで、レイヤーの種類ごとのプロトコルに従う必要があります。基本的な構造は、dataPath が指すフィールドにそのレイヤーの位置・内容・スタイルを含めることです。詳しくは下の layers と「レイヤー」の章を参照してください。

リモートレイヤー:layers

任意の layers 配列で、API が追加するレイヤーを宣言します。省略時はなしです。各項目は id、type、dataPath の3つだけを持ちます。id は remote: で始まりレスポンス内で一意、type は7種類のレイヤーのいずれか、dataPath は data 内でそのレイヤーを記述するオブジェクトを指します。

レイヤーの位置・内容・スタイルは layers の項目ではなく、dataPath が指すオブジェクトに記述します。frame、zIndex、rotation、transform がサイズとレイアウト、content が内容、style がスタイルです。リモートレイヤーはこのオブジェクトだけから作られるため、frame が必須です。

リモートレイヤーを1つ宣言する
{
  "protocol": 1,
  "pixels": [[{ "empty": true }]],
  "data": {
    "orders": {
      "badge": {
        "frame": { "right": 0.07, "top": 0.14, "width": 0.24, "height": 0.2 },
        "content": { "text": "LIVE" },
        "style": { "color": "#0B1222", "backgroundColor": "#7EDDB5", "opacity": 1, "cornerRadiusRatio": 1 }
      }
    }
  },
  "layers": [
    { "id": "remote:badge", "type": "text", "dataPath": "data.orders.badge" }
  ]
}

各レスポンスの layers はそのフレームのリモートレイヤー全体で、次の成功レスポンスで丸ごと置き換わります。差分更新の命令ではありません。エディタで追加したレイヤーはここで宣言せず、エディタで指定したデータパスでバインドします(「レイヤー」の章を参照)。

レイヤーのみの場合も、ルートマトリクスの pixels は必須です。[[{"empty": true}]] を返すとルートが空白になり、カードの素材の上にレイヤーだけを表示できます。

レスポンスボディの完全型リファレンス#

次の入れ子構造と関連する型がレスポンス全体を示します。? は省略可能、[] は配列、| は選択可能な型です。これは型のリファレンスであり、そのまま返す JSON ではありません。レイヤーの type に対応する content / style を使用します。用途、単位、有効条件は後続の節で説明します。

V1 レスポンスの型
type WidgetResponse = {
  protocol?: 1
  pixels: (Pixel | null)[][]
  default?: Pixel
  layout?: MatrixLayout
  batch?: Batch[]
  // Objects that layers point to live anywhere in data; layers only declare remote layers.
  data?: { [key: string]: JsonValue | LayerNode }
  layers?: {
    id: `remote:${string}`
    type: 'text' | 'matrix' | 'curve' | 'image' | 'icon' | 'line' | 'shape'
    dataPath: string // points to the object in data that describes this layer
  }[]
}

// The object a dataPath points to. type selects content and style:
// text → TextContent / TextStyle, matrix → MatrixContent / (none),
// curve → CurveContent / CurveStyle, image → ImageContent / ImageStyle,
// icon → IconContent / IconStyle, line → LineContent / LineStyle,
// shape → (none) / ShapeStyle
type LayerNode = {
  frame?: Frame // required for remote layers
  zIndex?: number
  rotation?: number
  transform?: { translateX?: number; translateY?: number }
  content?: object
  style?: object
}
type Frame = {
  width: number; height: number
  left?: number; right?: number; top?: number; bottom?: number
}

type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
type Color = string // #RRGGBB / #AARRGGBB
type GapMode = 'zero' | 'near' | 'default' | 'far' | 'extraFar'
type Pixel = {
  color?: Color; opacity?: number; cornerRadius?: number
  rotation?: number; halo?: number; empty?: boolean
}
type Batch = { pixel: Pixel; coordinates: [row: number, column: number][] }
type MatrixLayout = {
  gapMode?: GapMode; edgeInsetRatio?: number; cellAspectRatio?: number
  columnGapRatio?: number; rowGapRatio?: number
}

type MatrixContent = {
  pixels?: (Pixel | null)[][] // required for remote layers
  default?: Pixel; batch?: Batch[]; layout?: MatrixLayout
}

type TextContent = { text?: string | number }
type TextStyle = ChromeStyle & {
  color?: Color; textOpacity?: number; fontSizeRatio?: number
  fontWeight?: '300' | '400' | '500' | '600' | '700'
  fontFamily?: 'system' | 'systemMonospace' | 'monospace'
  fontFamilyFallback?: string[]; letterSpacing?: number; baselineRatio?: number
  horizontalAlignment?: 'left' | 'center' | 'right'
  verticalAlignment?: 'top' | 'center' | 'bottom'
  autoWrap?: boolean; paddingRatio?: number
}
type ChromeStyle = EffectsStyle & {
  backgroundColor?: Color; opacity?: number
  cornerRadiusRatio?: number
  borderColor?: Color; borderOpacity?: number; borderWidth?: number
  gradientEnabled?: boolean; gradientColors?: Color[]
  gradientStops?: number[]; gradientAngle?: number
}
type EffectsStyle = {
  maskColor?: Color; maskOpacity?: number
  vignetteIntensity?: number
  readabilityGradient?: 'none' | 'top' | 'bottom' | 'left' | 'right' | 'both'
    | 'topLeftDiagonal' | 'topRightDiagonal'
}
type ShapeStyle = ChromeStyle & { color?: Color }
type ImageContent = { url?: string }
type ImageStyle = ChromeStyle & { color?: Color }
type IconContent =
  | {
      icon?: string; iconCodePoint?: number
      iconFont?: 'phosphorBold' | 'devicon' | 'simpleIcons' | 'material'
    }
  | { url?: string }
type IconStyle = {
  color?: Color; textOpacity?: number
  iconGlowEnabled?: boolean; iconGlowColor?: Color
  iconGlowOpacity?: number; iconGlowRadius?: number
}
type LineContent = { direction?: 'horizontal' | 'vertical' }
type LineStyle = {
  color?: Color; opacity?: number; strokeWidth?: number
  pattern?: 'solid' | 'dashed' | 'dotted'; cap?: 'round' | 'butt'
  dashLength?: number; dashGap?: number
  gradientEnabled?: boolean; gradientColors?: Color[]; gradientStops?: number[]
}

type CurveContent = {
  mode?: 'samples' | 'function'
  points?: [x: number, y: number][]
  expression?: string; parameters?: { [name: string]: number }
  domain?: [number, number]; yDomain?: [number, number]
  interpolation?: 'linear' | 'stepAfter' | 'monotone'
  markers?: {
    at: { x?: number; sampleIndex?: number; offsetPt?: [number, number] }
    symbol?: { shape?: 'circle'; radiusPt?: number; fill?: Color }
    label?: string
    labelStyle?: {
      color?: Color; fontSizePt?: number
      position?: 'top' | 'bottom' | 'left' | 'right'
    }
  }[]
}
type CurveStyle = {
  color?: Color; opacity?: number; strokeWidth?: number
  lineCap?: 'round' | 'butt' | 'square'; lineJoin?: 'round' | 'bevel' | 'miter'
  dashLength?: number; dashGap?: number
  fillColor?: Color; fillOpacity?: number; plotPadding?: number
}

ピクセルのスタイル#

以下のフィールドは1つのピクセルのスタイルを表し、ルートの default、batch[].pixel、pixels[row][column] に記述します。マトリクスレイヤーの content 内の default、batch[].pixel、pixels にも適用します。

color
範囲#RRGGBB · #AARRGGBB
説明16進カラー。8桁形式はアルファが先頭(ARGB)で、CSSの #RRGGBBAA とは異なります。
opacity
範囲0 – 1
説明範囲内に丸められます。カラーのアルファと組み合わされます。
cornerRadius
範囲0 – 0.5
説明セル短辺に対する角丸半径。0 は直角、0.5 は最大の角丸。正方形なら円、長方形ならカプセル形です。
rotation
範囲度
説明45 で正方形がひし形になります。
halo
範囲0 – 1
説明点の発光強度。0 で無効。点の強調や電光表示の質感を作ります。
empty
範囲boolean
説明true でそのセルの描画をスキップします。

ルートマトリクスで default を省略すると、ピクセルは color #0A2D48、opacity 0.28、cornerRadius 0.18 から始まります。鮮やかで完全に不透明な色にしたい場合は opacity: 1 を指定してください。

ピクセルと優先順位#

各セルは、優先度の低いものから高いものへ、次の固定された順序で解決されます。

  • default(なければ組み込みの初期値)
  • 各 batch エントリ。配列の順で、後のものが優先
  • pixels 内の、明示的な null 以外の値

null は透明ではなく、ここでは上書きしないことを意味します。{"empty": true} は明示的な空白で、何も描画されませんが、セルはグリッド内の場所を保ちます。明示的なピクセルオブジェクトは、省略されたフィールドについて default にフォールバックし、下にあるバッチのスタイルにはフォールバックしません。

例
{
  "protocol": 1,
  "layout": { "gapMode": "near" },
  "default": { "color": "#18313A", "opacity": 0.34, "cornerRadius": 0.18 },
  "pixels": [
    [
      { "color": "#7CFFB2", "cornerRadius": 0.18 },
      null,
      { "color": "#F4BC54", "rotation": 45 }
    ],
    [
      { "empty": true },
      { "color": "#FFFFFF", "opacity": 0.7 },
      null
    ]
  ],
  "batch": [
    {
      "pixel": { "color": "#38A6FF", "cornerRadius": 0.5, "opacity": 1 },
      "coordinates": [
        [0, 1],
        [1, 2]
      ]
    }
  ]
}
default → batch → 明示的な pixels

バッチの座標は0始まりの [row, column] の整数ペアで、グリッド内に収まっている必要があります。範囲外の座標があると、アプリはフレームを拒否します。

グリッドのレイアウト#

以下のフィールドはマトリクス全体の配置を表します。レスポンスの layout、またはマトリクスレイヤーの content.layout に記述します。

グリッドはピクセルの縦横比を保って中央に収めます(contain)。間隔と端の余白はピクセルの短辺を基準とし、カード全体に対する割合ではありません。edgeInsetRatio、columnGapRatio、rowGapRatio の値は、1つのピクセルの短辺の長さに対する倍率です。gapMode は間隔のプリセット倍率を選び、cellAspectRatio はピクセルの幅と高さの比率を指定します。

gapMode
値zero · near · default · far · extraFar
既定値default
用途隣り合うピクセル間の間隔プリセット。順に、1つのピクセルの短辺の長さの 0、0.08、0.16、0.28、0.42 倍で、水平・垂直両方向に適用します。
edgeInsetRatio
値−0.45–0.45
既定値0
用途マトリクス周囲の余白 = 1つのピクセルの短辺の長さ × この値。正値は余白を増やし、負値は端のピクセルを描画領域の外へ広げ、はみ出す部分をクリップします。
cellAspectRatio
値0.1–10
既定値1
用途1つのピクセルの幅 ÷ 高さ。1 は正方形、1 未満は縦長、1 超は横長です。マトリクスの行数・列数は変更しません。
columnGapRatio
値0–10
既定値—
用途隣接するピクセル列の水平間隔 = 1つのピクセルの短辺の長さ × この値。gapMode の水平間隔を上書きし、省略時はプリセットを使用します。
rowGapRatio
値0–10
既定値—
用途隣接するピクセル行の垂直間隔 = 1つのピクセルの短辺の長さ × この値。gapMode の垂直間隔を上書きし、省略時はプリセットを使用します。
フィットの計算式
aspect = cellAspectRatio
short = min(aspect, 1)
gx = (columnGapRatio ?? gapModeRatio) * short
gy = (rowGapRatio ?? gapModeRatio) * short
inset = edgeInsetRatio * short
spanX = columns * aspect + (columns - 1) * gx + 2 * inset
spanY = rows + (rows - 1) * gy + 2 * inset
cellHeight = min(canvasWidth / spanX, canvasHeight / spanY)
cellWidth = cellHeight * aspect
例
{
  "protocol": 1,
  "default": { "color": "#FFFFFF", "opacity": 0.1, "cornerRadius": 0.5 },
  "layout": { "gapMode": "near", "cellAspectRatio": 0.32, "edgeInsetRatio": 0.45 },
  "pixels": [
    [
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#7EDDB5", "opacity": 1 },
      { "color": "#FFB86B", "opacity": 1 },
      { "color": "#FFB86B", "opacity": 1 },
      { "color": "#FFB86B", "opacity": 1 },
      null,
      null,
      null,
      null,
      null,
      null,
      null
    ]
  ]
}
1行のメーター:cellAspectRatio 0.32

レイヤー#

レイヤーはフラットで、入れ子も子要素もありません。ルートグリッドの上に描画され、リモートレイヤーとエディタのレイヤーは zIndex の順で合成されます。同値なら id 順です。

レイヤーの宣言

layers の各項目が1つのリモートレイヤーを宣言します。

id
型string
説明remote: で始まり、レスポンス内で一意。レイヤーを区別し、zIndex が同じ場合の描画順を決めます。
type
型string
説明text · matrix · curve · image · icon · line · shape。dataPath が指すオブジェクトの content、style で使えるフィールドを決めます。
dataPath
型string
説明data 内でこのレイヤーを記述するオブジェクトの位置。文法は下記「データパス」を参照。

フィールドの過不足、id の接頭辞の誤りや重複、未知の type、dataPath の文法エラーは、レスポンス全体を無効にします。

動的レイヤーの記述

エディタで追加してカスタムウィジェットの設定として保存されるエディタのレイヤーも、API だけが返すリモートレイヤーも、パスで data 内のフィールドを指します。前者はエディタの対応するフォーム項目にパスを入力し、後者は前述の dataPath フィールドに記述します。dataPath が指すオブジェクトは、どちらも同じ構造で、レイアウト・内容・スタイルから成ります。

frame
型object
説明位置とサイズ。下記を参照。リモートレイヤーでは必須。エディタのレイヤーでは任意で、指定するとエディタの位置とサイズを丸ごと置き換えます。
zIndex
型integer
説明重なり順。大きい値ほど上に描きます。リモートレイヤーの既定は 0。
rotation
型度
説明レイヤー全体を中心の周りに回転。リモートレイヤーの既定は 0。マトリクス内の各点の回転はピクセルスタイルの rotation を使います。
transform
型object
説明translateX、translateY:frame で配置した後の移動量。単位はレイヤー自身の幅・高さに対する比率です。
content
型object
説明レイヤーの内容。フィールドは type で決まります。「レイヤーのスタイルと内容」を参照。
style
型object
説明レイヤーのスタイル。フィールドは type で決まります。matrix にスタイルのフィールドがないのは、マトリクス全体の見え方に関わるものは基本的に layout に、個々のピクセルの描画情報は pixels の二次元配列に置くためです。既定スタイルや一括適用スタイルも content に置きます。

このオブジェクトは上表のフィールドだけを受け付け、content、style もその種類のフィールド表にあるものだけを受け付けます。省略と null はどちらも「指定なし」を意味します。

ウィジェットのレイヤーの合成規則

  • 優先順位は dataPath が指すオブジェクト > エディタの設定 > レンダラーの既定値 です。
  • content、style 内のオブジェクト値は、エディタに保存された値へフィールド単位で合成します(例:マトリクスの content.default、content.layout)。文字列・数値・真偽値・配列は丸ごと置き換えます。
  • frame は常に丸ごと置き換えます。アンカーは組み合わせて初めて意味を持ち、レスポンスの right を保存済みの left に合成しても left が優先されるだけだからです。そのため frame を返す場合は width と height が必須です。
  • 省略または null のフィールドはエディタの値をそのまま使います。0、false、空配列は有効な上書き値です。
  • レスポンスのたびに保存済みの設定から計算し直します。次のレスポンスがあるフィールドを返さなくなるとエディタの値に戻り、リクエストが失敗した場合は最後に成功したレスポンスを表示し続けます。

マトリクス・テキスト・曲線・直線・図形のレイヤーには、エディタに「現在のプレビュー」の切り替えがあります。影響するのは編集画面だけで、既定スタイルを選ぶとエディタの値だけを表示し、代替表示の見た目を調整できます。アプリのホーム、ホーム画面、StandBy では常にレスポンスの値を適用します。

エディタに保存されたレイヤー(イメージ)
[
  {
    "id": "local:title",
    "type": "text",
    "frame": { "left": 0.07, "top": 0.12, "width": 0.6, "height": 0.16 },
    "zIndex": 10,
    "content": { "text": "Orders today" },
    "style": {
      "color": "#A9C4E8",
      "fontSizeRatio": 0.1,
      "fontWeight": "500",
      "horizontalAlignment": "left"
    }
  },
  {
    "id": "local:total",
    "type": "text",
    "frame": { "left": 0.07, "top": 0.32, "width": 0.6, "height": 0.4 },
    "zIndex": 11,
    "dataPath": "data.orders.total",
    "content": { "text": "—" },
    "style": {
      "color": "#FFFFFF",
      "fontSizeRatio": 0.26,
      "fontWeight": "700",
      "horizontalAlignment": "left"
    }
  }
]
API レスポンス
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "orders": {
      "total": {
        "content": { "text": 128 },
        "style": { "color": "#7EDDB5" }
      },
      "badge": {
        "frame": {
          "right": 0.07,
          "top": 0.14,
          "width": 0.24,
          "height": 0.2
        },
        "zIndex": 12,
        "content": { "text": "LIVE" },
        "style": {
          "color": "#0B1222",
          "fontSizeRatio": 0.08,
          "fontWeight": "700",
          "horizontalAlignment": "center",
          "verticalAlignment": "center",
          "backgroundColor": "#7EDDB5",
          "opacity": 1,
          "cornerRadiusRatio": 1
        }
      }
    }
  },
  "layers": [
    {
      "id": "remote:badge",
      "type": "text",
      "dataPath": "data.orders.badge"
    }
  ]
}
エディタのレイヤー2つ(1つはデータパス付き)と、リモートレイヤー1つ

この例で local:title にはデータパスがなく、常にエディタの文字を表示します。local:total は data.orders.total にバインドされ、このオブジェクトは文字と色だけを返すため、サイズ・太さ・位置はエディタの設定のままです。remote:badge は layers で宣言され、位置・内容・スタイルをすべて data.orders.badge から読み取ります。

位置とサイズ

frame はコンテンツキャンバス内のレイヤーの位置とサイズを定義します。値はすべて割合で、水平はキャンバスの幅、垂直は高さを基準にします。例えば width: 0.5 はキャンバス幅の半分、top: 0.1 はレイヤー上端とキャンバス上端の間に高さの10%の余白を設けます。

  • 幅と高さは必須:frame を指定する場合、width と height を両方指定します。
  • 片側からの距離:left / right と top / bottom で対応する端からの距離を指定します。例えば right: 0.1 はレイヤー右端とキャンバス右端の間をキャンバス幅の10%にします。
  • 両側を指定:left と right を両方指定すると、その余白の間を埋める幅になり、width は使いません。両方が 0.1 なら幅はキャンバスの80%です。top と bottom も同様です。
  • 位置を省略:左右を両方省略すると左端から、上下を両方省略すると上端から配置します。width、height は 0.02–1.5 に制限されます。
frame の例
{
  "left": 0.1,
  "top": 0.1,
  "width": 0.4,
  "height": 0.2
}

キャンバスが200 × 100 ptなら、この例は左から20 pt、上から10 ptに、幅80 pt・高さ20 ptのレイヤーを配置します。さらに "transform": {"translateX": 0.5} を加えると、自身の幅の半分、つまり40 pt右へ移動します。

width / height
型 / 範囲0.02–1.5
用途必須。画布の対応軸に対する幅・高さの比。両側アンカーが揃うとその軸のサイズを決定します。
left / right
型 / 範囲number
用途左・右端からの距離を画布幅の比で指定。両方省略なら x = 0、right のみなら右側に固定。
top / bottom
型 / 範囲number
用途上・下端からの距離を画布高さの比で指定。両方省略なら y = 0、bottom のみなら下側に固定。
transform.translateX / translateY
型 / 範囲number
用途配置後の移動量。レイヤー自身の幅・高さが単位で、正の値は右・下へ移動します。

データパス

dataPath は data から始まり、フィールドと数値インデックスをたどります。例:data.orders.total、data.cards[0]。式、ワイルドカード、文字列キーは使えません。必ずオブジェクトを指す必要があります。

使用できる文法
^data(?:\.[A-Za-z_][A-Za-z0-9_]*|\[[0-9]+\])*$

オブジェクトにエラーがある場合

このオブジェクトのエラーは対応するレイヤーだけに影響し、レスポンス全体は有効なままです。パスが存在しない、オブジェクトを指していない、オブジェクトやレイヤーの種類が対応しないフィールドがある、値の型や選択肢が不正、frame に width か height がない、リモートレイヤーに frame がない、リモートマトリクスに content.pixels がない場合:

  • エディタのレイヤーは保存済みの状態に戻り、アプリはそのレイヤーの「データパス」の下に理由を表示します。
  • リモートレイヤーは描画されません。
  • ルートグリッドとほかのレイヤーは通常どおり表示されます。

フィールド表の範囲外の数値はエラーではなく、レンダラーが範囲内に丸めます。

例
{
  "protocol": 1,
  "default": { "color": "#8EC8FB", "opacity": 0.06, "cornerRadius": 0.5 },
  "layout": { "gapMode": "far" },
  "pixels": [
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
  ],
  "batch": [
    {
      "pixel": { "color": "#7EDDB5", "opacity": 1, "halo": 0.5 },
      "coordinates": [
        [7, 17]
      ]
    }
  ],
  "data": {
    "orders": {
      "title": {
        "frame": {
          "left": 0.07,
          "top": 0.12,
          "width": 0.6,
          "height": 0.16
        },
        "zIndex": 10,
        "content": { "text": "Orders today" },
        "style": {
          "color": "#A9C4E8",
          "fontSizeRatio": 0.1,
          "fontWeight": "500",
          "horizontalAlignment": "left"
        }
      },
      "total": {
        "frame": {
          "left": 0.07,
          "top": 0.32,
          "width": 0.6,
          "height": 0.4
        },
        "zIndex": 11,
        "content": { "text": 128 },
        "style": {
          "color": "#FFFFFF",
          "fontSizeRatio": 0.26,
          "fontWeight": "700",
          "horizontalAlignment": "left"
        }
      },
      "badge": {
        "frame": {
          "right": 0.07,
          "top": 0.14,
          "width": 0.24,
          "height": 0.2
        },
        "zIndex": 5,
        "style": {
          "gradientEnabled": true,
          "gradientColors": ["#2A63D4", "#56B9DD"],
          "cornerRadiusRatio": 1,
          "borderColor": "#D7F6FF",
          "borderOpacity": 0.46,
          "borderWidth": 1
        }
      },
      "trend": {
        "frame": {
          "right": 0.07,
          "top": 0.16,
          "width": 0.24,
          "height": 0.16
        },
        "zIndex": 6,
        "content": {
          "icon": "ph_trend_up",
          "iconCodePoint": 58542,
          "iconFont": "phosphorBold"
        },
        "style": { "color": "#FFFFFF" }
      }
    }
  },
  "layers": [
    {
      "id": "remote:orders-title",
      "type": "text",
      "dataPath": "data.orders.title"
    },
    {
      "id": "remote:orders-total",
      "type": "text",
      "dataPath": "data.orders.total"
    },
    {
      "id": "remote:badge",
      "type": "shape",
      "dataPath": "data.orders.badge"
    },
    {
      "id": "remote:trend",
      "type": "icon",
      "dataPath": "data.orders.trend"
    }
  ]
}
リモートレイヤー4つ:テキスト2つ、図形、アイコン

レイヤーのスタイルと内容#

dataPath が指すオブジェクトは content で内容を、style で外観を記述します。以下は種類別のフィールドで、content. / style. の接頭辞はそのオブジェクト内の位置を表します。色は #RRGGBB または alpha が先頭の #AARRGGBB、pt は iOS の論理ピクセル。表の既定値はリモートレイヤーで省略した場合の値です。エディタのレイヤーでは省略したフィールドにエディタの設定を使い、新規レイヤーのプリセットはこれらの既定値と異なる場合があります。

各レイヤー種類で共通のスタイルフィールド

この表は text、image、shape に適用します。opacity の意味は各表を参照。他の種類はそれぞれの表にあるフィールドを使用します。

style.backgroundColor
型 / 値Color
用途とルールレイヤー背景を塗る色。テキスト背景は style.opacity を上げると表示され、図形は省略時に style.color を使います。
style.cornerRadiusRatio
型 / 値0–1
用途とルール四隅を丸くし、その輪郭で内容をクリップします。半径 = レイヤー短辺 × 値 ÷ 2。0(省略時の既定値)は直角、1 は正方形なら円、長方形ならカプセル形。
style.borderColor
型 / 値Color
用途とルールレイヤー輪郭の内側に描く枠線の色。既定は白。borderWidth と borderOpacity が両方0より大きいと表示。
style.borderOpacity
型 / 値0–1
用途とルール枠線のみの不透明度。0 は非表示、1 は色の不透明度をそのまま使用。既定 0。背景や内容には影響しません。
style.borderWidth
型 / 値number · pt
用途とルール内側に占める枠線幅(pt)。既定 0。文字の有効領域も同じ幅だけ狭まります。0–8 pt を推奨し、テキストではこの範囲に制限します。
style.gradientEnabled
型 / 値boolean
用途とルール背景を線形グラデーションにします。API 省略時は無効。エディタで新規作成した図形は通常 true を保存します。
style.gradientColors
型 / 値Color[]
用途とルール方向に沿って並ぶ2〜3色。先頭が開始色、末尾が終了色で、中間位置は gradientStops で指定。gradientEnabled の有効化が必要。
style.gradientStops
型 / 値number[] · 0–1
用途とルール各色の位置を1対1で指定。0 は開始、1 は終点。[0, 0.2, 1] なら中間色は全体の20%地点。昇順を使用し、省略・個数不一致なら均等配置。
style.gradientAngle
型 / 値number · degrees
用途とルール背景グラデーションの方向だけを度単位で回転し、レイヤーや内容は回しません。0 は左から右、90 は上から下。API の既定は 0、新規図形は 135。
style.maskColor
型 / 値Color
用途とルール内容の上全体に重ねる色。既定は黒。強度は maskOpacity で指定し、暗色で暗くしたり別色で色味を重ねたりできます。
style.maskOpacity
型 / 値0–0.6
用途とルール均一マスクの強度。0 で無効、大きいほどマスクが濃くなります。既定 0。元の内容の不透明度を直接下げる値ではありません。
style.vignetteIntensity
型 / 値0–2
用途とルール周辺と四隅の暗化強度。0 で無効、大きいほど周辺が暗く、中央は比較的明るいままです。既定 0。
style.readabilityGradient
型 / 値string
用途とルール指定辺から内側へ透明になる黒いグラデーションで、上に重ねる文字を読みやすくします。none は無効(既定)。top・bottom・left・right は各辺、both は上下、topLeftDiagonal・topRightDiagonal は指定上角から対角方向へ薄くなります。
共通スタイルの構造狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
共通スタイルの構造破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / CHROME / DETAILレイヤーの枠00.51frame.widthbackgroundColor · opacity1gradientColors · Stops · Angle2maskColor · maskOpacity3cornerRadiusRatio4borderWidth · Color · Opacity5readability · vignette6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

text · テキスト

  • 内容:content.text が表示する文字です。文字列のほか、数値もそのまま表示できます。エディタのレイヤーでは、返されるまでエディタの静的テキストを表示します。
  • スタイル:style で字体・サイズ・配置を制御。背景・角丸・枠線は共通フィールドを使用。
content.text
型 / 値string · number
用途とルール表示する文字。数値はそのまま文字にし、128 は「128」と表示します。リモートレイヤーで省略すると空文字。
style.color
型 / 値Color
用途とルール文字色。既定は白。背景色には影響しません。
style.textOpacity
型 / 値0–1
用途とルール文字の筆画だけの不透明度。既定 1。0 で文字を隠し、1 で文字色の alpha を保持。背景には style.opacity を使用。
style.opacity
型 / 値0–1
用途とルールテキスト背景の不透明度。既定 0。文字自体やレイヤー全体の値ではありません。
style.fontSizeRatio
型 / 値0.02–0.28
用途とルール文字サイズ = コンテンツキャンバス全体の短辺 × 値。既定 0.12。短辺160 ptで 0.1 なら16 ptで、文字枠の高さの10%ではありません。
style.fontWeight
型 / 値string
用途とルール文字の筆画の太さ。文字列 "300"、"400"、"500"、"600"、"700" の順に太くなり、既定は "400"。
style.fontFamily
型 / 値string
用途とルール字形を選択。system はシステムの可変幅字体(既定)、systemMonospace は計数や時計向きのシステム等幅、monospace は Menlo。
style.fontFamilyFallback
型 / 値string[]
用途とルール不足する字形を補うインストール済みフォント名の候補。ネットワークからは取得しません。
style.letterSpacing
型 / 値number · pt
用途とルール通常の字間に追加する距離(pt)。iOS の既定は 0。正値で広く、負値で狭くなり、文字サイズは変えません。
style.baselineRatio
型 / 値number
用途とルール先頭行ベースラインの位置。内容上端からの距離 = 文字サイズ × 値。複数の文字レイヤーを揃える際に使用し、省略時はフォント本来の基準線。
style.horizontalAlignment
型 / 値left / center / right
用途とルール余白と枠線を除いた領域内で左・中央・右に揃えます。既定 right。枠自体は移動しません。
style.verticalAlignment
型 / 値top / center / bottom
用途とルール有効領域内で上・中央・下に配置。既定 top。frame.top や frame.bottom は変更しません。
style.autoWrap
型 / 値boolean
用途とルール有効幅での自動折り返し。既定 false。明示改行は保持し、はみ出しはクリップします。
style.paddingRatio
型 / 値0–0.3
用途とルール各辺の余白 = 文字レイヤー枠の短辺 × 値(既定 0)+枠線幅。大きいほど文字領域は狭まります。

文字やスタイルが変わると、実行時に新しい文字・サイズ・改行・余白・枠線に必要な最小の文字領域を計算し、コンテンツキャンバス内に収めます。これでホーム画面が古い領域で文字を切ることを防ぎます。この計算で frame は書き換わりません。

テキスト:文字と内容領域狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
テキスト:文字と内容領域破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / TEXT / DETAILレイヤーの枠GlanceAPI → 128frame.widthcontent.text1fontSizeRatio · fontWeight2paddingRatio · autoWrap3color · textOpacity4letterSpacing · baselineRatio5horizontal · verticalAlignment6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

例
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "parcels": {
      "frame": { "left": 0.08, "top": 0.22, "width": 0.84, "height": 0.56 },
      "content": { "text": 128 },
      "style": {
        "color": "#D9F2F7",
        "textOpacity": 1,
        "fontSizeRatio": 0.26,
        "fontWeight": "600",
        "fontFamily": "systemMonospace",
        "horizontalAlignment": "center",
        "verticalAlignment": "center",
        "autoWrap": false,
        "paddingRatio": 0,
        "backgroundColor": "#397DA0",
        "opacity": 0.3,
        "cornerRadiusRatio": 0,
        "borderColor": "#90BBCD",
        "borderOpacity": 0.6,
        "borderWidth": 1
      }
    }
  },
  "layers": [
    { "id": "remote:count", "type": "text", "dataPath": "data.parcels" }
  ]
}
数値・文字配置・背景

matrix · マトリクス

  • 内容:content はルートと同じ構造で、pixels、default、batch、layout から成り、意味もルールもルートと同じです。実際の行列数は pixels で決まります。
  • スタイル:マトリクスには style のフィールドがありません。点の外観は content.default、batch、pixels に、間隔と余白は content.layout に記述します。
content.pixels
型 / 値(Pixel | null)[][]
用途とルール局所マトリクスの内容。ルートの pixels と同じルール。リモートマトリクスでは必須で、エディタのマトリクスは返されるまで既定サンプルを表示します。
content.default
型 / 値Pixel
用途とルール点の既定スタイル。エディタのマトリクスではエディタの既定スタイルにフィールド単位で合成し、リモートマトリクスの基準値は color #5EA2FF、opacity 0.82、cornerRadius 0.16 です。
content.batch
型 / 値{ pixel, coordinates }[]
用途とルール一括スタイル。ルートの batch と同じルールで、content.pixels と一緒に返す必要があります。
content.layout
型 / 値MatrixLayout
用途とルール間隔・セルの縦横比・端の余白。フィールドは「マトリクスの配置」と同じで、エディタの設定にフィールド単位で合成します。gapMode 未指定時は near。
マトリクス:ピクセルと配置狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
マトリクス:ピクセルと配置破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / MATRIX / DETAILレイヤーの枠pixels[row][column]frame.widthcontent.pixels1color · opacity · halo2edgeInsetRatio3cellAspectRatio4columnGapRatio · rowGapRatio5cornerRadius · rotation6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

優先順位は高い順に、pixels の明示値、batch、レスポンスの default / layout、エディタの default / layout です。エディタで設定する行数・列数は既定サンプル専用です。有効な pixels がない場合、合成後の既定スタイルで安定したサンプルを描き、API の準備前にレイヤーが消えないようにします。有効な pixels が拡大縮小・並べ替えされることはありません。

例
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "title": {
      "frame": { "left": 0.06, "top": 0.08, "width": 0.88, "height": 0.14 },
      "content": { "text": "Contributions · 20 weeks" },
      "style": {
        "color": "#A9C4E8",
        "fontSizeRatio": 0.09,
        "fontWeight": "500",
        "horizontalAlignment": "left"
      }
    },
    "heatmap": {
      "frame": { "left": 0.06, "top": 0.28, "width": 0.88, "height": 0.64 },
      "content": {
        "default": { "color": "#FFFFFF", "opacity": 0.08, "cornerRadius": 0.25 },
        "layout": { "gapMode": "near" },
        "pixels": [
          [
            { "color": "#7EDDB5", "opacity": 0.85 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.7448688173552516 },
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.7545944628656934 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.9830690317402184 },
            null,
            null
          ],
          [
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.5229617408287728 },
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.9544462806264128 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.6555567507870917 },
            null,
            null,
            null
          ],
          [
            { "color": "#7EDDB5", "opacity": 0.7222294489865844 },
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.6638935382087501 },
            { "color": "#7EDDB5", "opacity": 0.6413972526463148 },
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.5311184461090914 },
            { "color": "#7EDDB5", "opacity": 0.8927628180779044 },
            { "color": "#7EDDB5", "opacity": 0.8698567704801822 },
            { "color": "#7EDDB5", "opacity": 0.5938312809225924 },
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.710199232267813 },
            { "color": "#7EDDB5", "opacity": 0.9951574673001937 },
            { "color": "#7EDDB5", "opacity": 0.974044818430527 }
          ],
          [
            null,
            { "color": "#7EDDB5", "opacity": 0.6708227479019796 },
            { "color": "#7EDDB5", "opacity": 0.9875732368676824 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.6668848538904473 },
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.5228328839421528 },
            { "color": "#7EDDB5", "opacity": 0.5214774850893487 },
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.7065983124568725 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.972886955341248 },
            null
          ],
          [
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.9993158218339733 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.7303804474628726 },
            null,
            null
          ],
          [
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.8229182097281214 },
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.6114321719061702 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.8256594886799811 },
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.7008502184288221 }
          ],
          [
            { "color": "#7EDDB5", "opacity": 0.5000626562032289 },
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.6949160968071824 },
            { "color": "#7EDDB5", "opacity": 0.8072480735714076 },
            { "color": "#7EDDB5", "opacity": 0.5647999972978959 },
            null,
            null,
            null,
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.6708958418347376 },
            { "color": "#7EDDB5", "opacity": 0.8120773109877517 },
            { "color": "#7EDDB5", "opacity": 0.6297695437501125 },
            null,
            null,
            { "color": "#7EDDB5", "opacity": 0.5926441641837852 },
            { "color": "#7EDDB5", "opacity": 0.9642279221859965 },
            { "color": "#7EDDB5", "opacity": 1 },
            { "color": "#7EDDB5", "opacity": 0.8892938715888167 }
          ]
        ]
      }
    }
  },
  "layers": [
    { "id": "remote:title", "type": "text", "dataPath": "data.title" },
    { "id": "remote:heatmap", "type": "matrix", "dataPath": "data.heatmap" }
  ]
}
局所ヒートマップ

curve · 曲線

  • 内容:content で点または関数と、曲線に追従するマーカーを宣言します。曲線の内容は常に API から来ます。エディタの曲線レイヤーが保存するのは、データパス・初期位置・代替スタイルだけです。
  • スタイル:style で線・塗り・描画領域の余白を制御します。

曲線の内容:dataPath が指すオブジェクトの content のプロパティ。

content.mode
型 / 値samples / function
用途とルール曲線の入力方式。samples は points を接続、function は domain 内で expression を計算。式があれば関数、なければサンプルが既定。
content.points
型 / 値[x, y][]
用途とルール最大1,024点の数値ペア。
content.expression
型 / 値string · ≤512
用途とルールx の数式。+ - * / ^、括弧、sin cos exp log abs sqrt min max、pi、e、名前付きパラメータに対応。任意スクリプトは実行しません。
content.parameters
型 / 値object · ≤64
用途とルール数式の名前付き数値パラメータを最大64個。値は ±1e9 に制限。
content.domain
型 / 値[min, max]
用途とルールX データ範囲 [最小, 最大] を描画領域の左右端へ対応させます。省略時は点から推定し、点がなければ [0, 1]。
content.yDomain
型 / 値[min, max]
用途とルールY 表示範囲 [最小, 最大]。最小が下端、最大が上端。[0, 100] の50は高さの中央です。省略時は点から推定し、点がなければ [0, 1]。
content.interpolation
型 / 値linear / stepAfter / monotone
用途とルール点の間の形。linear は直線、stepAfter は前の値を保ってから次へ跳び、monotone は過度な振れを避けて滑らかに接続(既定)。
content.markers
型 / 値array · ≤32
用途とルール曲線上の円とラベルのマーカー。最大32個。フィールドは次表。

マーカー:各 content.markers[] オブジェクトのプロパティ。

at.x
型 / 値number
用途とルールマーカーの X データ値。高さは曲線から計算。sampleIndex と片方を使用。
at.sampleIndex
型 / 値integer
用途とルールサンプルモードで追従する点の0始まりの番号。X 昇順の配列を使用します。
at.offsetPt
型 / 値[dx, dy] · −64–64
用途とルール曲線点からのラベル移動量 [水平, 垂直](pt)。既定 [0, 0]。正値は右・下へ移動し、円や曲線は動かしません。
symbol.shape
型 / 値circle
用途とルール現在のマーカー形状は円です。
symbol.radiusPt
型 / 値1–16
用途とルール円の半径(pt)。既定 4。
symbol.fill
型 / 値Color
用途とルール円の塗り色。既定 #E6EDC9。
label
型 / 値string
用途とルール任意のラベル。独立した入れ子のテキストレイヤーではありません。
labelStyle.color
型 / 値Color
用途とルールラベル色。既定 #E6EEEE。
labelStyle.fontSizePt
型 / 値6–32
用途とルールラベル文字サイズ(pt)。既定 10。
labelStyle.position
型 / 値top / bottom / left / right
用途とルール点に対するラベルの位置。既定 top。

曲線スタイル:dataPath が指すオブジェクトの style のプロパティ。

style.color
型 / 値Color
用途とルール曲線の線色。領域塗りは別の fillColor。
style.opacity
型 / 値0–1
用途とルール曲線全体の不透明度。既定 1。領域塗りやマーカーにも乗算。
style.strokeWidth
型 / 値0.25–12 · pt
用途とルール曲線の線幅(pt)。既定 2。
style.lineCap
型 / 値round / butt / square
用途とルール線端形状。round は半円を追加(既定)、butt は座標で平坦に終わり、square は半線幅だけ四角く延長。
style.lineJoin
型 / 値round / bevel / miter
用途とルール折れ点の接合。round は丸(既定)、bevel は角を削り、miter は尖らせます。折線や階段状の線で分かりやすい効果です。
style.dashLength
型 / 値0.5–64 · pt
用途とルール破線の線分長(pt)。省略で実線。
style.dashGap
型 / 値0–64 · pt
用途とルール破線の間隔(pt)。既定 4。破線でのみ有効。
style.fillColor
型 / 値Color
用途とルール曲線と描画領域下端の間の色。省略時は線色。
style.fillOpacity
型 / 値0–1
用途とルール塗り自体の不透明度。既定 0(無効)で style.opacity をさらに乗算。線幅や線自体の不透明度は変えません。
style.plotPadding
型 / 値0–48 · pt
用途とルール枠の四辺と内部描画領域の距離(pt)。既定 0。大きいほど描画範囲は狭まり、domain / yDomain をその内側へ対応させます。
曲線:データ座標と描画領域狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
曲線:データ座標と描画領域破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / CURVE / DETAILレイヤーの枠010100Nowframe.widthdomain · yDomain1points · interpolation2fillColor · fillOpacity3markers · symbol · label4strokeWidth · lineCap · lineJoin5plotPadding6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

マーカーは円と文字を描き、軸は自動生成しません。共通形式は [x, y]、label、labelStyle.position。画像・アイコン注釈は別レイヤーで組み合わせます。

例
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "activity": {
      "frame": { "left": 0.05, "top": 0.08, "width": 0.9, "height": 0.84 },
      "content": {
        "mode": "samples",
        "points": [
          [0, 30],
          [2, 18],
          [4, 65],
          [6, 45],
          [8, 90],
          [10, 70]
        ],
        "domain": [0, 10],
        "yDomain": [0, 100],
        "interpolation": "linear",
        "markers": [
          {
            "at": { "x": 8 },
            "symbol": { "shape": "circle", "radiusPt": 4, "fill": "#E1EEE0" },
            "label": "Now",
            "labelStyle": {
              "color": "#E1EEE0",
              "fontSizePt": 9,
              "position": "top"
            }
          }
        ]
      },
      "style": {
        "color": "#A7DAD7",
        "opacity": 1,
        "strokeWidth": 2,
        "lineCap": "round",
        "lineJoin": "round",
        "fillColor": "#77B6BB",
        "fillOpacity": 0.22,
        "plotPadding": 8
      }
    }
  },
  "layers": [
    {
      "id": "remote:activity",
      "type": "curve",
      "dataPath": "data.activity"
    }
  ]
}
サンプル点・領域塗り・曲線マーカー

image · 画像

  • 内容:content.url が画像です。HTTPS URL、data:image/…;base64,、または Base64 文字列を指定します。
  • スタイル:URL 画像は cover で枠を埋めて切り取り。角丸・枠線・マスクは共通フィールド。エディタのローカル画像は contain。
content.url
型 / 値string
用途とルール画像 URL または Base64 データ。エディタの画像レイヤーでソースを「API データ」にすると、そのオブジェクトのこのフィールドを読み取ります。url を返すと、その画像を表示します。サーバーから端末上のファイルパスは参照できません。
style.opacity
型 / 値0.1–1
用途とルール画像自体の不透明度。既定 1。0.5 なら半透明で下が透け、最小は 0.1。マスクや枠線の不透明度は変えません。
style.color
型 / 値Color
用途とルール読み込み中のプレースホルダー色。画像の着色ではありません。
画像:配置・クリップ・マスク狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
画像:配置・クリップ・マスク破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / IMAGE / DETAILレイヤーの枠frame.widthcontent.url1frame → cover crop2opacity · maskOpacity3cornerRadiusRatio · borderWidth4vignetteIntensity5readabilityGradient6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

ネイティブのリモート画像は image/*、最大6 MB、長辺1,200 px に縮小。Base64 は復号後4 MBまでですが、埋め込み分も JSON 全体の512 KB制限を受けます。

icon · アイコン

  • 内容:内蔵アイコンは content.icon、content.iconCodePoint、content.iconFont の組で同じアイコンを指定し、画像アイコンは content.url を使います。1つのオブジェクトはどちらか一方だけを返します。
  • スタイル:アイコンは縦横比を保って frame 内に全体を収め、textOpacity で不透明度を制御します。字形の色と発光は下表のフィールドを使います。
content.icon
型 / 値string
用途とルールph_activity などの安定した識別名。アプリのプレビューがアイコンを検索するために使用します。下の2フィールドと同じアイコンを指定し、セットで記述します。名前とコードの二者択一ではありません。
content.iconCodePoint
型 / 値integer
用途とルールiconFont のフォント内で字形を指定する Unicode コードポイント。ホーム画面の描画に使用し、ph_activity は 57344。一覧の通し番号ではありません。省略時は一部の旧 Material 名だけにフォールバックし、現在の全アイコンには対応しません。
content.iconFont
型 / 値phosphorBold / devicon / simpleIcons / material
用途とルールホーム画面が使用する内蔵フォントの識別子。iconCodePoint と一致する組が必要です。例:ph_activity は phosphorBold。同じコードでもフォントが違うと字形が変わります。外部フォントは読み込みません。
content.url
型 / 値string
用途とルール画像アイコンの URL または Base64 データ。contain で全体を表示します。content.icon と同時には返せません。エディタのアイコンレイヤーでソースを「API データ」にすると、そのオブジェクトは内蔵アイコンと url のどちらでも返せます。
style.color
型 / 値Color
用途とルール静的字形の色。既定は白。URL/API の画像アイコンは着色しません。
style.textOpacity
型 / 値0–1
用途とルール字形・画像両方に使うアイコン全体の不透明度。既定 1。0 で非表示。
style.iconGlowEnabled
型 / 値boolean
用途とルール発光の有効化。既定 false。静的字形とエディタのローカル SVG に対応し、リモート画像には適用しません。
style.iconGlowColor
型 / 値Color
用途とルール周りのぼかした光の色で、字形自体の色は変えません。省略時はアイコン色。iconGlowEnabled が必要。
style.iconGlowOpacity
型 / 値0–1
用途とルール光自体の不透明度。既定 0.65。大きいほど明るく見え、アイコン全体の textOpacity も最終表示に影響します。
style.iconGlowRadius
型 / 値0.01–0.16
用途とルールアイコン短辺に対するぼかし半径。既定 0.07。大きいほど光が広がりますが、字形サイズは変わりません。

専用ページでアプリと同じ分類・順序のアイコンを閲覧・検索し、対応する icon、iconCodePoint、iconFont を確認できます。スタイルをリアルタイムで調整し、レイヤー JSON をコピーできます。 アイコン検索とプレビューを開く

アイコン:字形・サイズ・発光狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
アイコン:字形・サイズ・発光破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / ICON / DETAILレイヤーの枠frame.widthicon · codePoint · font1color · textOpacity2frame → contain3iconGlowEnabled · Color4iconGlowRadius5iconGlowOpacity6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

ネイティブのリモート画像アイコンは最大1 MB、長辺512 px に縮小。エディタでアップロードしたローカルアイコンはエディタで管理します。

例
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "icons": [
      {
        "frame": {
          "left": 0.1,
          "top": 0.25,
          "width": 0.2,
          "height": 0.5
        },
        "content": {
          "icon": "ph_activity",
          "iconCodePoint": 57344,
          "iconFont": "phosphorBold"
        },
        "style": { "color": "#A7DAD7", "textOpacity": 1 }
      },
      {
        "frame": {
          "left": 0.4,
          "top": 0.25,
          "width": 0.2,
          "height": 0.5
        },
        "content": {
          "icon": "ph_lightning",
          "iconCodePoint": 58078,
          "iconFont": "phosphorBold"
        },
        "style": { "color": "#F0C778", "textOpacity": 1 }
      },
      {
        "frame": {
          "left": 0.7,
          "top": 0.25,
          "width": 0.2,
          "height": 0.5
        },
        "content": {
          "icon": "ph_trend_up",
          "iconCodePoint": 58542,
          "iconFont": "phosphorBold"
        },
        "style": { "color": "#A7DAD7", "textOpacity": 0.45 }
      }
    ]
  },
  "layers": [
    {
      "id": "remote:activity",
      "type": "icon",
      "dataPath": "data.icons[0]"
    },
    {
      "id": "remote:lightning",
      "type": "icon",
      "dataPath": "data.icons[1]"
    },
    {
      "id": "remote:trend",
      "type": "icon",
      "dataPath": "data.icons[2]"
    }
  ]
}
字形・色・不透明度の違い

line · 直線

  • 内容:content.direction で枠中央を通る水平・垂直線を選択。他の角度は rotation を使用。
  • スタイル:frame が位置と長さ、strokeWidth が太さ、pattern が実線・破線・点線を指定。
content.direction
型 / 値horizontal / vertical
用途とルール方向。既定は水平。横線は枠の高さの中央、縦線は幅の中央を通ります。
style.color
型 / 値Color
用途とルール線色。既定は白。グラデーション有効時は gradientColors を使用。
style.opacity
型 / 値0–1
用途とルール線全体の不透明度。既定 1。グラデーション色や円点にも適用。
style.strokeWidth
型 / 値0.5–8 · pt
用途とルール線幅(pt)。既定 2。枠は長さを決め、太さはこの値で指定。
style.pattern
型 / 値solid / dashed / dotted
用途とルール描画方式。solid は実線(既定)、dashed は実線部分と空隙、dotted は strokeWidth を直径とする円を繰り返します。
style.cap
型 / 値round / butt
用途とルール実線と破線線分の端。round は半円(既定)、butt は平坦。点線は常に円で、この指定は使いません。
style.dashLength
型 / 値1–32 · pt
用途とルール破線の各線分長(pt)。既定 6。dashed のみで使用。
style.dashGap
型 / 値0–32 · pt
用途とルール破線の線分や円点の間の空白長(pt)。既定 4。大きいほど疎になり、solid では使いません。
style.gradientEnabled
型 / 値boolean
用途とルールグラデーションで線を描画。既定 false。
style.gradientColors
型 / 値Color[]
用途とルール順に並ぶ2〜3色。gradientEnabled が必要。ネイティブは枠の左から右へ、アプリの縦線プレビューは上から下へ変化。
style.gradientStops
型 / 値number[] · 0–1
用途とルール各色に対応する昇順の位置。0 は開始、1 は終点。省略時は均等配置。
直線:線幅・破線・線端狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
直線:線幅・破線・線端破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / LINE / DETAILレイヤーの枠dashgapframe.widthdirection · frame1strokeWidth2pattern · dashLength · dashGap3cap4gradientColors · gradientStops5color · opacity6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

例
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "solid": {
      "frame": { "left": 0.1, "top": 0.32, "width": 0.8, "height": 0.04 },
      "content": { "direction": "horizontal" },
      "style": {
        "color": "#A4DAE3",
        "opacity": 0.55,
        "strokeWidth": 1,
        "pattern": "solid",
        "cap": "butt"
      }
    },
    "dashed": {
      "frame": { "left": 0.1, "top": 0.62, "width": 0.8, "height": 0.04 },
      "content": { "direction": "horizontal" },
      "style": {
        "color": "#A4DAE3",
        "opacity": 1,
        "strokeWidth": 3,
        "pattern": "dashed",
        "cap": "butt",
        "dashLength": 8,
        "dashGap": 6
      }
    }
  },
  "layers": [
    { "id": "remote:solid", "type": "line", "dataPath": "data.solid" },
    { "id": "remote:dashed", "type": "line", "dataPath": "data.dashed" }
  ]
}
実線と破線の太さ・間隔の比較

shape · 図形

  • 内容:レイヤー自体が角丸矩形で、content は不要。パネル・バッジ・進捗バーの背景などに使用。
  • スタイル:style で塗りと枠線を指定。角丸・グラデーション・マスクは共通フィールド。グラデーションや枠線は明示設定が必要。
content
型 / 値object
用途とルール内容値は不要。レイヤー自体が塗りと枠線を持つ角丸矩形です。
style.color
型 / 値Color
用途とルール図形の単色塗り。既定 #2A63D4。backgroundColor があれば優先し、グラデーション有効時は gradientColors で塗ります。
style.opacity
型 / 値0–1
用途とルール内部の塗りだけの不透明度。既定 1。0 で塗りを隠しても枠線は残せ、枠線は borderOpacity で別に制御。
図形:塗り・角丸・枠線狭い画面では横にスクロールして全体を確認できます · 注釈を選ぶと対応箇所を強調します
図形:塗り・角丸・枠線破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。GLANCE / SHAPE / DETAILレイヤーの枠00.51frame.widthcolor · backgroundColor · opacity1gradientEnabled · gradientColors2gradientStops · gradientAngle3cornerRadiusRatio4borderWidth5borderColor · borderOpacity6

破線や寸法線はフィールドの説明用で、レイヤー自体には描画されません。

例
{
  "protocol": 1,
  "pixels": [
    [
      { "empty": true }
    ]
  ],
  "data": {
    "badge": {
      "frame": { "left": 0.12, "top": 0.22, "width": 0.76, "height": 0.56 },
      "style": {
        "opacity": 1,
        "cornerRadiusRatio": 1,
        "gradientEnabled": true,
        "gradientColors": ["#225B80", "#78B6BA"],
        "gradientStops": [0, 1],
        "gradientAngle": 0,
        "borderColor": "#BCE3E4",
        "borderOpacity": 0.9,
        "borderWidth": 2
      }
    }
  },
  "layers": [
    { "id": "remote:badge", "type": "shape", "dataPath": "data.badge" }
  ]
}
カプセル形のグラデーション塗りと独立した枠線

サーバー側で描画した画像#

ドットやレイヤーで内容を記述するほかに、描画をすべてサーバーに任せることもできます。使い慣れたグラフライブラリ、テンプレートエンジン、ヘッドレスブラウザでウィジェット全体を 1 枚の画像にし、data で返します。ウィジェットはそれを「画像」マテリアルとしてキャンバス全体に表示するため、レイヤーの設定も、このページの描画構文の習得も不要です。

既存のレポートや画像生成の仕組みがある場合、複雑なグラフや独自フォント、自由なレイアウトが必要な場合、アプリ側の設定を変えずにサーバーだけで見た目を調整したい場合に向いています。

アプリでの設定

  • エディタでマテリアルを「画像」にする
  • 画像ソースを「API」にする
  • データパスに画像のフィールドを入力する(例:data.cover)
  • 画像の上に文字レイヤーを重ねる場合は、必要に応じて読みやすさのグラデーションをオンにする

レスポンス

画像だけを返す
{
  "protocol": 1,
  "pixels": [[{ "empty": true }]],
  "data": {
    "cover": "https://cdn.example.com/widgets/orders-medium@3x.png"
  }
}
HTTPS URL
image/* を返し、最大 6 MB。ホーム画面では長辺 1,200 px に縮小されます。
data:image/…;base64, または Base64 文字列
レスポンスに埋め込むため小さな画像向け。JSON レスポンス全体は 512 KB までです。

このデータパスはマテリアルの設定で、画像の文字列を直接指します。レイヤーではないため、オブジェクトにする必要はありません。

画像は cover でウィジェット全体を覆い、はみ出した部分は切り取られます。リクエストの gw_canvas_width_pt × gw_canvas_height_pt の縦横比で、ホーム画面でも鮮明になるよう 2〜3 倍のピクセル密度で描画してください。

pixels は引き続き必須です。[[{"empty": true}]] を返せば画像だけが表示されます。画像の上にドットやレイヤーを重ねることもでき、頻繁に変わる数値はネイティブの文字で表示できます。
画像内の文字は、システムのフォントや「文字を太くする」などの設定に追従しません。URL が同じまま内容が変わる場合は、古い画像のキャッシュが使われないよう、URL にバージョンやタイムスタンプを付けてください。

リクエスト署名#

アプリでウィジェットにシークレットを設定すると、各リクエストにサーバーで検証できる2つのヘッダーを付けられます。リクエストが無関係な第三者や悪意ある第三者ではなく、Glance Widgets のウィジェットから送られたことを証明するために使います。シークレット自体が送信されることはありません。

X-GlanceWidgets-Timestamp
Unix時間(ミリ秒)を10進数の文字列で。
X-GlanceWidgets-Signature
Base64(HMAC-SHA256(key = secret, message = timestamp + "\n" + secret))、すべてUTF-8。
ヘッダー名には現在の製品識別子 GlanceWidgets を使用します。署名の対象はタイムスタンプとシークレットのみで、URL、クエリ、ボディは含まれません。

サーバー側では、古いタイムスタンプ(例:±5分を超えるもの)を拒否し、署名を再計算して、定数時間で比較してください。

verify.ts
import crypto from 'node:crypto'

const SECRET = process.env.GLANCE_SECRET          // same value as in the app
const WINDOW_MS = 5 * 60 * 1000

export function verify(req) {
  const ts = req.get('X-GlanceWidgets-Timestamp')
  const sig = req.get('X-GlanceWidgets-Signature')
  if (!ts || !sig || Math.abs(Date.now() - Number(ts)) > WINDOW_MS) return false

  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(`${ts}\n${SECRET}`)
    .digest('base64')

  const a = Buffer.from(sig)
  const b = Buffer.from(expected)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
verify.py
import base64, hashlib, hmac, os, time

SECRET = os.environ["GLANCE_SECRET"]
WINDOW_MS = 5 * 60 * 1000

def verify(headers) -> bool:
    ts = headers.get("X-GlanceWidgets-Timestamp")
    sig = headers.get("X-GlanceWidgets-Signature")
    if not ts or not sig or abs(time.time() * 1000 - int(ts)) > WINDOW_MS:
        return False
    message = f"{ts}\n{SECRET}".encode()
    expected = base64.b64encode(
        hmac.new(SECRET.encode(), message, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(sig, expected)

テストベクタ:シークレット this is secret、タイムスタンプ 1700000000000 → sSFWELbV2YwjdDQhWZwTcWlX5BWUx5J6TPpsZmuPii0=。

制限とエラー#

ボディサイズ
512 KB (524,288 bytes)
ルートグリッド
rows × columns ≤ 4,096;≥ 1 × 1;長方形
レイヤーの宣言
各項目は id、type、dataPath のみで、id は remote: で始まる
プロトコル
1 または省略
曲線
1,024点以内 · マーカー32個以内 · 式は512文字以内
画像
リモート6 MB以内 · Base64は4 MB以内 · カバーフィット

レスポンス容量超過、不正なルート配列、範囲外の batch 座標、レイヤー宣言の構造エラーは、レスポンス全体がアプリで拒否されます。レイヤーが指すオブジェクトのエラーはそのレイヤーだけに影響し、エディタのレイヤーは保存済みの状態に戻り、リモートレイヤーは描画されません。曲線の点・マーカー・パラメータは上限内だけ読み取り、不正・長すぎる式は実行しません。

更新#

ホーム画面のウィジェットは、WidgetKitのタイムラインに沿って、約 15分 ごとに新しいフレームをリクエストします。正確なタイミングは、バッテリー、使用状況、システムの更新予算に応じてiOSが決めます。更新の合間にも意味が伝わるフレームを設計し、必要なら data に「最終更新」の時刻を含めてください。

プレイグラウンド#

JSONを編集すると、その場で描画されます。バリデーターは、アプリと同じ制限と優先順位を適用します。

有効なフレーム 0 セル · 0 レイヤー · 0 バイト

チェックリスト#

  • HTTPSエンドポイントが、数秒以内に 2xx を返す。
  • ルートが "protocol": 1 を持つオブジェクトで、pixels が4,096セル以内の長方形の配列である。
  • 色は #RRGGBB(またはARGBの #AARRGGBB)である。
  • layers の各項目は id、type、dataPath だけを持ち、id は remote: で始まる。
  • すべての dataPath が data 内のオブジェクトを指し、リモートレイヤーではそのオブジェクトに width、height を含む frame がある。
  • グリッドは固定の表ではなく、gw_aspect_ratio からサイズを決めている。
  • 署名を有効にしている場合は、タイムスタンプの有効期間を確認し、署名を定数時間で検証する。