Add custom layout creation.
This commit is contained in:
+138
-2
@@ -12,6 +12,7 @@ const std = @import("std");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const Layout = @import("Layout.zig");
|
||||
const Layouts = @import("Layouts.zig");
|
||||
const Pane = @import("Pane.zig");
|
||||
const Terminal = @import("Terminal.zig");
|
||||
|
||||
@@ -152,8 +153,8 @@ pub fn focus(self: *View) void {
|
||||
|
||||
/// Add a pane, splitting the focused one so the new pane appears beside
|
||||
/// whatever you were working in.
|
||||
pub fn addPane(self: *View, kind: Kind) !void {
|
||||
const pane = try Pane.create(self.alloc, self, kind);
|
||||
pub fn addPane(self: *View, spec: Pane.Spec) !void {
|
||||
const pane = try Pane.create(self.alloc, self, spec);
|
||||
errdefer pane.destroy();
|
||||
|
||||
const node = try self.layout.newLeaf(pane);
|
||||
@@ -228,6 +229,141 @@ pub fn paneTitleChanged(self: *View, pane: *Pane) void {
|
||||
if (self.focused == pane or self.panes.items.len == 1) self.on_title(self.ctx);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Layouts
|
||||
|
||||
/// Fill an empty view from a saved layout, substituting `bindings` into every
|
||||
/// directory, script and URL as the panes are created.
|
||||
///
|
||||
/// Only valid on a view that has no panes yet — a layout describes a whole
|
||||
/// tab, not an addition to one.
|
||||
pub fn applyLayout(
|
||||
self: *View,
|
||||
spec: *const Layouts.Node,
|
||||
bindings: []const Layouts.Binding,
|
||||
) !void {
|
||||
std.debug.assert(self.panes.items.len == 0);
|
||||
|
||||
const root = try self.buildNode(spec, bindings);
|
||||
self.layout.root = root;
|
||||
root.parent = null;
|
||||
self.layout.materialize(self.box);
|
||||
|
||||
// The first pane in tree order is the top-left one, which is where you
|
||||
// would start reading the tab and so where focus belongs.
|
||||
if (self.panes.items.len > 0) {
|
||||
self.setFocused(self.panes.items[0]);
|
||||
self.panes.items[0].grabFocus();
|
||||
}
|
||||
self.on_title(self.ctx);
|
||||
}
|
||||
|
||||
/// Build one subtree. Panes are appended to `self.panes` as they are created,
|
||||
/// so a failure part-way leaves them owned by the view and torn down with it
|
||||
/// rather than leaked.
|
||||
fn buildNode(
|
||||
self: *View,
|
||||
spec: *const Layouts.Node,
|
||||
bindings: []const Layouts.Binding,
|
||||
) !*Layout.Node {
|
||||
switch (spec.*) {
|
||||
.pane => |p| {
|
||||
const pane_spec = try self.paneSpec(p, bindings);
|
||||
defer freePaneSpec(self.alloc, pane_spec);
|
||||
|
||||
const pane = try Pane.create(self.alloc, self, pane_spec);
|
||||
errdefer pane.destroy();
|
||||
|
||||
const node = try self.layout.newLeaf(pane);
|
||||
errdefer self.alloc.destroy(node);
|
||||
|
||||
try self.panes.append(self.alloc, pane);
|
||||
return node;
|
||||
},
|
||||
.split => |s| {
|
||||
const first = try self.buildNode(s.first, bindings);
|
||||
const second = try self.buildNode(s.second, bindings);
|
||||
return self.layout.newSplit(
|
||||
switch (s.orientation) {
|
||||
.horizontal => .horizontal,
|
||||
.vertical => .vertical,
|
||||
},
|
||||
first,
|
||||
second,
|
||||
s.ratio,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn a layout's pane description into the spec `Pane.create` wants, with
|
||||
/// parameters substituted. The strings are owned by the caller.
|
||||
fn paneSpec(
|
||||
self: *View,
|
||||
p: Layouts.Pane,
|
||||
bindings: []const Layouts.Binding,
|
||||
) !Pane.Spec {
|
||||
return switch (p.kind) {
|
||||
.terminal => blk: {
|
||||
const cwd = try Layouts.expandPath(self.alloc, p.cwd, bindings);
|
||||
errdefer self.alloc.free(cwd);
|
||||
const command = try Layouts.expand(self.alloc, p.command, bindings);
|
||||
break :blk .{ .terminal = .{ .cwd = cwd, .command = command } };
|
||||
},
|
||||
.web => .{ .web = .{
|
||||
.url = try Layouts.expand(self.alloc, p.url, bindings),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
fn freePaneSpec(alloc: std.mem.Allocator, spec: Pane.Spec) void {
|
||||
switch (spec) {
|
||||
.terminal => |o| {
|
||||
alloc.free(o.cwd);
|
||||
alloc.free(o.command);
|
||||
},
|
||||
.web => |o| alloc.free(o.url),
|
||||
}
|
||||
}
|
||||
|
||||
/// Capture this view's arrangement as a layout tree, for "save tab as layout".
|
||||
///
|
||||
/// The shape, split orientations and divider ratios come across exactly as
|
||||
/// they are on screen. What each pane should *run* can't be known from a live
|
||||
/// pane, so terminals come back with their current directory and an empty
|
||||
/// command for the user to fill in; web panes bring their current URL.
|
||||
pub fn capture(self: *View, builder: Layouts.Builder) !?*Layouts.Node {
|
||||
const root = self.layout.root orelse return null;
|
||||
return try self.captureNode(root, builder);
|
||||
}
|
||||
|
||||
fn captureNode(self: *View, node: *Layout.Node, builder: Layouts.Builder) !*Layouts.Node {
|
||||
return switch (node.kind) {
|
||||
.leaf => |pane| switch (pane.content) {
|
||||
.terminal => |t| blk: {
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
break :blk try builder.pane(.{
|
||||
.kind = .terminal,
|
||||
.cwd = t.session.pty.cwd(&buf) orelse "",
|
||||
});
|
||||
},
|
||||
.web => |b| try builder.pane(.{
|
||||
.kind = .web,
|
||||
.url = b.currentUrl(),
|
||||
}),
|
||||
},
|
||||
.split => |s| try builder.split(
|
||||
switch (s.paned.as(gtk.Orientable).getOrientation()) {
|
||||
.vertical => .vertical,
|
||||
else => .horizontal,
|
||||
},
|
||||
s.ratio,
|
||||
try self.captureNode(s.a, builder),
|
||||
try self.captureNode(s.b, builder),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Rearranging
|
||||
|
||||
|
||||
Reference in New Issue
Block a user