Aplicaciones multiventana
Crea ventanas nativas aisladas para editores, vistas previas o paneles de herramientas, todas movidas por un único bucle de eventos que despacha por window_id.
Cuándo usarlo
Las aplicaciones de una sola ventana siguen usando App; su API no cambia. Pasa a MultiWindowApp (también exportado como zenit_app.Application) solo cuando las ventanas necesiten su propio ciclo de vida, enrutamiento de entrada, superficie GPU o árbol de accesibilidad.
Crear ventanas
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()
}Cada llamada a createWindow* devuelve un *App y acepta la misma configuración que un App de una sola ventana (window, console, frame_pacing, …). createWindow crea sin montar, así que llamas tú a app.mount(mountFn); createWindowWith hace ambas cosas en una sola transacción. El ejemplo zig build multi-window del repositorio muestra las dos.
Garantías de aislamiento
Ventana nativa, Cx (con su propio grafo reactivo), contexto de fuentes, superficie Metal / renderer / cola del dispositivo, IME y ruta de accesibilidad.
Los eventos de puntero, teclado, IME y arrastre se enrutan por el window_id nativo; los comandos de menú van a la key window capturada al dispararse, no a la ventana que casualmente los sacó de la cola.
Cerrar una ventana libera solo sus propios recursos mientras las demás siguen renderizando; run() retorna cuando se cierra la última.
Ciclo de vida
run()Ejecuta el bucle hasta que se cierra la última ventana o se llama a quit()tick()Una ronda de pump + frame, para controlar el bucle tú mismocloseWindow(id)Cierra una ventana; dentro de un callback se encola hasta un límite seguroquit()Sale de toda la aplicación; deinit() cierra en orden las ventanas restanteswindow(id)Busca una ventana viva por id; null si ya se cerróactivateWindow(id)Convierte una ventana en la activasetMenuModel / bindMenuCommandEl modelo de menú es global al proceso; los callbacks de comandos se vinculan por ventanaconst 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
}Compartir estado
El modelo compartido entre ventanas pertenece a la aplicación; cada ventana guarda solo su propio estado de vista. Cada Cx tiene su propio grafo reactivo, así que no dejes que el Effect de una ventana lea un Signal del Scope de otra, y nunca compartas punteros a Node ni recursos de Scope entre Cx.
- ✓
El modelo vive fuera de las ventanas. Guarda los datos en un struct simple o en un store de nivel de aplicación que sobreviva a cualquier ventana.
- ✓
Cada ventana refleja lo que necesita. Una ventana crea Signals en su propio Scope; cuando el modelo cambia, la aplicación escribe explícitamente el reflejo de cada ventana.
- ✓
Da de baja al cerrar. Tras cerrarse una ventana, el modelo no debe conservar punteros a sus Signals.