UI ツリーとレイアウト
ノードコンストラクタと Flex スタイルのレイアウトで、zenit の空間モデルを組み立てます。
メンタルモデル
マウント関数は、実体を持ち常駐する *ui.Node のツリーを一度だけ構築します。コンテナは子の並べ方を決め、リーフはテキスト、画像、アイコンを持ちます。以降の各フレームでは、フレームワークは「ダーティ」なノードからだけパイプラインに再入します。レイアウト、描画項目の生成、Metal への受け渡しです。
コンストラクタ
ui.box方向、サイズ、間隔、背景を完全に制御できる汎用コンテナ。既定は direction が column、幅と高さが fitui.hstack / ui.vstackdirection を row / column に固定した boxui.text / ui.textFmt静的テキスト / Signal を購読する書式付きテキスト:textFmt(cx, scope, fmt, .{signals}, props)ui.icon / ui.iconTint / ui.svg / ui.imageベクターアイコン、着色アイコン、SVG、ビットマップテクスチャui.grid列 / 行のトラックに沿って配置するグリッドコンテナui.spacer幅と高さがともに grow の伸縮する空白ui.clickable任意のノードに on_click ハンドラを付け、同じノードを返すui.boxStyled / hstackStyled / vstackStyled / textStyled名前付きのスタイル関数を受け取り、テーマ変更時に自動で再適用。スタイルとテーマを参照レイアウトの例
まずコンテナを作り、appendChild で子を追加します。スタイル値はまず cx.tokens から取るので、テーマの切り替えや間隔の一括調整でリテラルを探し回る必要はありません。
const card = try ui.vstack(cx, .{
.width = .fixed(360),
.gap = cx.tokens.space._4,
.padding = ui.Padding.all(cx.tokens.space._6),
.background = cx.tokens.color.bg_secondary,
.corner_radius = cx.tokens.radius.xl,
.align_items = .stretch,
}, .{});
const header = try ui.hstack(cx, .{
.gap = cx.tokens.space._2,
.align_items = .center,
}, .{});
try header.appendChild(cx.allocator, try ui.iconTint(
cx,
ui.icons.star,
cx.tokens.color.accent,
.{ .width = .fixed(18), .height = .fixed(18) },
));
try header.appendChild(cx.allocator, try ui.text(cx, "Overview", .{}));
try header.appendChild(cx.allocator, try ui.spacer(cx));
try card.appendChild(cx.allocator, header);子は構築時に tuple として渡すこともできます。ui.Padding.symmetric(v, h) は上下と左右のパディングを別々に設定します。
// Children can also be passed as a tuple at construction time.
const row = try ui.hstack(cx, .{
.width = .fill(),
.padding = ui.Padding.symmetric(cx.tokens.space._2, cx.tokens.space._4),
.gap = cx.tokens.space._3,
}, .{
try ui.text(cx, "Name", .{}),
try ui.spacer(cx),
try ui.text(cx, "42", .{ .color = cx.tokens.color.fg_secondary }),
});
// Any node can take a click handler.
// (model: *Model from cx.bindState; Model.select is fn (*Model) void)
_ = ui.clickable(row, cx.on(Model, model, Model.select));サイズと座標
幅と高さは ui.Sizing ユニオンで、4 つのメンバーそれぞれに省略形があります。
.fixed(120).{ .px = 120 }固定ピクセル.fill().{ .grow = .{} }残りの空間を占有。grow は min / max のペイロードを持つ(例:.{ .grow = .{ .min = 200 } }).pct(50).{ .percent = 50 }親の content box(padding を除く)に対する割合(0–100).{ .fit = .{} }内容によって決まる。box の既定値で、min / max も指定可能レイアウト結果はノードのフィールドには保存されません。node.rectFromWorldOrFallback() は直前のレイアウトで得られた、親の左上を基準とする矩形を返します。ウィンドウ座標が必要なら node.globalRect() を使います。これは祖先チェーンをたどって位置、translate、sticky オフセットを累積します。
// Parent-relative rect from the last layout pass.
const local = node.rectFromWorldOrFallback();
// Window coordinates: accumulates ancestors, translate and sticky offsets.
const screen = node.globalRect();
std.log.info("{d}x{d} at ({d}, {d})", .{ screen.w, screen.h, screen.x, screen.y });
_ = local;ノードの更新
ノードを直接変更するときは、パイプラインのどの部分を再実行すべきかをフレームワークに伝える必要があります。setStyle を優先してください。フィールドからコンパイル時にダーティレベルを選ぶので、手動の markLayoutDirty / markRenderDirty は不要です。
.sizingwidth, height自身と親を再レイアウト.layoutpadding, margin, gap, direction, justify, align_items, min/max_*このノードを再レイアウト.interactionopacity, translate_*, corner_radius, border, z_index, cursorヒットインデックスを更新して再描画.renderbackground, shadow, gradient, outline, text_color描画項目のみ再生成.nonetab_index, layout_isolationフレーム処理なし// setStyle picks the dirty level from the field at compile time.
node.setStyle(cx.allocator, .width, .fixed(240)); // sizing
node.setStyle(cx.allocator, .gap, 12); // layout
node.setStyle(cx.allocator, .background, next); // render
// Low-frequency fields live in StyleExt and need a real allocator.
node.setStyle(cx.allocator, .z_index, 10);
// Replace text: dupes the content, frees the previous owned copy,
// and re-measures only if the content actually changed.
try node.setTextContent(cx.allocator, "Updated");
// Hide without unmounting: display:none leaves layout, paint,
// hit-testing, Tab order and the a11y tree; state stays alive.
panel.setDisplay(.none);
panel.setDisplay(.flex); // back, same nodes
// Recolor an icon node whether it is icon-table or image (svgTint) backed.
_ = icon.setTint(cx.tokens.color.accent); // false: node has neithersetText は新旧テキストのシグネチャを比較します。計測に影響すれば sizing、見た目だけなら render をダーティにし、完全に同じなら何もしません。したがってテキスト更新後に markRenderDirty を呼ぶ必要はありません。
サブツリーを一時的に隠すには node.setDisplay(.none) を呼びます(または BoxStyle に .display = .none を書きます)。ノードとそのサブツリーは領域を占めず、gap にも数えられず、描画もされず、ヒットテスト、Tab 移動、アクセシビリティツリーからも外れます。ノードと状態は生きたままで、.flex で元に戻せるため、アンマウントして再構築する必要はありません。setDisplay は自身と親のレイアウトを自動でダーティにします。opacity を 0 にするだけでもヒットテストは止まりますが、領域は占めたままで Tab 順にも残ります。