apprt/gtk-ng: initial GhosttySplitTree widget
parent
ad1cfe8347
commit
fa08434b28
|
|
@ -40,6 +40,7 @@ pub const blueprints: []const Blueprint = &.{
|
|||
.{ .major = 1, .minor = 2, .name = "debug-warning" },
|
||||
.{ .major = 1, .minor = 3, .name = "debug-warning" },
|
||||
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
||||
.{ .major = 1, .minor = 5, .name = "split-tree" },
|
||||
.{ .major = 1, .minor = 2, .name = "surface" },
|
||||
.{ .major = 1, .minor = 3, .name = "surface-child-exited" },
|
||||
.{ .major = 1, .minor = 5, .name = "tab" },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const input = @import("../../../input.zig");
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const gtk_version = @import("../gtk_version.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_split_tree);
|
||||
|
||||
pub const SplitTree = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttySplitTree",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const @"is-empty" = struct {
|
||||
pub const name = "is-empty";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.nick = "Tree Is Empty",
|
||||
.blurb = "True when the tree has no surfaces.",
|
||||
.default = false,
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.getter = getIsEmpty,
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
/// The tree datastructure containing all of our surface views.
|
||||
tree: Surface.Tree,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
|
||||
// Start with an empty split tree.
|
||||
const priv = self.private();
|
||||
priv.tree = .empty;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
pub fn getIsEmpty(self: *Self) bool {
|
||||
const priv = self.private();
|
||||
return priv.tree.isEmpty();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual methods
|
||||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn finalize(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
priv.tree.deinit();
|
||||
priv.tree = .empty;
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Signal handlers
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Class
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gobject.ext.ensureType(Surface);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 5,
|
||||
.name = "split-tree",
|
||||
}),
|
||||
);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.@"is-empty".impl,
|
||||
});
|
||||
|
||||
// Bindings
|
||||
|
||||
// Template Callbacks
|
||||
|
||||
// Signals
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
|
|
@ -9,6 +9,7 @@ const gobject = @import("gobject");
|
|||
const gtk = @import("gtk");
|
||||
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const datastruct = @import("../../../datastruct/main.zig");
|
||||
const font = @import("../../../font/main.zig");
|
||||
const input = @import("../../../input.zig");
|
||||
const internal_os = @import("../../../os/main.zig");
|
||||
|
|
@ -42,6 +43,9 @@ pub const Surface = extern struct {
|
|||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
/// A SplitTree implementation that stores surfaces.
|
||||
pub const Tree = datastruct.SplitTree(Self);
|
||||
|
||||
pub const properties = struct {
|
||||
pub const config = struct {
|
||||
pub const name = "config";
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const Common = @import("../class.zig").Common;
|
|||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_window);
|
||||
|
|
@ -251,6 +252,7 @@ pub const Tab = extern struct {
|
|||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gobject.ext.ensureType(SplitTree);
|
||||
gobject.ext.ensureType(Surface);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttySplitTree: Adw.Bin {
|
||||
// This could be a lot more visually pleasing but in practice this doesn't
|
||||
// ever happen at the time of writing this comment. A surface-less split
|
||||
// tree always closes its parent.
|
||||
Label {
|
||||
visible: bind template.is-empty;
|
||||
// Purposely not localized currently because this shouldn't really
|
||||
// ever appear. When we have a situation it does appear, we may want
|
||||
// to change the styling and text so I don't want to burden localizers
|
||||
// to handle this yet.
|
||||
label: "No surfaces.";
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ template $GhosttyTab: Box {
|
|||
"tab",
|
||||
]
|
||||
|
||||
orientation: vertical;
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
// A tab currently just contains a surface directly. When we introduce
|
||||
|
|
@ -12,4 +13,6 @@ template $GhosttyTab: Box {
|
|||
$GhosttySurface surface {
|
||||
close-request => $surface_close_request();
|
||||
}
|
||||
|
||||
$GhosttySplitTree {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ const Allocator = std.mem.Allocator;
|
|||
/// for the debug view. If this isn't specified then the node handle
|
||||
/// will be used.
|
||||
///
|
||||
/// Note: for both the ref and unref functions, the allocator is optional.
|
||||
/// If the functions take less arguments, then the allocator will not be
|
||||
/// passed.
|
||||
pub fn SplitTree(comptime V: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
|
@ -88,8 +91,8 @@ pub fn SplitTree(comptime V: type) type {
|
|||
const alloc = arena.allocator();
|
||||
|
||||
const nodes = try alloc.alloc(Node, 1);
|
||||
nodes[0] = .{ .leaf = try view.ref(gpa) };
|
||||
errdefer view.unref(gpa);
|
||||
nodes[0] = .{ .leaf = try viewRef(view, gpa) };
|
||||
errdefer viewUnref(view, gpa);
|
||||
|
||||
return .{
|
||||
.arena = arena,
|
||||
|
|
@ -104,7 +107,7 @@ pub fn SplitTree(comptime V: type) type {
|
|||
// Unref all our views
|
||||
const gpa: Allocator = self.arena.child_allocator;
|
||||
for (self.nodes) |node| switch (node) {
|
||||
.leaf => |view| view.unref(gpa),
|
||||
.leaf => |view| viewUnref(view, gpa),
|
||||
.split => {},
|
||||
};
|
||||
self.arena.deinit();
|
||||
|
|
@ -113,6 +116,12 @@ pub fn SplitTree(comptime V: type) type {
|
|||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Returns true if this is an empty tree.
|
||||
pub fn isEmpty(self: *const Self) bool {
|
||||
// An empty tree has no nodes.
|
||||
return self.nodes.len == 0;
|
||||
}
|
||||
|
||||
/// An iterator over all the views in the tree.
|
||||
pub fn iterator(
|
||||
self: *const Self,
|
||||
|
|
@ -367,13 +376,13 @@ pub fn SplitTree(comptime V: type) type {
|
|||
errdefer for (0..reffed) |i| {
|
||||
switch (nodes[i]) {
|
||||
.split => {},
|
||||
.leaf => |view| view.unref(gpa),
|
||||
.leaf => |view| viewUnref(view, gpa),
|
||||
}
|
||||
};
|
||||
for (0..nodes.len) |i| {
|
||||
switch (nodes[i]) {
|
||||
.split => {},
|
||||
.leaf => |view| nodes[i] = .{ .leaf = try view.ref(gpa) },
|
||||
.leaf => |view| nodes[i] = .{ .leaf = try viewRef(view, gpa) },
|
||||
}
|
||||
reffed = i;
|
||||
}
|
||||
|
|
@ -658,6 +667,24 @@ pub fn SplitTree(comptime V: type) type {
|
|||
try writer.writeAll(row);
|
||||
}
|
||||
}
|
||||
|
||||
fn viewRef(view: *View, gpa: Allocator) Allocator.Error!*View {
|
||||
const func = @typeInfo(@TypeOf(View.ref)).@"fn";
|
||||
return switch (func.params.len) {
|
||||
1 => view.ref(),
|
||||
2 => try view.ref(gpa),
|
||||
else => @compileError("invalid view ref function"),
|
||||
};
|
||||
}
|
||||
|
||||
fn viewUnref(view: *View, gpa: Allocator) void {
|
||||
const func = @typeInfo(@TypeOf(View.unref)).@"fn";
|
||||
switch (func.params.len) {
|
||||
1 => view.unref(),
|
||||
2 => view.unref(gpa),
|
||||
else => @compileError("invalid view unref function"),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue