Arbre d'UI et mise en page
Construisez le modèle spatial de zenit avec les constructeurs de nœuds et une mise en page de style Flex.
Modèle mental
Votre fonction de montage construit un vrai arbre persistant de *ui.Node — une seule fois. Les conteneurs décident de la disposition des enfants ; les feuilles portent du texte, des images ou des icônes. À chaque frame suivante, le framework ne réentre dans le pipeline qu’à partir des nœuds sales : layout, peinture en display items, transmission à Metal.
Constructeurs
ui.boxConteneur générique avec contrôle complet de la direction, de la taille, de l’espacement et du fond. Par défaut : column, largeur et hauteur fitui.hstack / ui.vstackUn box dont la direction est fixée à row / columnui.text / ui.textFmtTexte statique / texte formaté abonné à des Signals : textFmt(cx, scope, fmt, .{signals}, props)ui.icon / ui.iconTint / ui.svg / ui.imageIcônes vectorielles, icônes teintées, SVG et textures bitmapui.gridUn conteneur de grille disposé sur des pistes de colonnes / lignesui.spacerEspace vide flexible, grow sur les deux axesui.clickableAttache un handler on_click à n’importe quel nœud et le renvoieui.boxStyled / hstackStyled / vstackStyled / textStyledPrennent une fonction de style nommée et la rejouent au changement de thème — voir Styles et thèmesUn exemple de mise en page
Créez le conteneur, puis attachez les enfants avec appendChild. Les valeurs de style viennent d’abord de cx.tokens : changer de thème ou d’espacement ne demande pas de traquer les littéraux.
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);Les enfants peuvent aussi être passés sous forme de tuple à la construction ; ui.Padding.symmetric(v, h) définit séparément le padding vertical et horizontal.
// 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));Dimensions et coordonnées
La largeur et la hauteur sont une union ui.Sizing ; chacun de ses quatre membres a un raccourci :
.fixed(120).{ .px = 120 }Pixels fixes.fill().{ .grow = .{} }Prend l’espace restant ; grow porte une charge min / max, p. ex. .{ .grow = .{ .min = 200 } }.pct(50).{ .percent = 50 }Pourcentage (0–100) de la content box du parent, padding exclu.{ .fit = .{} }Dimensionné par le contenu ; valeur par défaut de box, aussi avec min / maxLes résultats de layout ne sont pas stockés dans un champ du nœud. node.rectFromWorldOrFallback() renvoie le rect de la dernière passe de layout, relatif au coin supérieur gauche du parent ; pour des coordonnées de fenêtre, utilisez node.globalRect(), qui cumule positions, translate et offsets sticky le long de la chaîne d’ancêtres.
// 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;Mettre à jour les nœuds
Quand vous modifiez un nœud directement, le framework doit savoir quelle partie du pipeline réexécuter. Préférez setStyle : il choisit le niveau de saleté à partir du champ à la compilation, sans markLayoutDirty / markRenderDirty manuel.
.sizingwidth, heightRelayout du nœud et du parent.layoutpadding, margin, gap, direction, justify, align_items, min/max_*Relayout de ce nœud.interactionopacity, translate_*, corner_radius, border, z_index, cursorMise à jour de l’index de hit et repeinture.renderbackground, shadow, gradient, outline, text_colorRégénération de la peinture seule.nonetab_index, layout_isolationAucun travail de frame// 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 compare les signatures de l’ancien et du nouveau texte : sizing-dirty si la mesure change, render-dirty si seule l’apparence change, rien du tout s’ils sont identiques. Pas besoin de markRenderDirty après une mise à jour du texte.
Pour masquer un sous-arbre un moment, appelez node.setDisplay(.none) (ou définissez .display = .none dans un BoxStyle) : le nœud et son sous-arbre n’occupent plus d’espace, ne comptent plus de gap et ne sont plus peints, et ils sortent du hit-testing, du parcours Tab et de l’arbre d’accessibilité. Les nœuds et l’état restent vivants, et .flex les fait revenir — sans démontage ni reconstruction. setDisplay marque lui-même comme sale le layout du nœud et de son parent. Une opacity de 0 seule arrête aussi le hit-testing, mais le nœud occupe toujours de l’espace et reste dans l’ordre de Tab.