Compare commits

...

2 Commits

Author SHA1 Message Date
Chris Tate bb97110886 docs(windows): clarify restore policy support 2026-08-17 17:33:51 -05:00
Chris Tate 7379550a27 feat(windows): expose model restore policy
- Forward restore policy from model-declared windows into platform options.

- Expose matching TypeScript types, defaults, validation, and documentation.

- Cover fresh center-on-primary windows through the null platform.
2026-08-17 17:08:33 -05:00
14 changed files with 72 additions and 11 deletions
+1 -1
View File
@@ -484,7 +484,7 @@ For timestamps, the facade owns the clocks (Zig 0.16 puts `std.time` behind `std
## Secondary windows
In a default TypeScript app, export `windows(model): readonly WindowDescriptor[]` and put each possible window's markup at `src/windows/<label>.native`; the generated launcher compiles those files, maps descriptor labels to them, and includes shared components they import from nested paths under `src/windows/`. Construct descriptors with `windowDescriptor`. `closePolicy: "quit"` routes `onCloseCommand` through `commandMsg`; `"hide"` retains the window and dispatches no close command. `titlebar` includes `"chromeless"` for fully skinned windows. `examples/system-monitor-ts` is the complete default-path reference.
In a default TypeScript app, export `windows(model): readonly WindowDescriptor[]` and put each possible window's markup at `src/windows/<label>.native`; the generated launcher compiles those files, maps descriptor labels to them, and includes shared components they import from nested paths under `src/windows/`. Construct descriptors with `windowDescriptor`. `restorePolicy: "center_on_primary"` centers a fresh descriptor with no `x`/`y` on macOS; Windows and Linux currently keep their native default placement. The default policy is `"clamp_to_visible_screen"`. `closePolicy: "quit"` routes `onCloseCommand` through `commandMsg`; `"hide"` retains the window and dispatches no close command. `titlebar` includes `"chromeless"` for fully skinned windows. `examples/system-monitor-ts` is the complete default-path reference.
In Zig cores and custom wiring, windows are model-declared exactly like the tray: `Options.windows_fn` returns the window descriptors that should exist right now (presence IS liveness), and `Options.window_view` builds each declared window's whole canvas tree, keyed by the descriptor's window label. After every dispatched Msg the runtime reconciles: windows the model started declaring are created (a source-less native window wearing one `gpu_surface` view with the descriptor's `canvas_label`, inheriting the main canvas's gpu options), windows it stopped declaring close, and every open window's view rebuilds from the same model — a theme picked in the settings window restyles the main window on the same dispatch. There is no `visible` flag: transient visibility is host state changed through `hideWindow`/`showWindow` or a `.hide` close policy; stop declaring a window to really close it and release its retained views.
+2
View File
@@ -482,6 +482,8 @@ Export `windows(model): readonly WindowDescriptor[]` to derive the live secondar
`closePolicy` accepts `"quit"` (the default) or `"hide"`. A `"quit"` user close routes `onCloseCommand` through `commandMsg`, where the app maps it to the Msg that clears its open flag. A `"hide"` close retains the same native window and view and dispatches no close command; `Cmd.showWindow(label)` reveals it. Model-declared secondary windows are desktop-only. See `examples/system-monitor-ts`.
`restorePolicy` accepts `"clamp_to_visible_screen"` (the default) or `"center_on_primary"`. Model-declared windows do not restore persisted frames. On macOS, `"center_on_primary"` centers a fresh descriptor with no authored `x`/`y`; Windows and Linux currently keep their native default placement.
`titlebar` accepts `"standard"`, `"hidden_inset"`, `"hidden_inset_tall"`, or `"chromeless"`. Transparent Windows windows require `"chromeless"`; because that removes the system buttons, fully skinned windows must draw working close/minimize controls.
## Subscriptions are Sub data
+1
View File
@@ -96,6 +96,7 @@ export function windowDescriptor(spec: WindowDescriptorSpec): WindowDescriptor {
x: spec.x ?? null,
y: spec.y ?? null,
resizable: spec.resizable ?? true,
restorePolicy: spec.restorePolicy ?? "clamp_to_visible_screen",
minWidth: spec.minWidth ?? 0,
minHeight: spec.minHeight ?? 0,
titlebar: spec.titlebar ?? "standard",
+1
View File
@@ -388,6 +388,7 @@ export function windowDescriptor(spec: WindowDescriptorSpec): WindowDescriptor {
x: spec.x ?? null,
y: spec.y ?? null,
resizable: spec.resizable ?? true,
restorePolicy: spec.restorePolicy ?? "clamp_to_visible_screen",
minWidth: spec.minWidth ?? 0,
minHeight: spec.minHeight ?? 0,
titlebar: spec.titlebar ?? "standard",
+3
View File
@@ -55,6 +55,7 @@ export interface StatusItemDescriptor {
}
export type WindowClosePolicy = "quit" | "hide";
export type WindowTitlebarStyle = "standard" | "hidden_inset" | "hidden_inset_tall" | "chromeless";
export type WindowRestorePolicy = "clamp_to_visible_screen" | "center_on_primary";
export interface WindowDescriptorSpec {
readonly label: Uint8Array;
readonly canvasLabel: Uint8Array;
@@ -64,6 +65,7 @@ export interface WindowDescriptorSpec {
readonly x?: number | null;
readonly y?: number | null;
readonly resizable?: boolean;
readonly restorePolicy?: WindowRestorePolicy;
readonly minWidth?: number;
readonly minHeight?: number;
readonly titlebar?: WindowTitlebarStyle;
@@ -84,6 +86,7 @@ export interface WindowDescriptor {
readonly x: number | null;
readonly y: number | null;
readonly resizable: boolean;
readonly restorePolicy: WindowRestorePolicy;
readonly minWidth: number;
readonly minHeight: number;
readonly titlebar: WindowTitlebarStyle;
+4
View File
@@ -135,6 +135,8 @@ export type WindowClosePolicy = "quit" | "hide";
export type WindowTitlebarStyle = "standard" | "hidden_inset" | "hidden_inset_tall" | "chromeless";
export type WindowRestorePolicy = "clamp_to_visible_screen" | "center_on_primary";
/// Author-facing input to `windowDescriptor`; omitted fields receive the
/// same defaults as UiApp.WindowDescriptor.
export interface WindowDescriptorSpec {
@@ -146,6 +148,7 @@ export interface WindowDescriptorSpec {
readonly x?: number | null;
readonly y?: number | null;
readonly resizable?: boolean;
readonly restorePolicy?: WindowRestorePolicy;
readonly minWidth?: number;
readonly minHeight?: number;
readonly titlebar?: WindowTitlebarStyle;
@@ -172,6 +175,7 @@ export interface WindowDescriptor {
readonly x: number | null;
readonly y: number | null;
readonly resizable: boolean;
readonly restorePolicy: WindowRestorePolicy;
readonly minWidth: number;
readonly minHeight: number;
readonly titlebar: WindowTitlebarStyle;
+3 -2
View File
@@ -1467,12 +1467,13 @@ export class SubsetChecker {
return candidate?.type.k === "optional" && ["number", "i64", "f64", "numAlias"].includes(candidate.type.inner.k);
};
const valid =
names.join(",") === "activateOnShow,allowsFullscreen,alwaysOnTop,canvasLabel,clickThrough,closePolicy,height,label,minHeight,minWidth,onCloseCommand,resizable,title,titlebar,transparent,width,x,y" &&
names.join(",") === "activateOnShow,allowsFullscreen,alwaysOnTop,canvasLabel,clickThrough,closePolicy,height,label,minHeight,minWidth,onCloseCommand,resizable,restorePolicy,title,titlebar,transparent,width,x,y" &&
field("label")?.type.k === "bytes" &&
field("canvasLabel")?.type.k === "bytes" &&
field("title")?.type.k === "bytes" &&
numeric("width") && numeric("height") && optionalNumeric("x") && optionalNumeric("y") &&
field("resizable")?.type.k === "bool" && numeric("minWidth") && numeric("minHeight") &&
field("resizable")?.type.k === "bool" && enumMembersAre("restorePolicy", ["clamp_to_visible_screen", "center_on_primary"]) &&
numeric("minWidth") && numeric("minHeight") &&
enumMembersAre("titlebar", ["standard", "hidden_inset", "hidden_inset_tall", "chromeless"]) &&
field("transparent")?.type.k === "bool" && field("alwaysOnTop")?.type.k === "bool" &&
field("clickThrough")?.type.k === "bool" && field("activateOnShow")?.type.k === "bool" &&
@@ -34,6 +34,7 @@ test("windowDescriptor fills canonical window defaults", () => {
assert.equal(descriptor.width, 480);
assert.equal(descriptor.height, 360);
assert.equal(descriptor.resizable, true);
assert.equal(descriptor.restorePolicy, "clamp_to_visible_screen");
assert.equal(descriptor.titlebar, "chromeless");
assert.equal(descriptor.closePolicy, "hide");
assert.equal(descriptor.onCloseCommand.length, 0);
+1 -1
View File
@@ -1341,7 +1341,7 @@ export function initialModel(): Model { return { settingsOpen: false }; }
export function update(model: Model, msg: Msg): Model { return model; }
export function windows(model: Model): readonly WindowDescriptor[] {
if (!model.settingsOpen) return [];
return [windowDescriptor({ label: asciiBytes("settings"), canvasLabel: asciiBytes("settings-canvas"), titlebar: "chromeless", transparent: true, closePolicy: "hide", onCloseCommand: asciiBytes("settings.closed") })];
return [windowDescriptor({ label: asciiBytes("settings"), canvasLabel: asciiBytes("settings-canvas"), restorePolicy: "center_on_primary", titlebar: "chromeless", transparent: true, closePolicy: "hide", onCloseCommand: asciiBytes("settings.closed") })];
}
`;
const clean = check(cleanSource, { windowViews: ["settings"] });
+2 -1
View File
@@ -878,7 +878,7 @@ The `.wake` platform event is how live platforms marshal worker completions onto
## Secondary windows
In the default TypeScript path, export `windows(model): readonly WindowDescriptor[]` and put each possible window's Native markup at `src/windows/<label>.native`. Spell each constructor identity as a literal `label: asciiBytes("<label>")`; `native check` and every build reject dynamic labels and missing roots. Those roots may import shared components nested under `src/windows/`; the generated launcher compiles and hot-reloads the full closure. Use `windowDescriptor` from `@native-sdk/core`; its `closePolicy` field accepts `"quit"` or `"hide"`, and `titlebar` includes `"chromeless"`. A `"quit"` user close routes `onCloseCommand` through `commandMsg`; a `"hide"` close preserves the window and view and dispatches no close command. The `ts-core` skill has the complete descriptor and example.
In the default TypeScript path, export `windows(model): readonly WindowDescriptor[]` and put each possible window's Native markup at `src/windows/<label>.native`. Spell each constructor identity as a literal `label: asciiBytes("<label>")`; `native check` and every build reject dynamic labels and missing roots. Those roots may import shared components nested under `src/windows/`; the generated launcher compiles and hot-reloads the full closure. Use `windowDescriptor` from `@native-sdk/core`; its `restorePolicy` field accepts `"clamp_to_visible_screen"` or `"center_on_primary"` (centering is currently macOS-only), `closePolicy` accepts `"quit"` or `"hide"`, and `titlebar` includes `"chromeless"`. A `"quit"` user close routes `onCloseCommand` through `commandMsg`; a `"hide"` close preserves the window and view and dispatches no close command. The `ts-core` skill has the complete descriptor and example.
### Zig cores: `windows_fn` + `window_view`
@@ -905,6 +905,7 @@ fn windowView(ui: *App.Ui, model: *const Model, window_label: []const u8) App.Ui
Rules that matter:
- **Every canvas label must be unique across the app** (main + declared windows); input routes back by it, and automation verbs (`widget-click <canvas-label> <id>`, `screenshot`) address any window's canvas the same way.
- **Close policy**: `WindowDescriptor.close_policy` is `.quit` by default; a user close really closes and dispatches `on_close` (the dismissal precedent), so clear the open flag in `update` — or keep declaring the window and the next rebuild brings it back (source wins). `.hide` keeps the same window, slot, and views alive, dispatches no `on_close`, and `showWindow(label)` reveals it. A close the model itself initiated never echoes a Msg. Existing platform safeguards still apply: unsupported hosts refuse `.hide` rather than strand an unreachable window.
- **Restore policy**: `WindowDescriptor.restore_policy` is `.clamp_to_visible_screen` by default. On macOS, `.center_on_primary` centers a fresh descriptor with no authored `x`/`y`; Windows and Linux currently keep their native default placement. Model-declared windows deliberately do not restore persisted frames.
- **Budget**: at most `UiApp.max_ui_windows` (4) declared windows; excess warns and is ignored. Every dispatched Msg rebuilds every open window's view.
- **Present-before-show**: canvas windows (any `gpu_surface` view — startup, scene, and declared windows alike) are created ordered-out and become visible only after their first canvas frame presents, so opening one never flashes blank. Automatic (`WindowOptions.show = .on_first_present`, derived from the views); webview windows show immediately. The null platform records `window_show`, `window_visible`, and present/shown sequence numbers for ordering assertions; `NATIVE_SDK_WINDOW_TIMING=1` logs create→show latency on macOS.
- **Markup binds ONE window's content** — there is no `window` element in the closed grammar. A markup-authored secondary window is a `canvas.CompiledMarkupView` whose `build` `window_view` calls for that label.
+3
View File
@@ -231,6 +231,7 @@ export function windows(model: Model): readonly WindowDescriptor[] {
title: asciiBytes("Settings"),
width: 420,
height: 240,
restorePolicy: "center_on_primary",
resizable: false,
closePolicy: "quit",
onCloseCommand: asciiBytes("app.settings-closed"),
@@ -240,6 +241,8 @@ export function windows(model: Model): readonly WindowDescriptor[] {
`closePolicy` is `"quit"` by default. Under `"quit"`, a user close really closes the window and routes `onCloseCommand` through `commandMsg`; map it to the Msg that clears the model's open flag. If the model keeps declaring the label, source wins and the next reconciliation recreates it. Under `"hide"`, the same native window and view stay alive, no close command fires, and `Cmd.showWindow("settings")` reveals it. Stopping the declaration always performs a real reconcile close. The platform safeguards for `hide` are the same as manifest windows. Model-declared secondary windows are desktop-only.
`restorePolicy` accepts `"clamp_to_visible_screen"` (the default) or `"center_on_primary"`. Model-declared windows do not restore persisted frames. On macOS, the latter centers a fresh descriptor with no `x`/`y` directly at native creation time; Windows and Linux currently keep their native default placement.
`titlebar` accepts `"standard"`, `"hidden_inset"`, `"hidden_inset_tall"`, and `"chromeless"`. The last removes all OS chrome and is required when a transparent model-declared window targets Windows; provide working app-drawn close/minimize controls for that fully skinned shape.
### The init command
+20 -6
View File
@@ -720,6 +720,7 @@ pub fn TsUiApp(comptime core: type) type {
.x = optionalWindowFloat(window.x),
.y = optionalWindowFloat(window.y),
.resizable = window.resizable,
.restore_policy = windowRestorePolicy(window.restorePolicy),
.min_width = statusItemFloat(window.minWidth),
.min_height = statusItemFloat(window.minHeight),
.titlebar = windowTitlebar(window.titlebar),
@@ -752,6 +753,14 @@ pub fn TsUiApp(comptime core: type) type {
unreachable;
}
fn windowRestorePolicy(value: anytype) @import("app_manifest").WindowRestorePolicy {
const Target = @import("app_manifest").WindowRestorePolicy;
inline for (std.meta.fields(Target)) |field| {
if (std.mem.eql(u8, @tagName(value), field.name)) return @enumFromInt(field.value);
}
unreachable;
}
fn windowClosePolicy(value: anytype) @import("app_manifest").WindowClosePolicy {
const Target = @import("app_manifest").WindowClosePolicy;
inline for (std.meta.fields(Target)) |field| {
@@ -1018,9 +1027,10 @@ pub fn TsUiApp(comptime core: type) type {
if (return_info != .pointer or return_info.pointer.size != .slice or !return_info.pointer.is_const) @compileError(teaching);
const Window = statusItemRecordType(return_info.pointer.child, teaching);
const info = @typeInfo(Window).@"struct";
if (info.fields.len != 18 or !@hasField(Window, "label") or !@hasField(Window, "canvasLabel") or
if (info.fields.len != 19 or !@hasField(Window, "label") or !@hasField(Window, "canvasLabel") or
!@hasField(Window, "title") or !@hasField(Window, "width") or !@hasField(Window, "height") or
!@hasField(Window, "x") or !@hasField(Window, "y") or !@hasField(Window, "resizable") or
!@hasField(Window, "restorePolicy") or
!@hasField(Window, "minWidth") or !@hasField(Window, "minHeight") or !@hasField(Window, "titlebar") or
!@hasField(Window, "transparent") or !@hasField(Window, "alwaysOnTop") or !@hasField(Window, "clickThrough") or
!@hasField(Window, "activateOnShow") or !@hasField(Window, "allowsFullscreen") or
@@ -1029,6 +1039,7 @@ pub fn TsUiApp(comptime core: type) type {
@FieldType(Window, "title") != []const u8 or !statusItemNumericType(@FieldType(Window, "width")) or
!statusItemNumericType(@FieldType(Window, "height")) or !optionalNumericType(@FieldType(Window, "x")) or
!optionalNumericType(@FieldType(Window, "y")) or @FieldType(Window, "resizable") != bool or
!statusItemEnumType(@FieldType(Window, "restorePolicy"), &.{ "clamp_to_visible_screen", "center_on_primary" }) or
!statusItemNumericType(@FieldType(Window, "minWidth")) or !statusItemNumericType(@FieldType(Window, "minHeight")) or
!statusItemEnumType(@FieldType(Window, "titlebar"), &.{ "standard", "hidden_inset", "hidden_inset_tall", "chromeless" }) or
@FieldType(Window, "transparent") != bool or @FieldType(Window, "alwaysOnTop") != bool or
@@ -1637,6 +1648,7 @@ test "TypeScript statusItems adapter validates and projects canonical descriptor
const WindowsAdapterTestCore = struct {
const Titlebar = enum { standard, hidden_inset, hidden_inset_tall, chromeless };
const RestorePolicy = enum { clamp_to_visible_screen, center_on_primary };
const ClosePolicy = enum { quit, hide };
const Descriptor = struct {
label: []const u8,
@@ -1647,6 +1659,7 @@ const WindowsAdapterTestCore = struct {
x: ?f64,
y: ?f64,
resizable: bool,
restorePolicy: RestorePolicy,
minWidth: f64,
minHeight: f64,
titlebar: Titlebar,
@@ -1660,11 +1673,11 @@ const WindowsAdapterTestCore = struct {
};
const descriptors = [_]Descriptor{
.{ .label = "one", .canvasLabel = "one-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .minWidth = 0, .minHeight = 0, .titlebar = .chromeless, .transparent = true, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "two", .canvasLabel = "two-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "three", .canvasLabel = "three-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "four", .canvasLabel = "four-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "excess", .canvasLabel = "excess-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "one", .canvasLabel = "one-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .restorePolicy = .center_on_primary, .minWidth = 0, .minHeight = 0, .titlebar = .chromeless, .transparent = true, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "two", .canvasLabel = "two-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .restorePolicy = .clamp_to_visible_screen, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "three", .canvasLabel = "three-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .restorePolicy = .clamp_to_visible_screen, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "four", .canvasLabel = "four-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .restorePolicy = .clamp_to_visible_screen, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
.{ .label = "excess", .canvasLabel = "excess-canvas", .title = "", .width = 100, .height = 100, .x = null, .y = null, .resizable = true, .restorePolicy = .clamp_to_visible_screen, .minWidth = 0, .minHeight = 0, .titlebar = .standard, .transparent = false, .alwaysOnTop = false, .clickThrough = false, .activateOnShow = true, .allowsFullscreen = true, .closePolicy = .quit, .onCloseCommand = "" },
};
pub const Msg = union(enum) { noop };
@@ -1723,6 +1736,7 @@ test "TypeScript windows adapter keeps the declared prefix on overflow and proje
try std.testing.expectEqual(Adapter.App.max_ui_windows, descriptors.len);
try std.testing.expectEqualStrings("one", descriptors[0].label);
try std.testing.expectEqual(@import("app_manifest").WindowTitlebarStyle.chromeless, descriptors[0].titlebar);
try std.testing.expectEqual(@import("app_manifest").WindowRestorePolicy.center_on_primary, descriptors[0].restore_policy);
try std.testing.expect(descriptors[0].transparent);
try std.testing.expectEqualStrings("four", descriptors[3].label);
}
+7
View File
@@ -370,6 +370,12 @@ pub fn UiAppWithFeatures(comptime ModelT: type, comptime MsgT: type, comptime fe
x: ?f32 = null,
y: ?f32 = null,
resizable: bool = true,
/// Placement policy passed to the host when creating this
/// fresh, non-restored window. On macOS,
/// `.center_on_primary` centers a descriptor without an authored
/// origin on the primary screen; other hosts currently retain
/// their native default placement.
restore_policy: app_manifest.WindowRestorePolicy = .clamp_to_visible_screen,
/// Content min-size floor the WINDOW enforces (macOS
/// `contentMinSize`): the user's resize stops at the floor
/// instead of the layout clamping/clipping panes below
@@ -2880,6 +2886,7 @@ pub fn UiAppWithFeatures(comptime ModelT: type, comptime MsgT: type, comptime fe
.x = descriptor.x,
.y = descriptor.y,
.resizable = descriptor.resizable,
.restore_policy = descriptor.restore_policy,
.titlebar = descriptor.titlebar,
.transparent = descriptor.transparent,
.always_on_top = descriptor.always_on_top,
+23
View File
@@ -21,6 +21,7 @@ const settings_window_label = "settings";
const PanelModel = struct {
settings_open: bool = false,
position_settings: bool = true,
center_settings: bool = false,
transparent_settings: bool = false,
bumps: u32 = 0,
user_closes: u32 = 0,
@@ -63,6 +64,7 @@ fn panelWindows(model: *const PanelModel, scratch: *PanelApp.WindowsScratch) []c
.height = 240,
.x = if (model.position_settings) 84 else null,
.y = if (model.position_settings) 126 else null,
.restore_policy = if (model.center_settings) .center_on_primary else .clamp_to_visible_screen,
.min_width = 280,
.min_height = 200,
.titlebar = if (model.transparent_settings) .chromeless else .standard,
@@ -245,6 +247,27 @@ test "a Msg declares the settings window, its canvas installs, and automation dr
}
}
test "a windows_fn restore policy reaches fresh WindowOptions" {
const fixture = try Fixture.create();
defer fixture.destroy();
fixture.app_state.model.position_settings = false;
fixture.app_state.model.center_settings = true;
try fixture.clickSettingsButton();
const info = fixture.settingsWindowInfo() orelse return error.TestUnexpectedResult;
for (fixture.harness.null_platform.windows[0..fixture.harness.null_platform.window_count], 0..) |window, index| {
if (window.id != info.id) continue;
// Model-declared windows deliberately set restore_state=false. With
// no authored origin this remains a fresh/default placement while
// preserving the descriptor's policy at WindowOptions.
try std.testing.expectEqual(support.platform.WindowInitialPlacement.default, fixture.harness.null_platform.window_placement[index]);
try std.testing.expectEqual(support.platform.WindowRestorePolicy.center_on_primary, fixture.harness.null_platform.window_restore_policy[index]);
return;
}
return error.WindowNotFound;
}
test "a transparent descriptor selects premultiplied canvas alpha and an alpha-zero clear" {
const fixture = try Fixture.create();
defer fixture.destroy();