From 675ba0e9b8b4d7cbbe6855cfb0eace58d8bccbd7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Aug 2025 09:58:47 -0700 Subject: [PATCH] apprt/gtk-ng: defineVirtualMethod helper --- src/apprt/gtk-ng/class.zig | 72 +++++++++++++++++++++++++ src/apprt/gtk-ng/class/imgui_widget.zig | 24 ++------- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/apprt/gtk-ng/class.zig b/src/apprt/gtk-ng/class.zig index 0bfc049f6..4b46f8365 100644 --- a/src/apprt/gtk-ng/class.zig +++ b/src/apprt/gtk-ng/class.zig @@ -1,6 +1,7 @@ //! This files contains all the GObject classes for the GTK apprt //! along with helpers to work with them. +const std = @import("std"); const glib = @import("glib"); const gobject = @import("gobject"); const gtk = @import("gtk"); @@ -87,6 +88,77 @@ pub fn Common( return @ptrCast(type_instance.f_g_class orelse return null); } + /// Define a virtual method. The `Self.Class` type must have a field + /// named `name` which is a function pointer in the following form: + /// + /// ?*const fn (*Self) callconv(.c) void + /// + /// The virtual method may take additional parameters and specify + /// a non-void return type. The parameters and return type must be + /// valid for the C calling convention. + pub fn defineVirtualMethod( + comptime name: [:0]const u8, + ) type { + return struct { + pub fn call( + class: anytype, + object: *ClassInstance(@TypeOf(class)), + params: anytype, + ) (fn_info.return_type orelse void) { + const func = @field( + gobject.ext.as(Self.Class, class), + name, + ).?; + @call(.auto, func, .{ + gobject.ext.as(Self, object), + } ++ params); + } + + pub fn implement( + class: anytype, + implementation: *const ImplementFunc(@TypeOf(class)), + ) void { + @field(gobject.ext.as( + Self.Class, + class, + ), name) = @ptrCast(implementation); + } + + /// The type info of the virtual method. + const fn_info = fn_info: { + // This is broken down like this so its slightly more + // readable. We expect a field named "name" on the Class + // with the rough type of `?*const fn` and we need the + // function info. + const Field = @FieldType(Self.Class, name); + const opt = @typeInfo(Field).optional; + const ptr = @typeInfo(opt.child).pointer; + break :fn_info @typeInfo(ptr.child).@"fn"; + }; + + /// The instance type for a class. + fn ClassInstance(comptime T: type) type { + return @typeInfo(T).pointer.child.Instance; + } + + /// The function type for implementations. This is the same type + /// as the virtual method but the self parameter points to the + /// target instead of the original class. + fn ImplementFunc(comptime T: type) type { + var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined; + @memcpy(¶ms, fn_info.params); + params[0].type = *ClassInstance(T); + return @Type(.{ .@"fn" = .{ + .calling_convention = fn_info.calling_convention, + .is_generic = fn_info.is_generic, + .is_var_args = fn_info.is_var_args, + .return_type = fn_info.return_type, + .params = ¶ms, + } }); + } + }; + } + /// A helper that creates a property that reads and writes a /// private field with only shallow copies. This is good for primitives /// such as bools, numbers, etc. diff --git a/src/apprt/gtk-ng/class/imgui_widget.zig b/src/apprt/gtk-ng/class/imgui_widget.zig index d9851b4a1..854dec20b 100644 --- a/src/apprt/gtk-ng/class/imgui_widget.zig +++ b/src/apprt/gtk-ng/class/imgui_widget.zig @@ -41,28 +41,12 @@ pub const ImguiWidget = extern struct { /// This virtual method will be called to allow the Dear ImGui /// application to do one-time setup of the context. The correct context /// will be current when the virtual method is called. - pub const setup = struct { - pub fn call(class: anytype, object: *@typeInfo(@TypeOf(class)).pointer.child.Instance) void { - return gobject.ext.as(Self.Class, class).setup.?(gobject.ext.as(Self, object)); - } - - pub fn implement(class: anytype, implementation: *const fn (p_object: *@typeInfo(@TypeOf(class)).pointer.child.Instance) callconv(.c) void) void { - gobject.ext.as(Self.Class, class).setup = @ptrCast(implementation); - } - }; + pub const setup = C.defineVirtualMethod("setup"); /// This virtual method will be called at each frame to allow the Dear /// ImGui application to draw the application. The correct context will /// be current when the virtual method is called. - pub const render = struct { - pub fn call(class: anytype, object: *@typeInfo(@TypeOf(class)).pointer.child.Instance) void { - return gobject.ext.as(Self.Class, class).render.?(gobject.ext.as(Self, object)); - } - - pub fn implement(class: anytype, implementation: *const fn (p_object: *@typeInfo(@TypeOf(class)).pointer.child.Instance) callconv(.c) void) void { - gobject.ext.as(Self.Class, class).render = @ptrCast(implementation); - } - }; + pub const render = C.defineVirtualMethod("render"); }; const Private = struct { @@ -119,7 +103,7 @@ pub const ImguiWidget = extern struct { /// when the virtual method is called. pub fn setup(self: *Self) callconv(.c) void { const class = self.getClass() orelse return; - virtual_methods.setup.call(class, self); + virtual_methods.setup.call(class, self, .{}); } /// This virtual method will be called at each frame to allow the Dear ImGui @@ -127,7 +111,7 @@ pub const ImguiWidget = extern struct { /// when the virtual method is called. pub fn render(self: *Self) callconv(.c) void { const class = self.getClass() orelse return; - virtual_methods.render.call(class, self); + virtual_methods.render.call(class, self, .{}); } //---------------------------------------------------------------