ReviewOS

also looking at this

craft-native/craft

feat: portable typed window controls on Linux and Windows

#326
Merged glennmichael123 wants to merge codex/portable-window-parity into main
9 files +652 -32

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

docs/features/window-management.mdmodified+2-0
Changes to docs/features/window-management.md
@@ -77,6 +77,8 @@ await settings.toggleFullscreen()
7777The bounds update preserves omitted coordinates and dimensions. Native window
7878managers can adjust requested geometry for decorations or screen constraints;
7979read `getBounds()` after a resize event if you need the actual result.
80Creation-time `minWidth`/`minHeight` and `maxWidth`/`maxHeight` may constrain
81either axis independently; the native window system's own minimum still applies.
8082
8183The older `createWindow(html, options)` overload remains available when the
8284content is already in a string.
packages/zig/src/windows.zigmodified+30-7
Changes to packages/zig/src/windows.zig
@@ -1189,8 +1189,10 @@ fn setWindowFullscreen(entry: desktop_window_registry.Entry, fullscreen: bool) !
11891189 setWindowStyle(hwnd, (style & ~WS_OVERLAPPEDWINDOW) | WS_POPUP);
11901190 const width = @as(i64, info.rcMonitor.right) - @as(i64, info.rcMonitor.left);
11911191 const height = @as(i64, info.rcMonitor.bottom) - @as(i64, info.rcMonitor.top);
1192 // HWND_TOP (null) raises a child requested by another page. Keeping
1193 // its old Z-order can leave a borderless "fullscreen" behind that page.
11921194 if (width <= 0 or height <= 0 or width > std.math.maxInt(c_int) or height > std.math.maxInt(c_int) or
1193 SetWindowPos(hwnd, null, @intCast(info.rcMonitor.left), @intCast(info.rcMonitor.top), @intCast(width), @intCast(height), SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE) == 0)
1195 SetWindowPos(hwnd, null, @intCast(info.rcMonitor.left), @intCast(info.rcMonitor.top), @intCast(width), @intCast(height), SWP_FRAMECHANGED | SWP_NOACTIVATE) == 0)
11941196 {
11951197 setWindowStyle(hwnd, style);
11961198 _ = SetWindowPos(hwnd, null, bounds.x, bounds.y, @intCast(bounds.width), @intCast(bounds.height), SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
@@ -1216,6 +1218,32 @@ fn setWindowFullscreen(entry: desktop_window_registry.Entry, fullscreen: bool) !
12161218 }
12171219}
12181220
1221fn applyTrackingLimits(info: *MINMAXINFO, limits: desktop_window_controls.Limits) void {
1222 if (limits.minimum) |min| {
1223 info.ptMinTrackSize.x = @max(info.ptMinTrackSize.x, @as(c_long, @intCast(min.width)));
1224 info.ptMinTrackSize.y = @max(info.ptMinTrackSize.y, @as(c_long, @intCast(min.height)));
1225 }
1226 if (limits.maximum) |max| {
1227 info.ptMaxTrackSize.x = @max(info.ptMinTrackSize.x, @min(info.ptMaxTrackSize.x, @as(c_long, @intCast(max.width))));
1228 info.ptMaxTrackSize.y = @max(info.ptMinTrackSize.y, @min(info.ptMaxTrackSize.y, @as(c_long, @intCast(max.height))));
1229 }
1230}
1231
1232test "Windows tracking limits retain native defaults on an omitted axis" {
1233 var info: MINMAXINFO = .{
1234 .ptReserved = .{ .x = 0, .y = 0 },
1235 .ptMaxSize = .{ .x = 0, .y = 0 },
1236 .ptMaxPosition = .{ .x = 0, .y = 0 },
1237 .ptMinTrackSize = .{ .x = 120, .y = 80 },
1238 .ptMaxTrackSize = .{ .x = 2000, .y = 1300 },
1239 };
1240 applyTrackingLimits(&info, try desktop_window_controls.parseCreateLimits("{\"minWidth\":320,\"maxWidth\":1024}"));
1241 try std.testing.expectEqual(@as(c_long, 320), info.ptMinTrackSize.x);
1242 try std.testing.expectEqual(@as(c_long, 80), info.ptMinTrackSize.y);
1243 try std.testing.expectEqual(@as(c_long, 1024), info.ptMaxTrackSize.x);
1244 try std.testing.expectEqual(@as(c_long, 1300), info.ptMaxTrackSize.y);
1245}
1246
12191247fn windowTitle(allocator: std.mem.Allocator, hwnd: HWND) ![:0]u8 {
12201248 const length = GetWindowTextLengthW(hwnd);
12211249 if (length < 0) return error.NativeCallFailed;
@@ -1698,12 +1726,7 @@ fn WindowProc(hwnd: HWND, msg: UINT, wParam: WPARAM, lParam: LPARAM) callconv(.c
16981726 WM_GETMINMAXINFO => {
16991727 if (desktop_windows.byWindow(@intFromPtr(hwnd))) |entry| {
17001728 const info: *MINMAXINFO = @ptrFromInt(@as(usize, @bitCast(lParam)));
1701 if (entry.limits.minimum) |min| {
1702 info.ptMinTrackSize = .{ .x = @intCast(min.width), .y = @intCast(min.height) };
1703 }
1704 if (entry.limits.maximum) |max| {
1705 info.ptMaxTrackSize = .{ .x = @intCast(max.width), .y = @intCast(max.height) };
1706 }
1729 applyTrackingLimits(info, entry.limits);
17071730 return 0;
17081731 }
17091732 },
scripts/verify-desktop-multi-window.tsmodified+2-2
Changes to scripts/verify-desktop-multi-window.ts
@@ -24,7 +24,7 @@ const mainPage = `<!doctype html><script>
2424 const child = (cycle) => window.craft.window.open({
2525 name: 'settings', title: 'Child ' + cycle,
2626 url: location.origin + '/child?cycle=' + cycle,
27 minWidth: 300, minHeight: 200, maxWidth: 1200, maxHeight: 900,
27 minWidth: 300, maxWidth: 1200,
2828 })
2929 const call = (action, data = {}, id = 'settings') => window.craft.window._call(action, data, id)
3030 const waitSize = async (predicate, label) => {
@@ -53,7 +53,7 @@ const mainPage = `<!doctype html><script>
5353 const bounds = await window.craft.window._call('getBounds', {}, 'settings')
5454 if (!(bounds.width > 0 && bounds.height > 0)) throw new Error('child bounds not routed to creator')
5555 await call('setSize', { width: 100, height: 100 })
56 await waitSize(size => size.width >= 280 && size.height >= 180, 'creation limits')
56 await waitSize(size => size.width >= 280, 'one-axis creation limits')
5757 await window.craft.window._call('setSize', { width: 640, height: 480 }, 'settings')
5858 let resized = false
5959 for (let attempt = 0; attempt < 40; attempt++) {