Applications multifenêtres
Créez des fenêtres natives isolées pour des éditeurs, des aperçus ou des panneaux d’outils, toutes pilotées par une seule boucle d’événements qui distribue par window_id.
Quand l’utiliser
Les applications à fenêtre unique continuent d’utiliser App ; son API ne change pas. Passez à MultiWindowApp (aussi exporté sous zenit_app.Application) uniquement lorsque les fenêtres ont besoin de leur propre cycle de vie, routage des entrées, surface GPU ou arbre d’accessibilité.
Créer des fenêtres
const std = @import("std");
const ui = @import("ui");
const zenit_app = @import("zenit_app");
fn mountEditor(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
_ = scope;
return ui.box(cx, .{ .width = .fill(), .height = .fill() }, .{});
}
fn mountPreview(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
_ = scope;
return ui.box(cx, .{ .width = .fill(), .height = .fill() }, .{});
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var application = zenit_app.MultiWindowApp.init(gpa.allocator(), .{});
defer application.deinit();
// createWindowWith = create + mount in one transaction: if mount fails,
// every native / GPU resource of that window is torn down again.
const editor = try application.createWindowWith(.{
.window = .{ .width = 900, .height = 700, .title = "Editor" },
}, mountEditor);
_ = try application.createWindowWith(.{
.window = .{ .width = 480, .height = 700, .title = "Preview" },
}, mountPreview);
_ = application.activateWindow(editor.windowId());
try application.run(); // returns after the last window closes or quit()
}Chaque appel à createWindow* renvoie un *App et accepte la même configuration qu’un App à fenêtre unique (window, console, frame_pacing, …). createWindow crée sans monter : vous appelez vous-même app.mount(mountFn) ; createWindowWith fait les deux en une seule transaction. L’exemple zig build multi-window du dépôt illustre les deux.
Garanties d’isolation
Fenêtre native, Cx (avec son propre graphe réactif), contexte de polices, surface Metal / renderer / file du device, IME et route d’accessibilité.
Les événements de pointeur, clavier, IME et glisser sont routés par window_id natif ; les commandes de menu vont à la key window capturée au déclenchement, pas à la fenêtre qui les a dépilées par hasard.
Fermer une fenêtre ne détruit que ses propres ressources, les autres continuent de s’afficher ; run() rend la main quand la dernière se ferme.
Cycle de vie
run()Fait tourner la boucle jusqu’à la fermeture de la dernière fenêtre ou l’appel de quit()tick()Un tour de pump + frame, pour piloter la boucle vous-mêmecloseWindow(id)Ferme une fenêtre ; depuis un callback, c’est mis en file jusqu’à une frontière sûrequit()Quitte toute l’application ; deinit() détruit les fenêtres restantes dans l’ordrewindow(id)Trouve une fenêtre vivante par id ; null une fois ferméeactivateWindow(id)Rend une fenêtre activesetMenuModel / bindMenuCommandLe modèle de menu est global au processus ; les callbacks de commande se lient par fenêtreconst preview_id = preview.windowId(); // preview: *App from createWindowWith
// Safe from inside an input / menu / render callback: the close is queued
// and committed at the loop's iteration boundary.
_ = application.closeWindow(preview_id);
// Look windows up by id instead of holding *App across a close.
if (application.window(preview_id)) |app| {
_ = app; // still alive
}Partager l’état
Le modèle partagé entre fenêtres appartient à l’application ; chaque fenêtre ne garde que son propre état de vue. Chaque Cx a son propre graphe réactif : ne laissez donc pas l’Effect d’une fenêtre lire un Signal du Scope d’une autre, et ne partagez jamais de pointeurs Node ni de ressources de Scope entre Cx.
- ✓
Le modèle vit en dehors des fenêtres. Conservez les données dans une struct simple ou un store au niveau de l’application qui survit à chaque fenêtre.
- ✓
Chaque fenêtre reflète ce dont elle a besoin. Une fenêtre crée des Signals dans son propre Scope ; quand le modèle change, l’application écrit explicitement dans le miroir de chaque fenêtre.
- ✓
Désinscrivez à la fermeture. Une fois la fenêtre fermée, le modèle ne doit plus garder de pointeurs vers ses Signals.