summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-08-18 09:58:47 -0700
committerMitchell Hashimoto <m@mitchellh.com>2025-08-18 09:58:51 -0700
commit675ba0e9b8b4d7cbbe6855cfb0eace58d8bccbd7 (patch)
tree6ecab1e0f5d9e92a826fa4d962acc55179ddb888
parent1693c9a2aca107df208a3bfd82e3218fab88efbc (diff)
apprt/gtk-ng: defineVirtualMethod helper
-rw-r--r--src/apprt/gtk-ng/class.zig72
-rw-r--r--src/apprt/gtk-ng/class/imgui_widget.zig24
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(&params, 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 = &params,
+ } });
+ }
+ };
+ }
+
/// 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, .{});
}
//---------------------------------------------------------------