apprt/gtk-ng: render a single artificial split

pull/8165/head
Mitchell Hashimoto 2025-08-06 10:53:18 -07:00
parent 70b050ebb4
commit a7865d79ea
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
5 changed files with 126 additions and 4 deletions

View File

@ -66,16 +66,39 @@ pub const SplitTree = extern struct {
.{ .{
.nick = "Tree Model", .nick = "Tree Model",
.blurb = "Underlying data model for the tree.", .blurb = "Underlying data model for the tree.",
.accessor = C.privateBoxedFieldAccessor("tree"), .accessor = .{
.getter = getTreeValue,
.setter = setTreeValue,
},
}, },
); );
}; };
}; };
pub const signals = struct {
/// Emitted whenever the tree property is about to change.
///
/// The new value is given as the signal parameter. The old value
/// can still be retrieved from the tree property.
pub const @"tree-will-change" = struct {
pub const name = "tree-change";
pub const connect = impl.connect;
const impl = gobject.ext.defineSignal(
name,
Self,
&.{?*const Surface.Tree},
void,
);
};
};
const Private = struct { const Private = struct {
/// The tree datastructure containing all of our surface views. /// The tree datastructure containing all of our surface views.
tree: ?*Surface.Tree, tree: ?*Surface.Tree,
// Template bindings
tree_bin: *adw.Bin,
pub var offset: c_int = 0; pub var offset: c_int = 0;
}; };
@ -91,6 +114,52 @@ pub const SplitTree = extern struct {
return tree.isEmpty(); return tree.isEmpty();
} }
/// Get the tree data model that we're showing in this widget. This
/// does not clone the tree.
pub fn getTree(self: *Self) ?*Surface.Tree {
return self.private().tree;
}
/// Set the tree data model that we're showing in this widget. This
/// will clone the given tree.
pub fn setTree(self: *Self, tree: ?*const Surface.Tree) void {
const priv = self.private();
// Emit the signal so that handlers can witness both the before and
// after values of the tree.
signals.@"tree-will-change".impl.emit(
self,
null,
.{tree},
null,
);
if (priv.tree) |old_tree| {
ext.boxedFree(Surface.Tree, old_tree);
priv.tree = null;
}
if (tree) |new_tree| {
priv.tree = ext.boxedCopy(Surface.Tree, new_tree);
}
self.as(gobject.Object).notifyByPspec(properties.tree.impl.param_spec);
}
fn getTreeValue(self: *Self, value: *gobject.Value) void {
gobject.ext.Value.set(
value,
self.private().tree,
);
}
fn setTreeValue(self: *Self, value: *const gobject.Value) void {
self.setTree(gobject.ext.Value.get(
value,
?*Surface.Tree,
));
}
//--------------------------------------------------------------- //---------------------------------------------------------------
// Virtual methods // Virtual methods
@ -127,9 +196,48 @@ pub const SplitTree = extern struct {
_: *gobject.ParamSpec, _: *gobject.ParamSpec,
_: ?*anyopaque, _: ?*anyopaque,
) callconv(.c) void { ) callconv(.c) void {
const priv = self.private();
const tree: *const Surface.Tree = self.private().tree orelse &.empty;
// Reset our widget tree.
priv.tree_bin.setChild(null);
if (!tree.isEmpty()) {
priv.tree_bin.setChild(buildTree(tree, 0));
}
// Dependent properties
self.as(gobject.Object).notifyByPspec(properties.@"is-empty".impl.param_spec); self.as(gobject.Object).notifyByPspec(properties.@"is-empty".impl.param_spec);
} }
/// Builds the widget tree associated with a surface split tree.
///
/// The final returned widget is expected to be a floating reference,
/// ready to be attached to a parent widget.
fn buildTree(
tree: *const Surface.Tree,
current: Surface.Tree.Node.Handle,
) *gtk.Widget {
switch (tree.nodes[current]) {
.leaf => |v| {
// We have to setup our signal handlers.
return v.as(gtk.Widget);
},
.split => |s| return gobject.ext.newInstance(
gtk.Paned,
.{
.orientation = @as(gtk.Orientation, switch (s.layout) {
.horizontal => .horizontal,
.vertical => .vertical,
}),
.@"start-child" = buildTree(tree, s.left),
.@"end-child" = buildTree(tree, s.right),
// TODO: position/ratio
},
).as(gtk.Widget),
}
}
//--------------------------------------------------------------- //---------------------------------------------------------------
// Class // Class
@ -162,11 +270,13 @@ pub const SplitTree = extern struct {
}); });
// Bindings // Bindings
class.bindTemplateChildPrivate("tree_bin", .{});
// Template Callbacks // Template Callbacks
class.bindTemplateCallback("notify_tree", &propTree); class.bindTemplateCallback("notify_tree", &propTree);
// Signals // Signals
signals.@"tree-will-change".impl.register(.{});
// Virtual methods // Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose); gobject.Object.virtual_methods.dispose.implement(class, &dispose);

View File

@ -2302,6 +2302,7 @@ pub const Surface = extern struct {
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 unref = C.unref; pub const unref = C.unref;
const private = C.private; const private = C.private;

View File

@ -118,6 +118,7 @@ pub const Tab = extern struct {
surface_bindings: *gobject.BindingGroup, surface_bindings: *gobject.BindingGroup,
// Template bindings // Template bindings
split_tree: *SplitTree,
surface: *Surface, surface: *Surface,
pub var offset: c_int = 0; pub var offset: c_int = 0;
@ -161,6 +162,16 @@ pub const Tab = extern struct {
// We need to do this so that the title initializes properly, // We need to do this so that the title initializes properly,
// I think because its a dynamic getter. // I think because its a dynamic getter.
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec); self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
// Setup our initial split tree.
// TODO: Probably make this a property
const surface: *Surface = .new();
defer surface.unref();
_ = surface.refSink();
const alloc = Application.default().allocator();
var tree = Surface.Tree.init(alloc, surface) catch unreachable;
defer tree.deinit();
priv.split_tree.setTree(&tree);
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
@ -271,6 +282,7 @@ pub const Tab = extern struct {
}); });
// Bindings // Bindings
class.bindTemplateChildPrivate("split_tree", .{});
class.bindTemplateChildPrivate("surface", .{}); class.bindTemplateChildPrivate("surface", .{});
// Template Callbacks // Template Callbacks

View File

@ -7,9 +7,8 @@ template $GhosttySplitTree: Adw.Bin {
Box { Box {
orientation: vertical; orientation: vertical;
Box surface_box { Adw.Bin tree_bin {
visible: bind template.is-empty inverted; visible: bind template.is-empty inverted;
orientation: vertical;
hexpand: true; hexpand: true;
vexpand: true; vexpand: true;
} }

View File

@ -14,5 +14,5 @@ template $GhosttyTab: Box {
close-request => $surface_close_request(); close-request => $surface_close_request();
} }
$GhosttySplitTree {} $GhosttySplitTree split_tree {}
} }