Árbol de UI y layout
Construye el modelo espacial de zenit con constructores de nodos y layout de estilo Flex.
Modelo mental
Tu función de montaje construye un árbol real y persistente de *ui.Node, una sola vez. Los contenedores deciden cómo se disponen los hijos; las hojas llevan texto, imágenes o iconos. En cada frame posterior, el framework vuelve a entrar en la pipeline solo desde los nodos sucios: layout, pintado en display items y entrega a Metal.
Constructores
ui.boxContenedor genérico con control total de dirección, tamaño, espaciado y fondo. Por defecto: column, ancho y alto fitui.hstack / ui.vstackUn box con la dirección fijada en row / columnui.text / ui.textFmtTexto estático / texto formateado suscrito a Signals: textFmt(cx, scope, fmt, .{signals}, props)ui.icon / ui.iconTint / ui.svg / ui.imageIconos vectoriales, iconos tintados, SVG y texturas de mapa de bitsui.gridUn contenedor de cuadrícula dispuesto en pistas de columnas / filasui.spacerEspacio en blanco flexible, grow en ambos ejesui.clickableAsocia un handler on_click a cualquier nodo y lo devuelveui.boxStyled / hstackStyled / vstackStyled / textStyledReciben una función de estilo con nombre y la reaplican al cambiar de tema; consulta Estilos y temasUn ejemplo de layout
Crea el contenedor y luego añade los hijos con appendChild. Los valores de estilo salen primero de cx.tokens, así que cambiar de tema o ajustar el espaciado no obliga a buscar literales uno por uno.
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);Los hijos también pueden pasarse como tupla al construir; ui.Padding.symmetric(v, h) define el padding vertical y horizontal por separado.
// 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));Tamaño y coordenadas
El ancho y el alto son una unión ui.Sizing; cada uno de sus cuatro miembros tiene una forma abreviada:
.fixed(120).{ .px = 120 }Píxeles fijos.fill().{ .grow = .{} }Ocupa el espacio restante; grow lleva un payload min / max, p. ej. .{ .grow = .{ .min = 200 } }.pct(50).{ .percent = 50 }Porcentaje (0–100) del content box del padre, sin padding.{ .fit = .{} }Dimensionado por el contenido; valor por defecto de box, también con min / maxLos resultados del layout no se guardan como campo del nodo. node.rectFromWorldOrFallback() devuelve el rect de la última pasada de layout, relativo a la esquina superior izquierda del padre; para coordenadas de ventana usa node.globalRect(), que acumula posiciones, translate y offsets sticky a lo largo de la cadena de ancestros.
// 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;Actualizar nodos
Cuando cambias un nodo directamente, el framework tiene que saber qué parte de la pipeline volver a ejecutar. Prefiere setStyle: elige el nivel de suciedad a partir del campo en tiempo de compilación, así que no hace falta markLayoutDirty / markRenderDirty manual.
.sizingwidth, heightRe-layout propio y del padre.layoutpadding, margin, gap, direction, justify, align_items, min/max_*Re-layout de este nodo.interactionopacity, translate_*, corner_radius, border, z_index, cursorActualizar el índice de hit y repintar.renderbackground, shadow, gradient, outline, text_colorSolo regenerar el pintado.nonetab_index, layout_isolationSin trabajo 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 compara las firmas del texto antiguo y el nuevo: sizing-dirty si cambia la medición, render-dirty si solo cambia la apariencia, y nada en absoluto si son idénticas. No hace falta markRenderDirty tras actualizar el texto.
Para ocultar un subárbol durante un tiempo, llama a node.setDisplay(.none) (o pon .display = .none en un BoxStyle): el nodo y su subárbol no ocupan espacio, no cuentan gap ni se pintan, y salen del hit-testing, del recorrido con Tab y del árbol de accesibilidad. Los nodos y el estado siguen vivos, y .flex los recupera, sin desmontar ni reconstruir. setDisplay marca por sí mismo como sucio el layout del nodo y de su padre. Una opacity de 0 por sí sola también detiene el hit-testing, pero el nodo sigue ocupando espacio y permanece en el orden de Tab.