---
title: "Arbre UI et layout — Docs zenit Zig UI"
description: "Construisez le modèle spatial de zenit avec les constructeurs de nœuds et une mise en page de style Flex."
url: https://zenit.z.express/fr/docs/guide/ui-tree
language: fr
alternate_en: https://zenit.z.express/docs/guide/ui-tree.md
alternate_zh: https://zenit.z.express/zh/docs/guide/ui-tree.md
alternate_es: https://zenit.z.express/es/docs/guide/ui-tree.md
alternate_ja: https://zenit.z.express/ja/docs/guide/ui-tree.md
alternate_ko: https://zenit.z.express/ko/docs/guide/ui-tree.md
alternate_de: https://zenit.z.express/de/docs/guide/ui-tree.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# 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.

FRAME PIPELINE

Chaque champ de style a un niveau de saleté. Un changement de fond ne régénère que la peinture ; un changement de largeur refait aussi le layout du parent. Avec un arbre propre et aucune animation en cours, toute la soumission GPU est ignorée.

## Constructeurs

| Constructeur | Usage |
| --- | --- |
| `ui.box` | Conteneur 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 fit |
| `ui.hstack / ui.vstack` | Un box dont la direction est fixée à row / column |
| `ui.text / ui.textFmt` | Texte statique / texte formaté abonné à des Signals : `textFmt(cx, scope, fmt, .{signals}, props)` |
| `ui.icon / ui.iconTint / ui.svg / ui.image` | Icônes vectorielles, icônes teintées, SVG et textures bitmap |
| `ui.grid` | Un conteneur de grille disposé sur des pistes de colonnes / lignes |
| `ui.spacer` | Espace vide flexible, grow sur les deux axes |
| `ui.clickable` | Attache un handler `on_click` à n’importe quel nœud et le renvoie |
| `ui.boxStyled / hstackStyled / vstackStyled / textStyled` | Prennent une fonction de style nommée et la rejouent au changement de thème — voir [Styles et thèmes](https://zenit.z.express/fr/docs/guide/styling) |

> TIP
> 
> **Cherchez d’abord un widget.** Les boutons, champs, listes et autres contrôles de `ui.widgets` intègrent déjà l’état, le focus et la sémantique clavier ; les constructeurs servent à la structure entre eux. Parcourez le [site des composants](https://zenit.z.express/fr/components).

## Un 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.

`card.zig`

```zig
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.

`row.zig`

```zig
// 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));
```

[Video](https://zenit.z.express/media/stories/stack.mp4?v=f9618b3adf)

La vraie story [Stack](https://zenit.z.express/fr/components/stack) : conteneurs horizontaux, verticaux et imbriqués se résolvent tous via les mêmes règles de rect, gap, align et sizing.

## Dimensions et coordonnées

La largeur et la hauteur sont une union `ui.Sizing` ; chacun de ses quatre membres a un raccourci :

| Raccourci | Membre | Signification |
| --- | --- | --- |
| `.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 / max |

SIZING

Quand le parent est redimensionné, .fixed ne bouge pas, .pct suit proportionnellement la content box, et .fill prend tout ce qui reste après les tailles fixes, les pourcentages et les gaps.

Les 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.

```zig
// 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;
```

[Video](https://zenit.z.express/media/stories/layoutbox.mp4?v=4d3c4ba31b)

La story LayoutBox : modèle de boîte, pourcentages, positionnement absolu, Flex / Grid et découpage, chacun avec des repères de padding box et de content box.

> TIP
> 
> **Vérifiez d’abord le parent.** Quand l’alignement ou les zones de clic semblent faux, la cause vient généralement du padding, du gap, de la direction ou du sizing du parent, pas de la feuille. En développement, `ui.devtools.overlay.attach(cx, scope, root, .{})` ajoute un inspecteur au survol qui trace le rect de chaque nœud — voir [DevTools](https://zenit.z.express/fr/docs/advanced/devtools).

## 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.

| Niveau | Champs typiques | Frame suivante |
| --- | --- | --- |
| `.sizing` | `width, height` | Relayout du nœud et du parent |
| `.layout` | `padding, margin, gap, direction, justify, align_items, min/max_*` | Relayout de ce nœud |
| `.interaction` | `opacity, translate_*, corner_radius, border, z_index, cursor` | Mise à jour de l’index de hit et repeinture |
| `.render` | `background, shadow, gradient, outline, text_color` | Régénération de la peinture seule |
| `.none` | `tab_index, layout_isolation` | Aucun travail de frame |

`update.zig`

```zig
// 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 neither
```

`setText` 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.

> WARNING
> 
> **Remplacez le texte avec setTextContent.** N’appelez pas `getText()` pour pointer `content` vers une chaîne temporaire puis la réinjecter avec `setText` : `setText` ne copie pas le contenu, et si l’ancien contenu était owned, le flag `owned` est recopié et se mélange avec le nouveau slice. `setTextContent` duplique le nouveau contenu, le marque owned et laisse le framework libérer la copie précédente. Quand vous construisez vous-même des `TextProps`, utilisez `props.setContent(cx.allocator, src)` : jusqu’à 16 octets, le contenu va dans le buffer inline sans allocation ; au-delà, il est dupliqué comme owned. `setInlineContent` renvoie `error.InlineContentTooLong` au-delà de 16 octets au lieu de tronquer en silence.

> NOTE
> 
> **Les champs peu fréquents ont besoin d’un allocator.** Des champs comme `shadow`, `z_index`, `corner_radius` et `min_width` vivent dans un StyleExt alloué à la demande. Leur passer un allocator `null` littéral provoque une erreur de compilation ; dans le doute, passez `cx.allocator`.
