From 0ed547af0e6a869665227f509cd8f419f879ebe7 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Mon, 18 Oct 2021 00:02:57 -0700 Subject: [PATCH] glfw: add system for nice Zig callbacks, add Window.setPosCallback Signed-off-by: Stephen Gutekanst --- src/Window.zig | 137 ++++++++++++++++++++++++++++++++++--------------- src/opengl.zig | 2 +- 2 files changed, 98 insertions(+), 41 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 4de37e9..08ea0b1 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -14,6 +14,31 @@ const Window = @This(); handle: *c.GLFWwindow, +/// Returns a Zig GLFW window from an underlying C GLFW window handle. +/// +/// Note that the Zig GLFW library stores a custom user pointer in order to make callbacks nicer, +/// see glfw.Window.InternalUserPointer. +pub inline fn from(handle: *c.GLFWwindow) Error!Window { + const ptr = c.glfwGetWindowUserPointer(handle); + if (ptr == null) { + const internal = try std.heap.c_allocator.create(InternalUserPointer); + c.glfwSetWindowUserPointer(handle, @ptrCast(*c_void, internal)); + try getError(); + } + return Window{ .handle = handle }; +} + +/// The actual type which is stored by the Zig GLFW library in glfwSetWindowUserPointer. +/// +/// This is used to internally carry function callbacks with nicer Zig interfaces. +pub const InternalUserPointer = struct { + /// The actual user pointer that the user of the library wished to set via setUserPointer. + user_pointer: ?*c_void, + + // Callbacks to be invoked by wrapper functions. + setPosCallback: ?fn (window: Window, xpos: isize, ypos: isize) void, +}; + /// Resets all window hints to their default values. /// /// This function resets all window hints to their default values. @@ -212,7 +237,7 @@ pub inline fn create(width: usize, height: usize, title: [*c]const u8, monitor: if (share) |w| w.handle else null, ); try getError(); - return Window{ .handle = handle.? }; + return from(handle.?); } /// Destroys the specified window and its context. @@ -232,7 +257,9 @@ pub inline fn create(width: usize, height: usize, title: [*c]const u8, monitor: /// /// see also: window_creation, glfw.Window.create pub inline fn destroy(self: Window) void { + const internal = self.getInternal(); c.glfwDestroyWindow(self.handle); + std.heap.c_allocator.destroy(internal); // Technically, glfwDestroyWindow could produce errors including glfw.Error.NotInitialized and // glfw.Error.PlatformError. But how would anybody handle them? By creating a new window to @@ -1060,6 +1087,12 @@ pub inline fn setAttrib(self: Window, attrib: isize, value: bool) Error!void { try getError(); } +pub inline fn getInternal(self: Window) *InternalUserPointer { + const ptr = c.glfwGetWindowUserPointer(self.handle); + if (ptr) |p| return @ptrCast(*InternalUserPointer, @alignCast(@alignOf(*InternalUserPointer), p)); + @panic("expected GLFW window user pointer to be *glfw.Window.InternalUserPointer, found null"); +} + /// Sets the user pointer of the specified window. /// /// This function sets the user-defined pointer of the specified window. The current value is @@ -1069,12 +1102,8 @@ pub inline fn setAttrib(self: Window, attrib: isize, value: bool) Error!void { /// /// see also: window_userptr, glfw.Window.getUserPointer pub inline fn setUserPointer(self: Window, Type: anytype, pointer: Type) void { - c.glfwSetWindowUserPointer(self.handle, @ptrCast(*c_void, pointer)); - - // The only error this could return would be glfw.Error.NotInitialized, which should - // definitely have occurred before calls to this. Returning an error here makes the API - // awkward to use, so we discard it instead. - getError() catch {}; + var internal = self.getInternal(); + internal.user_pointer = @ptrCast(*c_void, pointer); } /// Returns the user pointer of the specified window. @@ -1086,43 +1115,49 @@ pub inline fn setUserPointer(self: Window, Type: anytype, pointer: Type) void { /// /// see also: window_userptr, glfw.Window.setUserPointer pub inline fn getUserPointer(self: Window, Type: anytype) ?Type { - const ptr = c.glfwGetWindowUserPointer(self.handle); - if (ptr) |p| return @ptrCast(Type, @alignCast(@alignOf(Type), p)); + var internal = self.getInternal(); + if (internal.user_pointer) |p| return @ptrCast(Type, @alignCast(@alignOf(Type), p)); return null; } -// TODO(window): +fn setPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.C) void { + const window = from(handle.?) catch unreachable; + const internal = window.getInternal(); + internal.setPosCallback.?(window, @intCast(isize, xpos), @intCast(isize, ypos)); +} -// /// Sets the position callback for the specified window. -// /// -// /// This function sets the position callback of the specified window, which is -// /// called when the window is moved. The callback is provided with the -// /// position, in screen coordinates, of the upper-left corner of the content -// /// area of the window. -// /// -// /// @param[in] window The window whose callback to set. -// /// @param[in] callback The new callback, or null to remove the currently set -// /// callback. -// /// @return The previously set callback, or null if no callback was set or the -// /// library had not been [initialized](@ref intro_init). -// /// -// /// @callback_signature -// /// @code -// /// void function_name(GLFWwindow* window, int xpos, int ypos) -// /// @endcode -// /// For more information about the callback parameters, see the -// /// [function pointer type](@ref GLFWwindowposfun). -// /// -// /// Possible errors include glfw.Error.NotInitialized. -// /// -// /// wayland: This callback will never be called, as there is no way for -// /// an application to know its global position. -// /// -// /// @thread_safety This function must only be called from the main thread. -// /// -// /// see also: window_pos -// /// -// GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindowposfun callback); +/// Sets the position callback for the specified window. +/// +/// This function sets the position callback of the specified window, which is called when the +/// window is moved. The callback is provided with the position, in screen coordinates, of the +/// upper-left corner of the content area of the window. +/// +/// @param[in] callback The new callback, or null to remove the currently set callback. +/// +/// @callback_param `window` the window that moved. +/// @callback_param `xpos` the new x-coordinate, in screen coordinates, of the upper-left corner of +/// the content area of the window. +/// @callback_param `ypos` the new y-coordinate, in screen coordinates, of the upper-left corner of +/// the content area of the window. +/// +/// wayland: This callback will never be called, as there is no way for an application to know its +/// global position. +/// +/// @thread_safety This function must only be called from the main thread. +/// +/// see also: window_pos +pub inline fn setPosCallback(self: Window, callback: ?fn (window: Window, xpos: isize, ypos: isize) void) void { + var internal = self.getInternal(); + internal.setPosCallback = callback; + _ = c.glfwSetWindowPosCallback(self.handle, if (callback != null) setPosCallbackWrapper else null); + + // The only error this could return would be glfw.Error.NotInitialized, which should + // definitely have occurred before calls to this. Returning an error here makes the API + // awkward to use, so we discard it instead. + getError() catch {}; +} + +// TODO(window): // /// Sets the size callback for the specified window. // /// @@ -1899,3 +1934,25 @@ test "getUserPointer" { const got = window.getUserPointer(*T); std.debug.assert(&my_value == got); } + +test "setPosCallback" { + const glfw = @import("main.zig"); + try glfw.init(); + defer glfw.terminate(); + + const window = glfw.Window.create(640, 480, "Hello, Zig!", null, null) catch |err| { + // return without fail, because most of our CI environments are headless / we cannot open + // windows on them. + std.debug.print("note: failed to create window: {}\n", .{err}); + return; + }; + defer window.destroy(); + + window.setPosCallback((struct { + fn callback(_window: Window, xpos: isize, ypos: isize) void { + _ = _window; + _ = xpos; + _ = ypos; + } + }).callback); +} diff --git a/src/opengl.zig b/src/opengl.zig index 0b0b8aa..cc3a713 100644 --- a/src/opengl.zig +++ b/src/opengl.zig @@ -48,7 +48,7 @@ pub inline fn makeContextCurrent(window: ?Window) Error!void { pub inline fn getCurrentContext() Error!?Window { const handle = c.glfwGetCurrentContext(); try getError(); - if (handle) |h| return Window{ .handle = h }; + if (handle) |h| return try Window.from(h); return null; }