apprt/gtk-ng: extract common methods into a mixin

Every GObject class we're ever going to make has the same handful of
methods. This adds *just enough* noise to be annoying. This commit
extracts the common ones so far into a central mixin. Since Zig is
removing `usingnamespace` and has no other mixin mechanism, we must
forward the decls, but this is still cleaner imo than what we did
before.

I suspect longer term we can probably abstract more of the `zig-gobject`
boilerplate into a higher level abstraction but I'm not confident doing
that yet across the 4 classes we have so far.
This commit is contained in:
Mitchell Hashimoto
2025-07-18 07:22:16 -07:00
parent 7d99042070
commit 833f7f1142
5 changed files with 70 additions and 68 deletions

View File

@ -24,3 +24,45 @@ pub fn unrefLater(obj: anytype) void {
} }
}).callback, obj.as(gobject.Object)); }).callback, obj.as(gobject.Object));
} }
/// Common methods for all GObject classes we create.
pub fn Common(
comptime Self: type,
comptime Private: ?type,
) type {
return struct {
/// Upcast our type to a parent type or interface. This will fail at
/// compile time if the cast isn't 100% safe. For unsafe casts,
/// use `gobject.ext.cast` instead. We don't have a helper for that
/// because its uncommon and unsafe behavior should be noisier.
pub fn as(self: *Self, comptime T: type) *T {
return gobject.ext.as(T, self);
}
/// Increase the reference count of the object.
pub fn ref(self: *Self) *Self {
return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
}
/// Decrease the reference count of the object.
pub fn unref(self: *Self) void {
gobject.Object.unref(self.as(gobject.Object));
}
/// Access the private data of the object. This should be forwarded
/// via a non-pub const usually.
pub const private = if (Private) |P| (struct {
fn private(self: *Self) *P {
return gobject.ext.impl_helpers.getPrivate(
self,
P,
P.offset,
);
}
}).private else {};
};
}
test {
@import("std").testing.refAllDecls(@This());
}

View File

@ -20,6 +20,7 @@ const CoreConfig = configpkg.Config;
const adw_version = @import("../adw_version.zig"); const adw_version = @import("../adw_version.zig");
const gtk_version = @import("../gtk_version.zig"); const gtk_version = @import("../gtk_version.zig");
const ApprtApp = @import("../App.zig"); const ApprtApp = @import("../App.zig");
const Common = @import("../class.zig").Common;
const WeakRef = @import("../weak_ref.zig").WeakRef; const WeakRef = @import("../weak_ref.zig").WeakRef;
const Config = @import("config.zig").Config; const Config = @import("config.zig").Config;
const Window = @import("window.zig").Window; const Window = @import("window.zig").Window;
@ -84,7 +85,7 @@ pub const Application = extern struct {
/// outside of our own lifecycle and that's okay. /// outside of our own lifecycle and that's okay.
config_errors_dialog: WeakRef(ConfigErrorsDialog) = .{}, config_errors_dialog: WeakRef(ConfigErrorsDialog) = .{},
var offset: c_int = 0; pub var offset: c_int = 0;
}; };
/// Creates a new Application instance. /// Creates a new Application instance.
@ -699,21 +700,11 @@ pub const Application = extern struct {
return self.private().core_app.alloc; return self.private().core_app.alloc;
} }
pub fn as(app: *Self, comptime T: type) *T { const C = Common(Self, Private);
return gobject.ext.as(T, app); pub const as = C.as;
} pub const ref = C.ref;
pub const unref = C.unref;
pub fn unref(self: *Self) void { const private = C.private;
gobject.Object.unref(self.as(gobject.Object));
}
fn private(self: *Self) *Private {
return gobject.ext.impl_helpers.getPrivate(
self,
Private,
Private.offset,
);
}
pub const Class = extern struct { pub const Class = extern struct {
parent_class: Parent.Class, parent_class: Parent.Class,

View File

@ -9,6 +9,7 @@ const configpkg = @import("../../../config.zig");
const CoreConfig = configpkg.Config; const CoreConfig = configpkg.Config;
const unrefLater = @import("../class.zig").unrefLater; const unrefLater = @import("../class.zig").unrefLater;
const Common = @import("../class.zig").Common;
const log = std.log.scoped(.gtk_ghostty_config); const log = std.log.scoped(.gtk_ghostty_config);
@ -66,7 +67,7 @@ pub const Config = extern struct {
const Private = struct { const Private = struct {
config: CoreConfig, config: CoreConfig,
var offset: c_int = 0; pub var offset: c_int = 0;
}; };
/// Create a new GhosttyConfig from a loaded configuration. /// Create a new GhosttyConfig from a loaded configuration.
@ -141,25 +142,11 @@ pub const Config = extern struct {
); );
} }
pub fn as(self: *Self, comptime T: type) *T { const C = Common(Self, Private);
return gobject.ext.as(T, self); pub const as = C.as;
} pub const ref = C.ref;
pub const unref = C.unref;
pub fn ref(self: *Self) *Self { const private = C.private;
return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
}
pub fn unref(self: *Self) void {
gobject.Object.unref(self.as(gobject.Object));
}
fn private(self: *Self) *Private {
return gobject.ext.impl_helpers.getPrivate(
self,
Private,
Private.offset,
);
}
pub const Class = extern struct { pub const Class = extern struct {
parent_class: Parent.Class, parent_class: Parent.Class,

View File

@ -5,6 +5,7 @@ const gtk = @import("gtk");
const gresource = @import("../build/gresource.zig"); const gresource = @import("../build/gresource.zig");
const adw_version = @import("../adw_version.zig"); const adw_version = @import("../adw_version.zig");
const Common = @import("../class.zig").Common;
const Config = @import("config.zig").Config; const Config = @import("config.zig").Config;
const log = std.log.scoped(.gtk_ghostty_config_errors_dialog); const log = std.log.scoped(.gtk_ghostty_config_errors_dialog);
@ -58,7 +59,7 @@ pub const ConfigErrorsDialog = extern struct {
const Private = struct { const Private = struct {
config: ?*Config, config: ?*Config,
var offset: c_int = 0; pub var offset: c_int = 0;
}; };
pub fn new(config: *Config) *Self { pub fn new(config: *Config) *Self {
@ -129,25 +130,11 @@ pub const ConfigErrorsDialog = extern struct {
priv.config = config; priv.config = config;
} }
pub fn as(win: *Self, comptime T: type) *T { const C = Common(Self, Private);
return gobject.ext.as(T, win); pub const as = C.as;
} pub const ref = C.ref;
pub const unref = C.unref;
pub fn ref(self: *Self) *Self { const private = C.private;
return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
}
pub fn unref(self: *Self) void {
gobject.Object.unref(self.as(gobject.Object));
}
fn private(self: *Self) *Private {
return gobject.ext.impl_helpers.getPrivate(
self,
Private,
Private.offset,
);
}
pub const Class = extern struct { pub const Class = extern struct {
parent_class: Parent.Class, parent_class: Parent.Class,

View File

@ -4,6 +4,7 @@ const gobject = @import("gobject");
const gtk = @import("gtk"); const gtk = @import("gtk");
const gresource = @import("../build/gresource.zig"); const gresource = @import("../build/gresource.zig");
const Common = @import("../class.zig").Common;
const Application = @import("application.zig").Application; const Application = @import("application.zig").Application;
const log = std.log.scoped(.gtk_ghostty_window); const log = std.log.scoped(.gtk_ghostty_window);
@ -22,7 +23,7 @@ pub const Window = extern struct {
const Private = struct { const Private = struct {
_todo: u8, _todo: u8,
var offset: c_int = 0; pub var offset: c_int = 0;
}; };
pub fn new(app: *Application) *Self { pub fn new(app: *Application) *Self {
@ -45,17 +46,11 @@ pub const Window = extern struct {
); );
} }
pub fn as(self: *Self, comptime T: type) *T { const C = Common(Self, Private);
return gobject.ext.as(T, self); pub const as = C.as;
} pub const ref = C.ref;
pub const unref = C.unref;
fn private(self: *Self) *Private { const private = C.private;
return gobject.ext.impl_helpers.getPrivate(
self,
Private,
Private.offset,
);
}
pub const Class = extern struct { pub const Class = extern struct {
parent_class: Parent.Class, parent_class: Parent.Class,