gtk-ng: use virtual methods to draw the inspector (#8237)
Insead of signals between the ImGui widget and the Inspector widget, make the Inspector widget a subclass of the ImGui widget and use virtual methods to handle setup and rendering of the Inspector.pull/8281/head
commit
d8842b933b
|
|
@ -1,6 +1,7 @@
|
||||||
//! This files contains all the GObject classes for the GTK apprt
|
//! This files contains all the GObject classes for the GTK apprt
|
||||||
//! along with helpers to work with them.
|
//! along with helpers to work with them.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
const glib = @import("glib");
|
const glib = @import("glib");
|
||||||
const gobject = @import("gobject");
|
const gobject = @import("gobject");
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
|
|
@ -53,6 +54,111 @@ pub fn Common(
|
||||||
}
|
}
|
||||||
}).private else {};
|
}).private else {};
|
||||||
|
|
||||||
|
/// Get the class for the object.
|
||||||
|
///
|
||||||
|
/// This _seems_ ugly and unsafe but this is how GObject
|
||||||
|
/// works under the hood. From the [GObject Type System
|
||||||
|
/// Concepts](https://docs.gtk.org/gobject/concepts.html) documentation:
|
||||||
|
///
|
||||||
|
/// Every object must define two structures: its class structure
|
||||||
|
/// and its instance structure. All class structures must contain
|
||||||
|
/// as first member a GTypeClass structure. All instance structures
|
||||||
|
/// must contain as first member a GTypeInstance structure.
|
||||||
|
/// …
|
||||||
|
/// These constraints allow the type system to make sure that
|
||||||
|
/// every object instance (identified by a pointer to the object’s
|
||||||
|
/// instance structure) contains in its first bytes a pointer to the
|
||||||
|
/// object’s class structure.
|
||||||
|
/// …
|
||||||
|
/// The C standard mandates that the first field of a C structure is
|
||||||
|
/// stored starting in the first byte of the buffer used to hold the
|
||||||
|
/// structure’s fields in memory. This means that the first field of
|
||||||
|
/// an instance of an object B is A’s first field which in turn is
|
||||||
|
/// GTypeInstance‘s first field which in turn is g_class, a pointer
|
||||||
|
/// to B’s class structure.
|
||||||
|
///
|
||||||
|
/// This means that to access the class structure for an object you cast it
|
||||||
|
/// to `*gobject.TypeInstance` and then access the `f_g_class` field.
|
||||||
|
///
|
||||||
|
/// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L555-571
|
||||||
|
/// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L2673
|
||||||
|
///
|
||||||
|
pub fn getClass(self: *Self) ?*Self.Class {
|
||||||
|
const type_instance: *gobject.TypeInstance = @ptrCast(self);
|
||||||
|
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
|
/// A helper that creates a property that reads and writes a
|
||||||
/// private field with only shallow copies. This is good for primitives
|
/// private field with only shallow copies. This is good for primitives
|
||||||
/// such as bools, numbers, etc.
|
/// such as bools, numbers, etc.
|
||||||
|
|
|
||||||
|
|
@ -35,36 +35,18 @@ pub const ImguiWidget = extern struct {
|
||||||
|
|
||||||
pub const properties = struct {};
|
pub const properties = struct {};
|
||||||
|
|
||||||
pub const signals = struct {
|
pub const signals = struct {};
|
||||||
/// Emitted when the child widget should render. During the callback,
|
|
||||||
/// the Imgui context is valid.
|
|
||||||
pub const render = struct {
|
|
||||||
pub const name = "render";
|
|
||||||
pub const connect = impl.connect;
|
|
||||||
const impl = gobject.ext.defineSignal(
|
|
||||||
name,
|
|
||||||
Self,
|
|
||||||
&.{},
|
|
||||||
void,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Emitted when first realized to allow the embedded ImGui application
|
pub const virtual_methods = struct {
|
||||||
/// to initialize itself. When this is called, the ImGui context
|
/// This virtual method will be called to allow the Dear ImGui
|
||||||
/// is properly set.
|
/// application to do one-time setup of the context. The correct context
|
||||||
///
|
/// will be current when the virtual method is called.
|
||||||
/// This might be called multiple times, but each time it is
|
pub const setup = C.defineVirtualMethod("setup");
|
||||||
/// called a new Imgui context will be created.
|
|
||||||
pub const setup = struct {
|
/// This virtual method will be called at each frame to allow the Dear
|
||||||
pub const name = "setup";
|
/// ImGui application to draw the application. The correct context will
|
||||||
pub const connect = impl.connect;
|
/// be current when the virtual method is called.
|
||||||
const impl = gobject.ext.defineSignal(
|
pub const render = C.defineVirtualMethod("render");
|
||||||
name,
|
|
||||||
Self,
|
|
||||||
&.{},
|
|
||||||
void,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Private = struct {
|
const Private = struct {
|
||||||
|
|
@ -113,6 +95,25 @@ pub const ImguiWidget = extern struct {
|
||||||
priv.gl_area.queueRender();
|
priv.gl_area.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Public wrappers for virtual methods
|
||||||
|
|
||||||
|
/// 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 fn setup(self: *Self) callconv(.c) void {
|
||||||
|
const class = self.getClass() orelse return;
|
||||||
|
virtual_methods.setup.call(class, self, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 fn render(self: *Self) callconv(.c) void {
|
||||||
|
const class = self.getClass() orelse return;
|
||||||
|
virtual_methods.render.call(class, self, .{});
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Private Methods
|
// Private Methods
|
||||||
|
|
||||||
|
|
@ -232,13 +233,8 @@ pub const ImguiWidget = extern struct {
|
||||||
// initialize the ImgUI OpenGL backend for our context.
|
// initialize the ImgUI OpenGL backend for our context.
|
||||||
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
|
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
|
||||||
|
|
||||||
// Setup our app
|
// Call the virtual method to setup the UI.
|
||||||
signals.setup.impl.emit(
|
self.setup();
|
||||||
self,
|
|
||||||
null,
|
|
||||||
.{},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a request to unrealize the GLArea
|
/// Handle a request to unrealize the GLArea
|
||||||
|
|
@ -279,13 +275,8 @@ pub const ImguiWidget = extern struct {
|
||||||
self.newFrame();
|
self.newFrame();
|
||||||
cimgui.c.igNewFrame();
|
cimgui.c.igNewFrame();
|
||||||
|
|
||||||
// Use the callback to draw the UI.
|
// Call the virtual method to draw the UI.
|
||||||
signals.render.impl.emit(
|
self.render();
|
||||||
self,
|
|
||||||
null,
|
|
||||||
.{},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
cimgui.c.igRender();
|
cimgui.c.igRender();
|
||||||
|
|
@ -422,15 +413,34 @@ pub const ImguiWidget = extern struct {
|
||||||
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Default virtual method handlers
|
||||||
|
|
||||||
|
/// Default setup function. Does nothing but log a warning.
|
||||||
|
fn defaultSetup(_: *Self) callconv(.c) void {
|
||||||
|
log.warn("default Dear ImGui setup called, this is a bug.", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default render function. Does nothing but log a warning.
|
||||||
|
fn defaultRender(_: *Self) callconv(.c) void {
|
||||||
|
log.warn("default Dear ImGui render called, this is a bug.", .{});
|
||||||
|
}
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
pub const refSink = C.refSink;
|
pub const refSink = C.refSink;
|
||||||
pub const unref = C.unref;
|
pub const unref = C.unref;
|
||||||
|
pub const getClass = C.getClass;
|
||||||
const private = C.private;
|
const private = C.private;
|
||||||
|
|
||||||
pub const Class = extern struct {
|
pub const Class = extern struct {
|
||||||
parent_class: Parent.Class,
|
parent_class: Parent.Class,
|
||||||
|
|
||||||
|
/// Function pointers for virtual methods.
|
||||||
|
setup: ?*const fn (*Self) callconv(.c) void,
|
||||||
|
render: ?*const fn (*Self) callconv(.c) void,
|
||||||
|
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
|
|
@ -444,6 +454,10 @@ pub const ImguiWidget = extern struct {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize our virtual methods with default functions.
|
||||||
|
class.setup = defaultSetup;
|
||||||
|
class.render = defaultRender;
|
||||||
|
|
||||||
// Bindings
|
// Bindings
|
||||||
class.bindTemplateChildPrivate("gl_area", .{});
|
class.bindTemplateChildPrivate("gl_area", .{});
|
||||||
class.bindTemplateChildPrivate("im_context", .{});
|
class.bindTemplateChildPrivate("im_context", .{});
|
||||||
|
|
@ -464,8 +478,6 @@ pub const ImguiWidget = extern struct {
|
||||||
class.bindTemplateCallback("im_commit", &imCommit);
|
class.bindTemplateCallback("im_commit", &imCommit);
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
signals.render.impl.register(.{});
|
|
||||||
signals.setup.impl.register(.{});
|
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const log = std.log.scoped(.gtk_ghostty_inspector_widget);
|
||||||
pub const InspectorWidget = extern struct {
|
pub const InspectorWidget = extern struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
parent_instance: Parent,
|
parent_instance: Parent,
|
||||||
pub const Parent = adw.Bin;
|
pub const Parent = ImguiWidget;
|
||||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||||
.name = "GhosttyInspectorWidget",
|
.name = "GhosttyInspectorWidget",
|
||||||
.instanceInit = &init,
|
.instanceInit = &init,
|
||||||
|
|
@ -50,9 +50,6 @@ pub const InspectorWidget = extern struct {
|
||||||
/// We attach a weak notify to the object.
|
/// We attach a weak notify to the object.
|
||||||
surface: ?*Surface = null,
|
surface: ?*Surface = null,
|
||||||
|
|
||||||
/// The embedded Dear ImGui widget.
|
|
||||||
imgui_widget: *ImguiWidget,
|
|
||||||
|
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -78,13 +75,30 @@ pub const InspectorWidget = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called to do initial setup of the UI.
|
||||||
|
fn imguiSetup(
|
||||||
|
_: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
Inspector.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for every frame to draw the UI.
|
||||||
|
fn imguiRender(
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const surface = priv.surface orelse return;
|
||||||
|
const core_surface = surface.core() orelse return;
|
||||||
|
const inspector = core_surface.inspector orelse return;
|
||||||
|
inspector.render();
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Public methods
|
// Public methods
|
||||||
|
|
||||||
/// Queue a render of the Dear ImGui widget.
|
/// Queue a render of the Dear ImGui widget.
|
||||||
pub fn queueRender(self: *Self) void {
|
pub fn queueRender(self: *Self) void {
|
||||||
const priv = self.private();
|
self.as(ImguiWidget).queueRender();
|
||||||
priv.imgui_widget.queueRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
|
|
@ -189,24 +203,6 @@ pub const InspectorWidget = extern struct {
|
||||||
// for completeness sake we should clean this up.
|
// for completeness sake we should clean this up.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn imguiRender(
|
|
||||||
_: *ImguiWidget,
|
|
||||||
self: *Self,
|
|
||||||
) callconv(.c) void {
|
|
||||||
const priv = self.private();
|
|
||||||
const surface = priv.surface orelse return;
|
|
||||||
const core_surface = surface.core() orelse return;
|
|
||||||
const inspector = core_surface.inspector orelse return;
|
|
||||||
inspector.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn imguiSetup(
|
|
||||||
_: *ImguiWidget,
|
|
||||||
_: *Self,
|
|
||||||
) callconv(.c) void {
|
|
||||||
Inspector.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
|
@ -230,13 +226,6 @@ pub const InspectorWidget = extern struct {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bindings
|
|
||||||
class.bindTemplateChildPrivate("imgui_widget", .{});
|
|
||||||
|
|
||||||
// Template callbacks
|
|
||||||
class.bindTemplateCallback("imgui_render", &imguiRender);
|
|
||||||
class.bindTemplateCallback("imgui_setup", &imguiSetup);
|
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.surface.impl,
|
properties.surface.impl,
|
||||||
|
|
@ -245,6 +234,8 @@ pub const InspectorWidget = extern struct {
|
||||||
// Signals
|
// Signals
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
|
ImguiWidget.virtual_methods.setup.implement(class, imguiSetup);
|
||||||
|
ImguiWidget.virtual_methods.render.implement(class, imguiRender);
|
||||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
using Adw 1;
|
|
||||||
|
|
||||||
template $GhosttyInspectorWidget: Adw.Bin {
|
template $GhosttyInspectorWidget: $GhosttyImguiWidget {
|
||||||
styles [
|
styles [
|
||||||
"inspector",
|
"inspector",
|
||||||
]
|
]
|
||||||
|
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
|
|
||||||
Adw.Bin {
|
|
||||||
$GhosttyImguiWidget imgui_widget {
|
|
||||||
render => $imgui_render();
|
|
||||||
setup => $imgui_setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue