apprt/gtk-ng: split tree new split actions

pull/8207/head
Mitchell Hashimoto 2025-08-07 10:32:34 -07:00
parent 4742177daa
commit ae5dc3a4fb
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
3 changed files with 175 additions and 29 deletions

View File

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const build_config = @import("../../../build_config.zig"); const build_config = @import("../../../build_config.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const adw = @import("adw"); const adw = @import("adw");
const gio = @import("gio"); const gio = @import("gio");
const glib = @import("glib"); const glib = @import("glib");
@ -36,6 +37,30 @@ pub const SplitTree = extern struct {
}); });
pub const properties = struct { pub const properties = struct {
/// The active surface is the surface that should be receiving all
/// surface-targeted actions. This is usually the focused surface,
/// but may also not be focused if the user has selected a non-surface
/// widget.
pub const @"active-surface" = struct {
pub const name = "active-surface";
const impl = gobject.ext.defineProperty(
name,
Self,
?*Surface,
.{
.nick = "Active Surface",
.blurb = "The currently active surface.",
.accessor = gobject.ext.typedAccessor(
Self,
?*Surface,
.{
.getter = getActiveSurface,
},
),
},
);
};
pub const @"has-surfaces" = struct { pub const @"has-surfaces" = struct {
pub const name = "has-surfaces"; pub const name = "has-surfaces";
const impl = gobject.ext.defineProperty( const impl = gobject.ext.defineProperty(
@ -98,11 +123,132 @@ pub const SplitTree = extern struct {
fn init(self: *Self, _: *Class) callconv(.c) void { fn init(self: *Self, _: *Class) callconv(.c) void {
gtk.Widget.initTemplate(self.as(gtk.Widget)); gtk.Widget.initTemplate(self.as(gtk.Widget));
// Initialize our actions
self.initActions();
}
fn initActions(self: *Self) void {
// The set of actions. Each action has (in order):
// [0] The action name
// [1] The callback function
// [2] The glib.VariantType of the parameter
//
// For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
const actions = .{
// All of these will eventually take a target surface parameter.
// For now all our targets originate from the focused surface.
.{ "new-left", actionNew, null },
.{ "new-right", actionNew, null },
.{ "new-up", actionNew, null },
.{ "new-down", actionNew, null },
};
// We need to collect our actions into a group since we're just
// a plain widget that doesn't implement ActionGroup directly.
const group = gio.SimpleActionGroup.new();
errdefer group.unref();
const map = group.as(gio.ActionMap);
inline for (actions) |entry| {
const action = gio.SimpleAction.new(
entry[0],
entry[2],
);
defer action.unref();
_ = gio.SimpleAction.signals.activate.connect(
action,
*Self,
entry[1],
self,
.{},
);
map.addAction(action.as(gio.Action));
}
self.as(gtk.Widget).insertActionGroup(
"split-tree",
group.as(gio.ActionGroup),
);
}
/// Create a new split in the given direction from the currently
/// active surface.
///
/// If the tree is empty this will create a new tree with a new surface
/// and ignore the direction.
///
/// The parent will be used as the parent of the surface regardless of
/// if that parent is in this split tree or not. This allows inheriting
/// surface properties from anywhere.
pub fn newSplit(
self: *Self,
direction: Surface.Tree.Split.Direction,
parent_: ?*Surface,
) Allocator.Error!void {
const alloc = Application.default().allocator();
// Create our new surface.
const surface: *Surface = .new();
defer surface.unref();
_ = surface.refSink();
// Inherit properly if we were asked to.
if (parent_) |p| {
if (p.core()) |core| {
surface.setParent(core);
}
}
// Create our tree
var single_tree = try Surface.Tree.init(alloc, surface);
defer single_tree.deinit();
// If we have no tree yet, then this becomes our tree and we're done.
const old_tree = self.getTree() orelse {
self.setTree(&single_tree);
return;
};
// The handle we create the split relative to. Today this is the active
// surface but this might be the handle of the given parent if we want.
const handle = self.getActiveSurfaceHandle() orelse 0;
// Create our split!
var new_tree = try old_tree.split(
alloc,
handle,
direction,
&single_tree,
);
defer new_tree.deinit();
self.setTree(&new_tree);
// Focus our new surface
surface.grabFocus();
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
// Properties // Properties
/// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value.
pub fn getActiveSurface(self: *Self) ?*Surface {
const tree = self.getTree() orelse return null;
const handle = self.getActiveSurfaceHandle() orelse return null;
return tree.nodes[handle].leaf;
}
fn getActiveSurfaceHandle(self: *Self) ?Surface.Tree.Node.Handle {
const tree = self.getTree() orelse return null;
var it = tree.iterator();
while (it.next()) |entry| {
if (entry.view.getFocused()) return entry.handle;
}
return null;
}
pub fn getHasSurfaces(self: *Self) bool { pub fn getHasSurfaces(self: *Self) bool {
const tree: *const Surface.Tree = self.private().tree orelse &.empty; const tree: *const Surface.Tree = self.private().tree orelse &.empty;
return !tree.isEmpty(); return !tree.isEmpty();
@ -185,6 +331,20 @@ pub const SplitTree = extern struct {
//--------------------------------------------------------------- //---------------------------------------------------------------
// Signal handlers // Signal handlers
pub fn actionNew(
_: *gio.SimpleAction,
parameter_: ?*glib.Variant,
self: *Self,
) callconv(.c) void {
_ = parameter_;
self.newSplit(
.right,
self.getActiveSurface(),
) catch |err| {
log.warn("new split failed error={}", .{err});
};
}
fn propTree( fn propTree(
self: *Self, self: *Self,
_: *gobject.ParamSpec, _: *gobject.ParamSpec,

View File

@ -36,7 +36,7 @@ pub const Tab = extern struct {
}); });
pub const properties = struct { pub const properties = struct {
/// The active surface is the focus that should be receiving all /// The active surface is the surface that should be receiving all
/// surface-targeted actions. This is usually the focused surface, /// surface-targeted actions. This is usually the focused surface,
/// but may also not be focused if the user has selected a non-surface /// but may also not be focused if the user has selected a non-surface
/// widget. /// widget.
@ -164,23 +164,15 @@ pub const Tab = extern struct {
.{}, .{},
); );
// A tab always starts with a single surface. // Create our initial surface in the split tree.
const surface: *Surface = .new(); priv.split_tree.newSplit(.right, null) catch |err| switch (err) {
defer surface.unref(); error.OutOfMemory => {
_ = surface.refSink(); // TODO: We should make our "no surfaces" state more aesthetically
const alloc = Application.default().allocator(); // pleasing and show something like an "Oops, something went wrong"
if (Surface.Tree.init(alloc, surface)) |tree| { // message. For now, this is incredibly unlikely.
priv.split_tree.setTree(&tree); @panic("oom");
},
// Hacky because we need a non-const result. };
var mut = tree;
mut.deinit();
} else |_| {
// TODO: We should make our "no surfaces" state more aesthetically
// pleasing and show something like an "Oops, something went wrong"
// message. For now, this is incredibly unlikely.
@panic("oom");
}
} }
fn connectSurfaceHandlers( fn connectSurfaceHandlers(
@ -232,13 +224,7 @@ pub const Tab = extern struct {
/// Get the currently active surface. See the "active-surface" property. /// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value. /// This does not ref the value.
pub fn getActiveSurface(self: *Self) ?*Surface { pub fn getActiveSurface(self: *Self) ?*Surface {
const tree = self.getSurfaceTree() orelse return null; return self.getSplitTree().getActiveSurface();
var it = tree.iterator();
while (it.next()) |entry| {
if (entry.view.getFocused()) return entry.view;
}
return null;
} }
/// Get the surface tree of this tab. /// Get the surface tree of this tab.

View File

@ -172,22 +172,22 @@ menu context_menu_model {
item { item {
label: _("Split Up"); label: _("Split Up");
action: "win.split-up"; action: "split-tree.new-up";
} }
item { item {
label: _("Split Down"); label: _("Split Down");
action: "win.split-down"; action: "split-tree.new-down";
} }
item { item {
label: _("Split Left"); label: _("Split Left");
action: "win.split-left"; action: "split-tree.new-left";
} }
item { item {
label: _("Split Right"); label: _("Split Right");
action: "win.split-right"; action: "split-tree.new-right";
} }
} }