chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:49 +08:00
commit 505bac6b53
1470 changed files with 457757 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# Native SDK split-collapse example
The smallest honest pane-collapse animation harness: a two-pane split whose sidebar collapses and expands over 180 ms, built to measure frame pacing during a layout tween and to demonstrate the runtime layout-tween primitive against the manual idiom it replaces.
Three driving modes, selected by environment variable:
- Default (runtime tween): the model owns only the resting `collapsed` flag; the `layout_tweens` hook declares the split's target fraction and the runtime eases the rendered fraction toward it, one step per presented frame — no per-frame Msgs, and reduced-motion appearances snap.
- `SPLIT_COLLAPSE_MARKUP=1` (markup tween): the same primitive declared entirely in markup — `resize-duration="180"` (and `resize-easing`) on the `split` element in `src/split_collapse.native` makes the bound `value` a tween target, so the view file is the whole animation and no Zig hook exists.
- `SPLIT_COLLAPSE_MANUAL=1` (manual ticks): the historical idiom — `on_frame` returns a tick Msg carrying the presented frame's timestamp, `update` eases the fraction, and every tick is a full rebuild.
Every tween step logs its arrival cadence on stderr (`tween-frame dt_ms=...` in manual mode, `tween-echo fraction=...` in the runtime and markup modes), so a driver can count the visible steps of the 180 ms collapse and read the real deltas between frames.
Extra knobs:
- `SPLIT_COLLAPSE_AUTO_MS=<interval>` arms a repeating auto-toggle so the tween runs without automation.
- `SPLIT_COLLAPSE_WEB=1` snaps a live webview to the content pane, so the tween reflows real web content beside the collapsing sidebar.
Run with the macOS system backend:
```sh
native dev
```
Run the model-contract tests headless:
```sh
native test -Dplatform=null
```
+32
View File
@@ -0,0 +1,32 @@
.{
.id = "dev.native_sdk.split_collapse",
.name = "split-collapse",
.display_name = "Split Collapse",
.description = "A two-pane split whose collapse animates over 180 ms; the frame-pacing measurement harness.",
.version = "0.1.0",
.platforms = .{"macos"},
.permissions = .{ "view", "command" },
.capabilities = .{ "native_views", "gpu_surfaces" },
.shell = .{
.windows = .{
.{
.label = "main",
.title = "Split Collapse",
.width = 800,
.height = 520,
.restore_state = false,
.views = .{
.{ .label = "split-canvas", .kind = "gpu_surface", .fill = true, .role = "Split canvas", .accessibility_label = "Split collapse", .gpu_backend = "metal", .gpu_pixel_format = "bgra8_unorm", .gpu_present_mode = "timer", .gpu_alpha_mode = "opaque", .gpu_color_space = "srgb", .gpu_vsync = true },
},
},
},
},
.security = .{
.navigation = .{
.allowed_origins = .{ "zero://app", "zero://inline" },
.external_links = .{ .action = "deny" },
},
},
.web_engine = "system",
.cef = .{ .dir = "third_party/cef/macos", .auto_install = false },
}
+375
View File
@@ -0,0 +1,375 @@
//! split-collapse: the smallest honest pane-collapse animation harness.
//!
//! A two-pane split whose first-pane fraction tweens between an expanded
//! and a collapsed position over 180 ms. The app exists to measure frame
//! pacing during a layout tween, so it carries BOTH driving styles:
//!
//! - Manual (`--manual`): the historical idiom — `on_frame` returns a
//! tick Msg carrying the presented frame's timestamp, `update` eases
//! the fraction, the rebuild changes layout, and the changed layout
//! requests the next frame. Every model tick is a full rebuild.
//! - Runtime tween (default): the layout-animation primitive — one
//! `runtime.startCanvasLayoutTween` call when the toggle flips, no
//! per-frame Msgs, no rebuilds while the runtime drives the fraction
//! toward the target.
//! - Markup tween (`SPLIT_COLLAPSE_MARKUP=1`): the same primitive
//! declared entirely in markup — `resize-duration="180"` on the
//! split makes its bound value a tween target, so the view file is
//! the whole animation and this Zig file supplies only model/update.
//!
//! Each presented frame during a tween logs `tween-frame dt_ms=...` on
//! stderr, so a driver (or a person) can count the visible steps of the
//! 180 ms collapse and read the real deltas between them.
const std = @import("std");
const runner = @import("runner");
const native_sdk = @import("native_sdk");
pub const panic = std.debug.FullPanic(native_sdk.debug.capturePanic);
const canvas = native_sdk.canvas;
const geometry = native_sdk.geometry;
pub const canvas_label = "split-canvas";
pub const window_width: f32 = 800;
pub const window_height: f32 = 520;
pub const expanded_fraction: f32 = 0.35;
pub const collapsed_fraction: f32 = 0.06;
pub const tween_duration_ms: u32 = 180;
// -------------------------------------------------------------------- model
pub const Tween = struct {
from: f32,
to: f32,
/// First tick stamps the start; 0 means "not started yet" so the
/// tween's clock begins on the first presented frame, not at the
/// (earlier) input timestamp.
start_ns: u64 = 0,
};
pub const Model = struct {
appearance: native_sdk.Appearance = .{},
collapsed: bool = false,
fraction: f32 = expanded_fraction,
tween: ?Tween = null,
/// Presented-frame observations while a tween runs (manual mode).
tween_frame_count: u32 = 0,
last_frame_ns: u64 = 0,
/// True runs the historical on_frame/Msg-tick driver; false asks the
/// runtime to drive the fraction (the layout tween primitive).
manual_mode: bool = false,
/// The markup view's declared RESTING fraction: the split binds
/// `value="{pane_fraction}"` and its `resize-duration` makes this a
/// tween target, so flipping `collapsed` is the whole collapse.
pub fn pane_fraction(model: *const Model) f32 {
return if (model.collapsed) collapsed_fraction else expanded_fraction;
}
pub fn toggle_label(model: *const Model) []const u8 {
return if (model.collapsed) "Expand sidebar" else "Collapse sidebar";
}
pub fn content_hint(model: *const Model) []const u8 {
return if (model.collapsed)
"The sidebar is collapsed; this pane reflowed to fill the width."
else
"The sidebar is expanded; drag the divider or press the button.";
}
};
pub const Msg = union(enum) {
toggle,
auto_toggle: native_sdk.EffectTimer,
frame_tick: u64,
split_resized: f32,
set_appearance: native_sdk.Appearance,
};
fn easeInOutCubic(t: f32) f32 {
if (t < 0.5) return 4 * t * t * t;
const back = -2 * t + 2;
return 1 - (back * back * back) / 2;
}
fn applyToggle(model: *Model) void {
model.collapsed = !model.collapsed;
if (model.manual_mode) {
const target: f32 = if (model.collapsed) collapsed_fraction else expanded_fraction;
model.tween = .{ .from = model.fraction, .to = target };
model.tween_frame_count = 0;
model.last_frame_ns = 0;
}
// Runtime-driven mode changes NOTHING else: the collapsed flag is
// the resting truth, `layoutTweens` declares the target fraction,
// and the runtime moves the divider (the on_resize echo keeps
// `model.fraction` — the declared `value` — in step).
}
pub fn update(model: *Model, msg: Msg) void {
switch (msg) {
.toggle => applyToggle(model),
.auto_toggle => applyToggle(model),
.frame_tick => |timestamp_ns| {
const tween = &(model.tween orelse return);
if (tween.start_ns == 0) tween.start_ns = timestamp_ns;
if (model.last_frame_ns != 0) {
const dt_ns = timestamp_ns -| model.last_frame_ns;
std.debug.print("tween-frame dt_ms={d:.1} fraction={d:.3}\n", .{
@as(f64, @floatFromInt(dt_ns)) / 1_000_000.0,
model.fraction,
});
} else {
std.debug.print("tween-frame dt_ms=start fraction={d:.3}\n", .{model.fraction});
}
model.last_frame_ns = timestamp_ns;
model.tween_frame_count += 1;
const elapsed_ns = timestamp_ns -| tween.start_ns;
const duration_ns: u64 = @as(u64, tween_duration_ms) * 1_000_000;
if (elapsed_ns >= duration_ns) {
model.fraction = tween.to;
model.tween = null;
std.debug.print("tween-done frames={d}\n", .{model.tween_frame_count});
return;
}
const t = @as(f32, @floatFromInt(elapsed_ns)) / @as(f32, @floatFromInt(duration_ns));
model.fraction = tween.from + (tween.to - tween.from) * easeInOutCubic(t);
},
.split_resized => |fraction| {
// The controlled-split echo: divider drags AND runtime
// layout-tween steps both land here. A running MANUAL tween
// owns the fraction, so ignore echoes then; in runtime mode
// every echo is truth (each tween step logs its arrival
// cadence, the same measurement the manual mode takes).
if (model.tween != null) return;
if (!model.manual_mode and fraction != model.fraction) {
std.debug.print("tween-echo fraction={d:.3}\n", .{fraction});
}
model.fraction = fraction;
},
.set_appearance => |appearance| model.appearance = appearance,
}
}
// --------------------------------------------------------------------- view
pub const Ui = canvas.Ui(Msg);
pub fn rootView(ui: *Ui, model: *const Model) Ui.Node {
return ui.column(.{ .grow = 1, .style_tokens = .{ .background = .background } }, .{
headerView(ui, model),
ui.split(.{
.grow = 1,
.value = model.fraction,
.on_resize = Ui.valueMsg(.split_resized),
}, .{
sidebarPane(ui, model),
contentPane(ui, model),
}),
});
}
fn headerView(ui: *Ui, model: *const Model) Ui.Node {
return ui.row(.{ .height = 48, .padding = 8, .gap = 8, .cross = .center }, .{
ui.button(.{
.on_press = .toggle,
.semantics = .{ .label = "Toggle sidebar" },
}, if (model.collapsed) "Expand sidebar" else "Collapse sidebar"),
ui.text(.{ .style_tokens = .{ .foreground = .text_muted } }, if (model.manual_mode) "manual ticks" else "runtime tween"),
});
}
fn sidebarPane(ui: *Ui, model: *const Model) Ui.Node {
_ = model;
return ui.column(.{ .padding = 12, .gap = 8, .style_tokens = .{ .background = .surface } }, .{
ui.text(.{}, "Sidebar"),
ui.text(.{ .style_tokens = .{ .foreground = .text_muted } }, "Inbox"),
ui.text(.{ .style_tokens = .{ .foreground = .text_muted } }, "Drafts"),
ui.text(.{ .style_tokens = .{ .foreground = .text_muted } }, "Archive"),
});
}
fn contentPane(ui: *Ui, model: *const Model) Ui.Node {
return ui.column(.{ .padding = 16, .gap = 8 }, .{
ui.text(.{ .size = .lg }, "Content"),
ui.text(.{ .style_tokens = .{ .foreground = .text_muted } }, if (model.collapsed)
"The sidebar is collapsed; this pane reflowed to fill the width."
else
"The sidebar is expanded; drag the divider or press the button."),
// The web pane's anchor: a growing panel whose layout frame the
// embedded webview snaps to every presented frame — the shape
// where a collapsing neighbor makes the webview reflow live.
ui.column(.{ .grow = 1, .semantics = .{ .label = web_pane_anchor } }, .{}),
});
}
pub const web_pane_label = "content-web";
pub const web_pane_anchor = "content-web-pane";
/// SPLIT_COLLAPSE_WEB=1 declares a live webview snapped to the content
/// pane, reflowing through the whole tween (the heavy field shape).
var web_pane_enabled = false;
fn webPanes(model: *const Model, out: []SplitCollapseApp.WebViewPane) usize {
_ = model;
out[0] = .{
.label = web_pane_label,
.anchor = web_pane_anchor,
.url = "https://example.com",
};
return 1;
}
// ------------------------------------------------------------- markup mode
/// SPLIT_COLLAPSE_MARKUP=1: the markup-only twin of the runtime tween.
/// The whole animation is declared in the view — `resize-duration` on
/// the split — so this mode sets NO on_frame and NO layout_tweens hook;
/// the on-resize echoes still land in `update` as `split_resized`, so
/// the same tween-echo cadence log measures the markup path.
pub const markup_source = @embedFile("split_collapse.native");
pub const CompiledSplitView = canvas.CompiledMarkupView(Model, Msg, markup_source);
/// How the collapse is driven: the historical per-frame Msg idiom, the
/// Zig `layout_tweens` hook, or the markup-declared `resize-duration`.
pub const Mode = enum { manual, runtime, markup };
// ---------------------------------------------------------------------- app
pub const SplitCollapseApp = native_sdk.UiApp(Model, Msg);
pub const Effects = SplitCollapseApp.Effects;
fn onFrame(model: *const Model, frame: native_sdk.platform.GpuFrame) ?Msg {
if (model.tween != null) return Msg{ .frame_tick = frame.timestamp_ns };
return null;
}
/// The layout-tween declaration (runtime mode): the split's target
/// fraction derives from the resting collapsed flag; the runtime eases
/// the rendered fraction toward it whenever they diverge. No per-frame
/// Msgs, no manual clock — and reduced motion snaps inside the runtime.
fn layoutTweens(model: *const Model, tree: *const Ui.Tree, out: []canvas.CanvasWidgetLayoutTween) usize {
const split = findWidgetByKind(tree.root, .split) orelse return 0;
out[0] = .{
.id = split.id,
.to = if (model.collapsed) collapsed_fraction else expanded_fraction,
.duration_ms = tween_duration_ms,
.easing = .standard,
};
return 1;
}
fn findWidgetByKind(widget: canvas.Widget, kind: canvas.WidgetKind) ?canvas.Widget {
if (widget.kind == kind) return widget;
for (widget.children) |child| {
if (findWidgetByKind(child, kind)) |found| return found;
}
return null;
}
/// SPLIT_COLLAPSE_AUTO_MS=<interval> arms a repeating toggle so the
/// tween can be observed live without automation (no polling timers in
/// the host, exactly the field shape).
var auto_toggle_interval_ms: u64 = 0;
fn initFx(model: *Model, fx: *Effects) void {
_ = model;
if (auto_toggle_interval_ms == 0) return;
fx.startTimer(.{
.key = 1,
.interval_ms = auto_toggle_interval_ms,
.mode = .repeating,
.on_fire = Effects.timerMsg(.auto_toggle),
});
}
fn onAppearance(appearance: native_sdk.Appearance) ?Msg {
return Msg{ .set_appearance = appearance };
}
const app_permissions = [_][]const u8{ native_sdk.security.permission_command, native_sdk.security.permission_view };
const shell_views = [_]native_sdk.ShellView{
.{ .label = canvas_label, .kind = .gpu_surface, .fill = true, .role = "Split canvas", .accessibility_label = "Split collapse", .gpu_backend = .metal, .gpu_pixel_format = .bgra8_unorm, .gpu_present_mode = .timer, .gpu_alpha_mode = .@"opaque", .gpu_color_space = .srgb, .gpu_vsync = true },
// A live scene webview parented to the canvas: `webPanes` snaps it
// to the content pane's anchor every presented frame, so the split
// tween reflows real web content (the heavy field shape).
.{ .label = web_pane_label, .kind = .webview, .parent = canvas_label, .url = "https://example.com", .x = 0, .y = 0, .width = 1, .height = 1, .layer = 20 },
};
const shell_windows = [_]native_sdk.ShellWindow{.{
.label = "main",
.title = "Split Collapse",
.width = window_width,
.height = window_height,
.restore_state = false,
.views = &shell_views,
}};
pub const shell_scene: native_sdk.ShellConfig = .{ .windows = &shell_windows };
pub fn appOptions(mode: Mode) SplitCollapseApp.Options {
return .{
.name = "split-collapse",
.scene = shell_scene,
.canvas_label = canvas_label,
.update = update,
// Markup mode renders the compiled markup view; the tween is
// declared IN the view (resize-duration), so neither Zig hook
// exists there.
.view = if (mode == .markup) CompiledSplitView.build else rootView,
.init_fx = initFx,
.on_appearance = onAppearance,
.on_frame = if (mode == .manual) onFrame else null,
.layout_tweens = if (mode == .runtime) layoutTweens else null,
.web_panes = if (web_pane_enabled) webPanes else null,
};
}
pub fn initModel(manual: bool) Model {
return .{ .manual_mode = manual };
}
// --------------------------------------------------------------------- main
pub fn main(init: std.process.Init) !void {
// SPLIT_COLLAPSE_MANUAL=1 selects the historical on_frame/Msg-tick
// driver; SPLIT_COLLAPSE_MARKUP=1 the markup-declared tween
// (resize-duration in the view, no Zig hooks); the default runs the
// Zig-declared runtime layout tween.
const manual = if (init.environ_map.get("SPLIT_COLLAPSE_MANUAL")) |value|
!std.mem.eql(u8, value, "0")
else
false;
const markup_mode = if (init.environ_map.get("SPLIT_COLLAPSE_MARKUP")) |value|
!std.mem.eql(u8, value, "0")
else
false;
const mode: Mode = if (markup_mode) .markup else if (manual) .manual else .runtime;
if (init.environ_map.get("SPLIT_COLLAPSE_AUTO_MS")) |value| {
auto_toggle_interval_ms = std.fmt.parseInt(u64, value, 10) catch 0;
}
if (init.environ_map.get("SPLIT_COLLAPSE_WEB")) |value| {
web_pane_enabled = !std.mem.eql(u8, value, "0");
}
const app_state = try std.heap.page_allocator.create(SplitCollapseApp);
defer std.heap.page_allocator.destroy(app_state);
app_state.* = SplitCollapseApp.init(std.heap.page_allocator, initModel(mode == .manual), appOptions(mode));
defer app_state.deinit();
try runner.runWithOptions(app_state.app(), .{
.app_name = "split-collapse",
.window_title = "Split Collapse",
.bundle_id = "dev.native_sdk.split_collapse",
.default_frame = geometry.RectF.init(0, 0, window_width, window_height),
.restore_state = false,
.js_window_api = false,
.security = .{
.permissions = &app_permissions,
.navigation = .{ .allowed_origins = &.{ "zero://inline", "zero://app", "https://example.com" } },
},
}, init);
}
test {
_ = @import("tests.zig");
}
@@ -0,0 +1,31 @@
<!-- The markup twin of the runtime-tween mode: a MARKUP-ONLY view where
the split's collapse animates with zero Zig hooks. value binds the
model's declared RESTING fraction (derived from the collapsed flag),
resize-duration turns that value into a tween target — a toggle
rebuild eases the rendered fraction there one presented frame at a
time instead of snapping — and on-resize echoes every applied step
into the model exactly as a divider drag would. Reduced-motion
appearances snap inside the runtime; this file declares nothing for
that. -->
<column grow="1" background="background">
<row height="48" padding="8" gap="8" cross="center">
<button on-press="toggle" label="Toggle sidebar">{toggle_label}</button>
<text foreground="text_muted">markup tween</text>
</row>
<split grow="1" value="{pane_fraction}" resize-duration="180" resize-easing="standard" on-resize="split_resized">
<column padding="12" gap="8" background="surface">
<text>Sidebar</text>
<text foreground="text_muted">Inbox</text>
<text foreground="text_muted">Drafts</text>
<text foreground="text_muted">Archive</text>
</column>
<column padding="16" gap="8">
<text size="lg">Content</text>
<text foreground="text_muted">{content_hint}</text>
<!-- The web pane's anchor (SPLIT_COLLAPSE_WEB=1): the embedded
webview snaps to this growing column's frame every presented
frame, so the tween reflows live web content too. -->
<column grow="1" label="content-web-pane"></column>
</column>
</split>
</column>
+58
View File
@@ -0,0 +1,58 @@
//! split-collapse tests: the manual tween's model math (easing endpoints,
//! frame ticks, completion) — the runtime-tween coverage lives with the
//! runtime's own layout-animation tests.
const std = @import("std");
const main = @import("main.zig");
const testing = std.testing;
const Model = main.Model;
test "manual tween eases the fraction from expanded to collapsed and completes" {
var model = main.initModel(true);
try testing.expectEqual(main.expanded_fraction, model.fraction);
main.update(&model, .toggle);
try testing.expect(model.collapsed);
try testing.expect(model.tween != null);
// First tick stamps the start; fraction still at the origin.
main.update(&model, .{ .frame_tick = 1_000_000_000 });
try testing.expectEqual(main.expanded_fraction, model.fraction);
// Halfway: strictly between the endpoints.
main.update(&model, .{ .frame_tick = 1_000_000_000 + 90_000_000 });
try testing.expect(model.fraction < main.expanded_fraction);
try testing.expect(model.fraction > main.collapsed_fraction);
// Past the duration: snapped to the target, tween cleared.
main.update(&model, .{ .frame_tick = 1_000_000_000 + 200_000_000 });
try testing.expectEqual(main.collapsed_fraction, model.fraction);
try testing.expect(model.tween == null);
}
test "runtime mode flips only the resting flag; the tween hook declares the target" {
var model = main.initModel(false);
main.update(&model, .toggle);
// The model's fraction is the controlled `value` (last echo), not
// the target — the runtime moves it; no manual tween ever opens.
try testing.expectEqual(main.expanded_fraction, model.fraction);
try testing.expect(model.collapsed);
try testing.expect(model.tween == null);
// The echo path is the only writer of the fraction in runtime mode.
main.update(&model, .{ .split_resized = 0.21 });
try testing.expectEqual(@as(f32, 0.21), model.fraction);
}
test "markup mode derives the split's declared resting fraction from the collapsed flag" {
// The markup view binds value="{pane_fraction}" with resize-duration,
// so flipping the flag IS the collapse: the declared value moves to
// the target and the runtime tween eases the rendered fraction there.
var model = main.initModel(false);
try testing.expectEqual(main.expanded_fraction, model.pane_fraction());
main.update(&model, .toggle);
try testing.expectEqual(main.collapsed_fraction, model.pane_fraction());
main.update(&model, .toggle);
try testing.expectEqual(main.expanded_fraction, model.pane_fraction());
}