also looking at this
feat: add Linux and Windows named-window routing
#325
29 files
+2250
-200
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.
| @@ -32,6 +32,8 @@ The `craft-native` SDK returns a typed `Window` handle. Runtime-created | ||
| 32 | 32 | windows are available on macOS, Linux, and Windows and must provide either |
| 33 | 33 | `html` or `url` content. The common create, close, title, geometry, and event |
| 34 | 34 | routes are cross-platform; some advanced window controls remain macOS-only. |
| 35 | On Linux, the window manager may ignore position requests, especially before | |
| 36 | the window is shown. | |
| 35 | 37 | |
| 36 | 38 | ```typescript |
| 37 | 39 | import { createWindow } from 'craft-native' |
| @@ -20,6 +20,7 @@ pub extern "c" fn g_application_run(app: *anyopaque, argc: c_int, argv: [*c][*c] | ||
| 20 | 20 | pub extern "c" fn gtk_application_window_new(app: *anyopaque) *anyopaque; |
| 21 | 21 | pub extern "c" fn gtk_window_set_title(window: *anyopaque, title: [*:0]const u8) void; |
| 22 | 22 | pub extern "c" fn gtk_window_set_default_size(window: *anyopaque, width: c_int, height: c_int) void; |
| 23 | pub extern "c" fn gtk_window_resize(window: *anyopaque, width: c_int, height: c_int) void; | |
| 23 | 24 | pub extern "c" fn gtk_window_present(window: *anyopaque) void; |
| 24 | 25 | pub extern "c" fn gtk_window_close(window: *anyopaque) void; |
| 25 | 26 | pub extern "c" fn gtk_window_set_decorated(window: *anyopaque, decorated: c_int) void; |
| @@ -31,7 +32,7 @@ pub extern "c" fn gtk_window_unmaximize(window: *anyopaque) void; | ||
| 31 | 32 | pub extern "c" fn gtk_window_iconify(window: *anyopaque) void; |
| 32 | 33 | pub extern "c" fn gtk_widget_hide(widget: *anyopaque) void; |
| 33 | 34 | pub extern "c" fn gtk_widget_show(widget: *anyopaque) void; |
| 34 | pub extern "c" fn gtk_window_set_position(window: *anyopaque, x: c_int, y: c_int) void; | |
| 35 | pub extern "c" fn gtk_window_move(window: *anyopaque, x: c_int, y: c_int) void; | |
| 35 | 36 | pub extern "c" fn gtk_window_get_position(window: *anyopaque, x: *c_int, y: *c_int) void; |
| 36 | 37 | pub extern "c" fn gtk_window_get_size(window: *anyopaque, width: *c_int, height: *c_int) void; |
| 37 | 38 | pub extern "c" fn gtk_window_get_title(window: *anyopaque) ?[*:0]const u8; |
| @@ -493,12 +494,12 @@ fn handleWindowAction(action: []const u8, data: ?[]const u8) !void { | ||
| 493 | 494 | const height = json_utils.getInt(u32, json, "height") orelse return error.InvalidParameter; |
| 494 | 495 | if (width == 0 or height == 0 or width > @as(u32, std.math.maxInt(c_int)) or height > @as(u32, std.math.maxInt(c_int))) |
| 495 | 496 | return error.InvalidParameter; |
| 496 | gtk_window_set_default_size(window, @intCast(width), @intCast(height)); | |
| 497 | gtk_window_resize(window, @intCast(width), @intCast(height)); | |
| 497 | 498 | } else if (std.mem.eql(u8, action, "setPosition")) { |
| 498 | 499 | const json = data orelse return error.MissingData; |
| 499 | 500 | const x = json_utils.getInt(i32, json, "x") orelse return error.InvalidParameter; |
| 500 | 501 | const y = json_utils.getInt(i32, json, "y") orelse return error.InvalidParameter; |
| 501 | gtk_window_set_position(window, x, y); | |
| 502 | gtk_window_move(window, x, y); | |
| 502 | 503 | } else if (std.mem.eql(u8, action, "loadURL") or std.mem.eql(u8, action, "loadHTML")) { |
| 503 | 504 | const json = data orelse return error.MissingData; |
| 504 | 505 | const key: []const u8 = if (std.mem.eql(u8, action, "loadURL")) "url" else "html"; |
| @@ -627,7 +628,7 @@ pub const Window = struct { | ||
| 627 | 628 | const x: i32 = options.x orelse 100; |
| 628 | 629 | const y: i32 = options.y orelse 100; |
| 629 | 630 | if (options.x != null and options.y != null) { |
| 630 | gtk_window_set_position(window, @intCast(x), @intCast(y)); | |
| 631 | gtk_window_move(window, @intCast(x), @intCast(y)); | |
| 631 | 632 | } |
| 632 | 633 | |
| 633 | 634 | // Add WebView to window |
| @@ -681,11 +682,12 @@ pub const Window = struct { | ||
| 681 | 682 | } |
| 682 | 683 | |
| 683 | 684 | pub fn setSize(self: *Window, width: u32, height: u32) void { |
| 684 | gtk_window_set_default_size(self.gtk_window, @intCast(width), @intCast(height)); | |
| 685 | if (width == 0 or height == 0 or width > @as(u32, std.math.maxInt(c_int)) or height > @as(u32, std.math.maxInt(c_int))) return; | |
| 686 | gtk_window_resize(self.gtk_window, @intCast(width), @intCast(height)); | |
| 685 | 687 | } |
| 686 | 688 | |
| 687 | 689 | pub fn setPosition(self: *Window, x: i32, y: i32) void { |
| 688 | gtk_window_set_position(self.gtk_window, @intCast(x), @intCast(y)); | |
| 690 | gtk_window_move(self.gtk_window, x, y); | |
| 689 | 691 | } |
| 690 | 692 | |
| 691 | 693 | pub fn setTitle(self: *Window, title: []const u8) void { |
| @@ -30,6 +30,17 @@ const mainPage = `<!doctype html><script> | ||
| 30 | 30 | await waitFor('child-1') |
| 31 | 31 | const bounds = await window.craft.window._call('getBounds', {}, 'settings') |
| 32 | 32 | if (!(bounds.width > 0 && bounds.height > 0)) throw new Error('child bounds not routed to creator') |
| 33 | await window.craft.window._call('setSize', { width: 640, height: 480 }, 'settings') | |
| 34 | let resized = false | |
| 35 | for (let attempt = 0; attempt < 40; attempt++) { | |
| 36 | const size = await window.craft.window._call('getSize', {}, 'settings') | |
| 37 | if (Math.abs(size.width - 640) <= 40 && Math.abs(size.height - 480) <= 40) { | |
| 38 | resized = true | |
| 39 | break | |
| 40 | } | |
| 41 | await new Promise(resolve => setTimeout(resolve, 100)) | |
| 42 | } | |
| 43 | if (!resized) throw new Error('live child resize did not change its size') | |
| 33 | 44 | const title = await window.craft.window._call('getTitle', {}, 'settings') |
| 34 | 45 | if (title !== 'Child 1') throw new Error('child title not routed to creator: ' + title) |
| 35 | 46 | |