apprt/gtk-ng: split zoom (#8217)
This makes `toggle_split_zoom` work via a new widget action `split-tree.zoom`. The zoom state is tracked on the core `SplitTree` data structure. Zoom state is propagated via a `is-zoomed` property on the split tree in GTK. I deferred the title changes since I can do that all at once with subtitle and other things.pull/8218/head
commit
bede3d8011
|
|
@ -615,12 +615,11 @@ pub const Application = extern struct {
|
||||||
.toggle_tab_overview => return Action.toggleTabOverview(target),
|
.toggle_tab_overview => return Action.toggleTabOverview(target),
|
||||||
.toggle_window_decorations => return Action.toggleWindowDecorations(target),
|
.toggle_window_decorations => return Action.toggleWindowDecorations(target),
|
||||||
.toggle_command_palette => return Action.toggleCommandPalette(target),
|
.toggle_command_palette => return Action.toggleCommandPalette(target),
|
||||||
|
.toggle_split_zoom => return Action.toggleSplitZoom(target),
|
||||||
|
|
||||||
// Unimplemented but todo on gtk-ng branch
|
// Unimplemented but todo on gtk-ng branch
|
||||||
.prompt_title,
|
.prompt_title,
|
||||||
.inspector,
|
.inspector,
|
||||||
// TODO: splits
|
|
||||||
.toggle_split_zoom,
|
|
||||||
=> {
|
=> {
|
||||||
log.warn("unimplemented action={}", .{action});
|
log.warn("unimplemented action={}", .{action});
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -2121,6 +2120,21 @@ const Action = struct {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggleSplitZoom(target: apprt.Target) bool {
|
||||||
|
switch (target) {
|
||||||
|
.app => {
|
||||||
|
log.warn("toggle_split_zoom to app is unexpected", .{});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
.surface => |core| {
|
||||||
|
// TODO: pass surface ID when we have that
|
||||||
|
const surface = core.rt_surface.surface;
|
||||||
|
return surface.as(gtk.Widget).activateAction("split-tree.zoom", null) != 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn getQuickTerminalWindow() ?*Window {
|
fn getQuickTerminalWindow() ?*Window {
|
||||||
// Find a quick terminal window.
|
// Find a quick terminal window.
|
||||||
const list = gtk.Window.listToplevels();
|
const list = gtk.Window.listToplevels();
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,25 @@ pub const SplitTree = extern struct {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const @"is-zoomed" = struct {
|
||||||
|
pub const name = "is-zoomed";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
bool,
|
||||||
|
.{
|
||||||
|
.default = false,
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
bool,
|
||||||
|
.{
|
||||||
|
.getter = getIsZoomed,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
pub const tree = struct {
|
pub const tree = struct {
|
||||||
pub const name = "tree";
|
pub const name = "tree";
|
||||||
const impl = gobject.ext.defineProperty(
|
const impl = gobject.ext.defineProperty(
|
||||||
|
|
@ -165,6 +184,7 @@ pub const SplitTree = extern struct {
|
||||||
.{ "new-down", actionNewDown, null },
|
.{ "new-down", actionNewDown, null },
|
||||||
|
|
||||||
.{ "equalize", actionEqualize, null },
|
.{ "equalize", actionEqualize, null },
|
||||||
|
.{ "zoom", actionZoom, null },
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to collect our actions into a group since we're just
|
// We need to collect our actions into a group since we're just
|
||||||
|
|
@ -241,7 +261,7 @@ pub const SplitTree = extern struct {
|
||||||
|
|
||||||
// The handle we create the split relative to. Today this is the active
|
// 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.
|
// surface but this might be the handle of the given parent if we want.
|
||||||
const handle = self.getActiveSurfaceHandle() orelse 0;
|
const handle = self.getActiveSurfaceHandle() orelse .root;
|
||||||
|
|
||||||
// Create our split!
|
// Create our split!
|
||||||
var new_tree = try old_tree.split(
|
var new_tree = try old_tree.split(
|
||||||
|
|
@ -328,7 +348,7 @@ pub const SplitTree = extern struct {
|
||||||
if (active == target) return false;
|
if (active == target) return false;
|
||||||
|
|
||||||
// Get the surface at the target location and grab focus.
|
// Get the surface at the target location and grab focus.
|
||||||
const surface = tree.nodes[target].leaf;
|
const surface = tree.nodes[target.idx()].leaf;
|
||||||
surface.grabFocus();
|
surface.grabFocus();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -388,7 +408,7 @@ pub const SplitTree = extern struct {
|
||||||
pub fn getActiveSurface(self: *Self) ?*Surface {
|
pub fn getActiveSurface(self: *Self) ?*Surface {
|
||||||
const tree = self.getTree() orelse return null;
|
const tree = self.getTree() orelse return null;
|
||||||
const handle = self.getActiveSurfaceHandle() orelse return null;
|
const handle = self.getActiveSurfaceHandle() orelse return null;
|
||||||
return tree.nodes[handle].leaf;
|
return tree.nodes[handle.idx()].leaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getActiveSurfaceHandle(self: *Self) ?Surface.Tree.Node.Handle {
|
fn getActiveSurfaceHandle(self: *Self) ?Surface.Tree.Node.Handle {
|
||||||
|
|
@ -429,6 +449,11 @@ pub const SplitTree = extern struct {
|
||||||
return !tree.isEmpty();
|
return !tree.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getIsZoomed(self: *Self) bool {
|
||||||
|
const tree: *const Surface.Tree = self.private().tree orelse &.empty;
|
||||||
|
return tree.zoomed != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the tree data model that we're showing in this widget. This
|
/// Get the tree data model that we're showing in this widget. This
|
||||||
/// does not clone the tree.
|
/// does not clone the tree.
|
||||||
pub fn getTree(self: *Self) ?*Surface.Tree {
|
pub fn getTree(self: *Self) ?*Surface.Tree {
|
||||||
|
|
@ -600,6 +625,23 @@ pub const SplitTree = extern struct {
|
||||||
self.setTree(&new_tree);
|
self.setTree(&new_tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn actionZoom(
|
||||||
|
_: *gio.SimpleAction,
|
||||||
|
_: ?*glib.Variant,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const tree = self.getTree() orelse return;
|
||||||
|
if (tree.zoomed != null) {
|
||||||
|
tree.zoomed = null;
|
||||||
|
} else {
|
||||||
|
const active = self.getActiveSurfaceHandle() orelse return;
|
||||||
|
if (tree.zoomed == active) return;
|
||||||
|
tree.zoom(active);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.tree.impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
fn surfaceCloseRequest(
|
fn surfaceCloseRequest(
|
||||||
surface: *Surface,
|
surface: *Surface,
|
||||||
scope: *const Surface.CloseScope,
|
scope: *const Surface.CloseScope,
|
||||||
|
|
@ -679,7 +721,7 @@ pub const SplitTree = extern struct {
|
||||||
// Note: we don't need to ref this or anything because its
|
// Note: we don't need to ref this or anything because its
|
||||||
// guaranteed to remain in the new tree since its not part
|
// guaranteed to remain in the new tree since its not part
|
||||||
// of the handle we're removing.
|
// of the handle we're removing.
|
||||||
break :next_focus old_tree.nodes[next_handle].leaf;
|
break :next_focus old_tree.nodes[next_handle.idx()].leaf;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove it from the tree.
|
// Remove it from the tree.
|
||||||
|
|
@ -781,6 +823,7 @@ pub const SplitTree = extern struct {
|
||||||
|
|
||||||
// Dependent properties
|
// Dependent properties
|
||||||
self.as(gobject.Object).notifyByPspec(properties.@"has-surfaces".impl.param_spec);
|
self.as(gobject.Object).notifyByPspec(properties.@"has-surfaces".impl.param_spec);
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"is-zoomed".impl.param_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onRebuild(ud: ?*anyopaque) callconv(.c) c_int {
|
fn onRebuild(ud: ?*anyopaque) callconv(.c) c_int {
|
||||||
|
|
@ -797,7 +840,10 @@ pub const SplitTree = extern struct {
|
||||||
// Rebuild our tree
|
// Rebuild our tree
|
||||||
const tree: *const Surface.Tree = self.private().tree orelse &.empty;
|
const tree: *const Surface.Tree = self.private().tree orelse &.empty;
|
||||||
if (!tree.isEmpty()) {
|
if (!tree.isEmpty()) {
|
||||||
priv.tree_bin.setChild(self.buildTree(tree, 0));
|
priv.tree_bin.setChild(self.buildTree(
|
||||||
|
tree,
|
||||||
|
tree.zoomed orelse .root,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a last focused surface, we need to refocus it, because
|
// If we have a last focused surface, we need to refocus it, because
|
||||||
|
|
@ -823,7 +869,7 @@ pub const SplitTree = extern struct {
|
||||||
tree: *const Surface.Tree,
|
tree: *const Surface.Tree,
|
||||||
current: Surface.Tree.Node.Handle,
|
current: Surface.Tree.Node.Handle,
|
||||||
) *gtk.Widget {
|
) *gtk.Widget {
|
||||||
return switch (tree.nodes[current]) {
|
return switch (tree.nodes[current.idx()]) {
|
||||||
.leaf => |v| v.as(gtk.Widget),
|
.leaf => |v| v.as(gtk.Widget),
|
||||||
.split => |s| SplitTreeSplit.new(
|
.split => |s| SplitTreeSplit.new(
|
||||||
current,
|
current,
|
||||||
|
|
@ -982,7 +1028,7 @@ const SplitTreeSplit = extern struct {
|
||||||
self.as(gtk.Widget),
|
self.as(gtk.Widget),
|
||||||
) orelse return 0;
|
) orelse return 0;
|
||||||
const tree = split_tree.getTree() orelse return 0;
|
const tree = split_tree.getTree() orelse return 0;
|
||||||
const split: *const Surface.Tree.Split = &tree.nodes[priv.handle].split;
|
const split: *const Surface.Tree.Split = &tree.nodes[priv.handle.idx()].split;
|
||||||
|
|
||||||
// Current, min, and max positions as pixels.
|
// Current, min, and max positions as pixels.
|
||||||
const pos = paned.getPosition();
|
const pos = paned.getPosition();
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
/// All the nodes in the tree. Node at index 0 is always the root.
|
/// All the nodes in the tree. Node at index 0 is always the root.
|
||||||
nodes: []const Node,
|
nodes: []const Node,
|
||||||
|
|
||||||
|
/// The handle of the zoomed node. A "zoomed" node is one that is
|
||||||
|
/// expected to be made the full size of the split tree. Various
|
||||||
|
/// operations may unzoom (e.g. resize).
|
||||||
|
zoomed: ?Node.Handle,
|
||||||
|
|
||||||
/// An empty tree.
|
/// An empty tree.
|
||||||
pub const empty: Self = .{
|
pub const empty: Self = .{
|
||||||
// Arena can be undefined because we have zero allocated nodes.
|
// Arena can be undefined because we have zero allocated nodes.
|
||||||
|
|
@ -63,6 +68,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// arena.
|
// arena.
|
||||||
.arena = undefined,
|
.arena = undefined,
|
||||||
.nodes = &.{},
|
.nodes = &.{},
|
||||||
|
.zoomed = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Node = union(enum) {
|
pub const Node = union(enum) {
|
||||||
|
|
@ -72,7 +78,24 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
/// A handle into the nodes array. This lets us keep track of
|
/// A handle into the nodes array. This lets us keep track of
|
||||||
/// nodes with 16-bit handles rather than full pointer-width
|
/// nodes with 16-bit handles rather than full pointer-width
|
||||||
/// values.
|
/// values.
|
||||||
pub const Handle = u16;
|
pub const Handle = enum(Backing) {
|
||||||
|
root = 0,
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub const Backing = u16;
|
||||||
|
|
||||||
|
pub inline fn idx(self: Handle) usize {
|
||||||
|
return @intFromEnum(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Offset the handle by a given amount.
|
||||||
|
pub fn offset(self: Handle, v: usize) Handle {
|
||||||
|
const self_usize: usize = @intCast(@intFromEnum(self));
|
||||||
|
const final = self_usize + v;
|
||||||
|
assert(final < std.math.maxInt(Backing));
|
||||||
|
return @enumFromInt(final);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Split = struct {
|
pub const Split = struct {
|
||||||
|
|
@ -98,6 +121,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
return .{
|
return .{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.nodes = nodes,
|
.nodes = nodes,
|
||||||
|
.zoomed = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +160,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
return .{
|
return .{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.nodes = nodes,
|
.nodes = nodes,
|
||||||
|
.zoomed = self.zoomed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,17 +183,17 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Iterator = struct {
|
pub const Iterator = struct {
|
||||||
i: Node.Handle = 0,
|
i: Node.Handle = .root,
|
||||||
nodes: []const Node,
|
nodes: []const Node,
|
||||||
|
|
||||||
pub fn next(self: *Iterator) ?ViewEntry {
|
pub fn next(self: *Iterator) ?ViewEntry {
|
||||||
// If we have no nodes, return null.
|
// If we have no nodes, return null.
|
||||||
if (self.i >= self.nodes.len) return null;
|
if (@intFromEnum(self.i) >= self.nodes.len) return null;
|
||||||
|
|
||||||
// Get the current node and increment the index.
|
// Get the current node and increment the index.
|
||||||
const handle = self.i;
|
const handle = self.i;
|
||||||
self.i += 1;
|
self.i = @enumFromInt(handle.idx() + 1);
|
||||||
const node = self.nodes[handle];
|
const node = self.nodes[handle.idx()];
|
||||||
|
|
||||||
return switch (node) {
|
return switch (node) {
|
||||||
.leaf => |v| .{ .handle = handle, .view = v },
|
.leaf => |v| .{ .handle = handle, .view = v },
|
||||||
|
|
@ -177,6 +202,16 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Change the zoomed state to the given node. Assumes the handle
|
||||||
|
/// is valid.
|
||||||
|
pub fn zoom(self: *Self, handle: ?Node.Handle) void {
|
||||||
|
if (handle) |v| {
|
||||||
|
assert(@intFromEnum(v) >= 0);
|
||||||
|
assert(@intFromEnum(v) < self.nodes.len);
|
||||||
|
}
|
||||||
|
self.zoomed = handle;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Goto = union(enum) {
|
pub const Goto = union(enum) {
|
||||||
/// Previous view, null if we're the first view.
|
/// Previous view, null if we're the first view.
|
||||||
previous,
|
previous,
|
||||||
|
|
@ -211,8 +246,8 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
return switch (to) {
|
return switch (to) {
|
||||||
.previous => self.previous(from),
|
.previous => self.previous(from),
|
||||||
.next => self.next(from),
|
.next => self.next(from),
|
||||||
.previous_wrapped => self.previous(from) orelse self.deepest(.right, 0),
|
.previous_wrapped => self.previous(from) orelse self.deepest(.right, .root),
|
||||||
.next_wrapped => self.next(from) orelse self.deepest(.left, 0),
|
.next_wrapped => self.next(from) orelse self.deepest(.left, .root),
|
||||||
.spatial => |d| spatial: {
|
.spatial => |d| spatial: {
|
||||||
// Get our spatial representation.
|
// Get our spatial representation.
|
||||||
var sp = try self.spatial(alloc);
|
var sp = try self.spatial(alloc);
|
||||||
|
|
@ -234,7 +269,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
) Node.Handle {
|
) Node.Handle {
|
||||||
var current: Node.Handle = from;
|
var current: Node.Handle = from;
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (self.nodes[current]) {
|
switch (self.nodes[current.idx()]) {
|
||||||
.leaf => return current,
|
.leaf => return current,
|
||||||
.split => |s| current = switch (side) {
|
.split => |s| current = switch (side) {
|
||||||
.left => s.left,
|
.left => s.left,
|
||||||
|
|
@ -253,7 +288,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
/// may want to change this to something that better matches a
|
/// may want to change this to something that better matches a
|
||||||
/// spatial view of the tree later.
|
/// spatial view of the tree later.
|
||||||
fn previous(self: *const Self, from: Node.Handle) ?Node.Handle {
|
fn previous(self: *const Self, from: Node.Handle) ?Node.Handle {
|
||||||
return switch (self.previousBacktrack(from, 0)) {
|
return switch (self.previousBacktrack(from, .root)) {
|
||||||
.result => |v| v,
|
.result => |v| v,
|
||||||
.backtrack, .deadend => null,
|
.backtrack, .deadend => null,
|
||||||
};
|
};
|
||||||
|
|
@ -261,7 +296,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
|
|
||||||
/// Same as `previous`, but returns the next view instead.
|
/// Same as `previous`, but returns the next view instead.
|
||||||
fn next(self: *const Self, from: Node.Handle) ?Node.Handle {
|
fn next(self: *const Self, from: Node.Handle) ?Node.Handle {
|
||||||
return switch (self.nextBacktrack(from, 0)) {
|
return switch (self.nextBacktrack(from, .root)) {
|
||||||
.result => |v| v,
|
.result => |v| v,
|
||||||
.backtrack, .deadend => null,
|
.backtrack, .deadend => null,
|
||||||
};
|
};
|
||||||
|
|
@ -286,7 +321,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// value of, then we need to backtrack from here.
|
// value of, then we need to backtrack from here.
|
||||||
if (from == current) return .backtrack;
|
if (from == current) return .backtrack;
|
||||||
|
|
||||||
return switch (self.nodes[current]) {
|
return switch (self.nodes[current.idx()]) {
|
||||||
// If we hit a leaf that isn't our target, then deadend.
|
// If we hit a leaf that isn't our target, then deadend.
|
||||||
.leaf => .deadend,
|
.leaf => .deadend,
|
||||||
|
|
||||||
|
|
@ -322,7 +357,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
current: Node.Handle,
|
current: Node.Handle,
|
||||||
) Backtrack {
|
) Backtrack {
|
||||||
if (from == current) return .backtrack;
|
if (from == current) return .backtrack;
|
||||||
return switch (self.nodes[current]) {
|
return switch (self.nodes[current.idx()]) {
|
||||||
.leaf => .deadend,
|
.leaf => .deadend,
|
||||||
.split => |s| switch (self.nextBacktrack(from, s.right)) {
|
.split => |s| switch (self.nextBacktrack(from, s.right)) {
|
||||||
.result => |v| .{ .result = v },
|
.result => |v| .{ .result = v },
|
||||||
|
|
@ -343,7 +378,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
from: Node.Handle,
|
from: Node.Handle,
|
||||||
direction: Spatial.Direction,
|
direction: Spatial.Direction,
|
||||||
) ?Node.Handle {
|
) ?Node.Handle {
|
||||||
const target = sp.slots[from];
|
const target = sp.slots[from.idx()];
|
||||||
|
|
||||||
var result: ?struct {
|
var result: ?struct {
|
||||||
handle: Node.Handle,
|
handle: Node.Handle,
|
||||||
|
|
@ -351,7 +386,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
} = null;
|
} = null;
|
||||||
for (sp.slots, 0..) |slot, handle| {
|
for (sp.slots, 0..) |slot, handle| {
|
||||||
// Never match ourself
|
// Never match ourself
|
||||||
if (handle == from) continue;
|
if (handle == from.idx()) continue;
|
||||||
|
|
||||||
// Only match leaves
|
// Only match leaves
|
||||||
switch (self.nodes[handle]) {
|
switch (self.nodes[handle]) {
|
||||||
|
|
@ -377,7 +412,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
if (distance >= n.distance) continue;
|
if (distance >= n.distance) continue;
|
||||||
}
|
}
|
||||||
result = .{
|
result = .{
|
||||||
.handle = @intCast(handle),
|
.handle = @enumFromInt(handle),
|
||||||
.distance = distance,
|
.distance = distance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +437,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// who directly access the nodes to be able to modify them
|
// who directly access the nodes to be able to modify them
|
||||||
// (without nasty stuff like this), but given this is internal
|
// (without nasty stuff like this), but given this is internal
|
||||||
// usage its perfectly fine to modify the node in-place.
|
// usage its perfectly fine to modify the node in-place.
|
||||||
const s: *Split = @constCast(&self.nodes[at].split);
|
const s: *Split = @constCast(&self.nodes[at.idx()].split);
|
||||||
s.ratio = ratio;
|
s.ratio = ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -430,7 +465,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// We know we're going to need the sum total of the nodes
|
// We know we're going to need the sum total of the nodes
|
||||||
// between the two trees plus one for the new split node.
|
// between the two trees plus one for the new split node.
|
||||||
const nodes = try alloc.alloc(Node, self.nodes.len + insert.nodes.len + 1);
|
const nodes = try alloc.alloc(Node, self.nodes.len + insert.nodes.len + 1);
|
||||||
if (nodes.len > std.math.maxInt(Node.Handle)) return error.OutOfMemory;
|
if (nodes.len > std.math.maxInt(Node.Handle.Backing)) return error.OutOfMemory;
|
||||||
|
|
||||||
// We can copy our nodes exactly as they are, since they're
|
// We can copy our nodes exactly as they are, since they're
|
||||||
// mostly not changing (only `at` is changing).
|
// mostly not changing (only `at` is changing).
|
||||||
|
|
@ -446,8 +481,8 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
.leaf => {},
|
.leaf => {},
|
||||||
.split => |*s| {
|
.split => |*s| {
|
||||||
// We need to offset the handles in the split
|
// We need to offset the handles in the split
|
||||||
s.left += @intCast(self.nodes.len);
|
s.left = s.left.offset(self.nodes.len);
|
||||||
s.right += @intCast(self.nodes.len);
|
s.right = s.right.offset(self.nodes.len);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -461,18 +496,23 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
|
|
||||||
// Copy our previous value to the end of the nodes list and
|
// Copy our previous value to the end of the nodes list and
|
||||||
// create our new split node.
|
// create our new split node.
|
||||||
nodes[nodes.len - 1] = nodes[at];
|
nodes[nodes.len - 1] = nodes[at.idx()];
|
||||||
nodes[at] = .{ .split = .{
|
nodes[at.idx()] = .{ .split = .{
|
||||||
.layout = layout,
|
.layout = layout,
|
||||||
.ratio = ratio,
|
.ratio = ratio,
|
||||||
.left = @intCast(if (left) self.nodes.len else nodes.len - 1),
|
.left = @enumFromInt(if (left) self.nodes.len else nodes.len - 1),
|
||||||
.right = @intCast(if (left) nodes.len - 1 else self.nodes.len),
|
.right = @enumFromInt(if (left) nodes.len - 1 else self.nodes.len),
|
||||||
} };
|
} };
|
||||||
|
|
||||||
// We need to increase the reference count of all the nodes.
|
// We need to increase the reference count of all the nodes.
|
||||||
try refNodes(gpa, nodes);
|
try refNodes(gpa, nodes);
|
||||||
|
|
||||||
return .{ .arena = arena, .nodes = nodes };
|
return .{
|
||||||
|
.arena = arena,
|
||||||
|
.nodes = nodes,
|
||||||
|
// Splitting always resets zoom state.
|
||||||
|
.zoomed = null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a node from the tree.
|
/// Remove a node from the tree.
|
||||||
|
|
@ -481,10 +521,10 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
gpa: Allocator,
|
gpa: Allocator,
|
||||||
at: Node.Handle,
|
at: Node.Handle,
|
||||||
) Allocator.Error!Self {
|
) Allocator.Error!Self {
|
||||||
assert(at < self.nodes.len);
|
assert(at.idx() < self.nodes.len);
|
||||||
|
|
||||||
// If we're removing node zero then we're clearing the tree.
|
// If we're removing node zero then we're clearing the tree.
|
||||||
if (at == 0) return .empty;
|
if (at == .root) return .empty;
|
||||||
|
|
||||||
// The new arena for our new tree.
|
// The new arena for our new tree.
|
||||||
var arena = ArenaAllocator.init(gpa);
|
var arena = ArenaAllocator.init(gpa);
|
||||||
|
|
@ -494,43 +534,61 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// Allocate our new nodes list with the number of nodes we'll
|
// Allocate our new nodes list with the number of nodes we'll
|
||||||
// need after the removal.
|
// need after the removal.
|
||||||
const nodes = try alloc.alloc(Node, self.countAfterRemoval(
|
const nodes = try alloc.alloc(Node, self.countAfterRemoval(
|
||||||
0,
|
.root,
|
||||||
at,
|
at,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
var result: Self = .{
|
||||||
|
.arena = arena,
|
||||||
|
.nodes = nodes,
|
||||||
|
.zoomed = null,
|
||||||
|
};
|
||||||
|
|
||||||
// Traverse the tree and copy all our nodes into place.
|
// Traverse the tree and copy all our nodes into place.
|
||||||
assert(self.removeNode(
|
assert(self.removeNode(
|
||||||
nodes,
|
&result,
|
||||||
0,
|
|
||||||
0,
|
0,
|
||||||
|
.root,
|
||||||
at,
|
at,
|
||||||
) > 0);
|
) != 0);
|
||||||
|
|
||||||
// Increase the reference count of all the nodes.
|
// Increase the reference count of all the nodes.
|
||||||
try refNodes(gpa, nodes);
|
try refNodes(gpa, nodes);
|
||||||
|
|
||||||
return .{
|
return result;
|
||||||
.arena = arena,
|
|
||||||
.nodes = nodes,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn removeNode(
|
fn removeNode(
|
||||||
self: *Self,
|
old: *Self,
|
||||||
nodes: []Node,
|
new: *Self,
|
||||||
new_offset: Node.Handle,
|
new_offset: usize,
|
||||||
current: Node.Handle,
|
current: Node.Handle,
|
||||||
target: Node.Handle,
|
target: Node.Handle,
|
||||||
) Node.Handle {
|
) usize {
|
||||||
assert(current != target);
|
assert(current != target);
|
||||||
|
|
||||||
switch (self.nodes[current]) {
|
// If we have a zoomed node and this is it then we migrate it.
|
||||||
|
if (old.zoomed) |v| {
|
||||||
|
if (v == current) {
|
||||||
|
assert(new.zoomed == null);
|
||||||
|
new.zoomed = @enumFromInt(new_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's talk about this constCast. Our member are const but
|
||||||
|
// we actually always own their memory. We don't want consumers
|
||||||
|
// who directly access the nodes to be able to modify them
|
||||||
|
// (without nasty stuff like this), but given this is internal
|
||||||
|
// usage its perfectly fine to modify the node in-place.
|
||||||
|
const new_nodes: []Node = @constCast(new.nodes);
|
||||||
|
|
||||||
|
switch (old.nodes[current.idx()]) {
|
||||||
// Leaf is simple, just copy it over. We don't ref anything
|
// Leaf is simple, just copy it over. We don't ref anything
|
||||||
// yet because it'd make undo (errdefer) harder. We do that
|
// yet because it'd make undo (errdefer) harder. We do that
|
||||||
// all at once later.
|
// all at once later.
|
||||||
.leaf => |view| {
|
.leaf => |view| {
|
||||||
nodes[new_offset] = .{ .leaf = view };
|
new_nodes[new_offset] = .{ .leaf = view };
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -538,39 +596,39 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// If we're removing one of the split node sides then
|
// If we're removing one of the split node sides then
|
||||||
// we remove the split node itself as well and only add
|
// we remove the split node itself as well and only add
|
||||||
// the other (non-removed) side.
|
// the other (non-removed) side.
|
||||||
if (s.left == target) return self.removeNode(
|
if (s.left == target) return old.removeNode(
|
||||||
nodes,
|
new,
|
||||||
new_offset,
|
new_offset,
|
||||||
s.right,
|
s.right,
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
if (s.right == target) return self.removeNode(
|
if (s.right == target) return old.removeNode(
|
||||||
nodes,
|
new,
|
||||||
new_offset,
|
new_offset,
|
||||||
s.left,
|
s.left,
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Neither side is being directly removed, so we traverse.
|
// Neither side is being directly removed, so we traverse.
|
||||||
const left = self.removeNode(
|
const left = old.removeNode(
|
||||||
nodes,
|
new,
|
||||||
new_offset + 1,
|
new_offset + 1,
|
||||||
s.left,
|
s.left,
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
assert(left > 0);
|
assert(left != 0);
|
||||||
const right = self.removeNode(
|
const right = old.removeNode(
|
||||||
nodes,
|
new,
|
||||||
new_offset + 1 + left,
|
new_offset + left + 1,
|
||||||
s.right,
|
s.right,
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
assert(right > 0);
|
assert(right != 0);
|
||||||
nodes[new_offset] = .{ .split = .{
|
new_nodes[new_offset] = .{ .split = .{
|
||||||
.layout = s.layout,
|
.layout = s.layout,
|
||||||
.ratio = s.ratio,
|
.ratio = s.ratio,
|
||||||
.left = new_offset + 1,
|
.left = @enumFromInt(new_offset + 1),
|
||||||
.right = new_offset + 1 + left,
|
.right = @enumFromInt(new_offset + 1 + left),
|
||||||
} };
|
} };
|
||||||
|
|
||||||
return left + right + 1;
|
return left + right + 1;
|
||||||
|
|
@ -588,7 +646,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
) usize {
|
) usize {
|
||||||
assert(current != target);
|
assert(current != target);
|
||||||
|
|
||||||
return switch (self.nodes[current]) {
|
return switch (self.nodes[current.idx()]) {
|
||||||
// Leaf is simple, always takes one node.
|
// Leaf is simple, always takes one node.
|
||||||
.leaf => acc + 1,
|
.leaf => acc + 1,
|
||||||
|
|
||||||
|
|
@ -679,6 +737,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
return .{
|
return .{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.nodes = nodes,
|
.nodes = nodes,
|
||||||
|
.zoomed = self.zoomed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -688,7 +747,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
layout: Split.Layout,
|
layout: Split.Layout,
|
||||||
acc: usize,
|
acc: usize,
|
||||||
) usize {
|
) usize {
|
||||||
return switch (self.nodes[from]) {
|
return switch (self.nodes[from.idx()]) {
|
||||||
.leaf => acc + 1,
|
.leaf => acc + 1,
|
||||||
.split => |s| if (s.layout == layout)
|
.split => |s| if (s.layout == layout)
|
||||||
self.weight(s.left, layout, acc) +
|
self.weight(s.left, layout, acc) +
|
||||||
|
|
@ -737,7 +796,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
const parent_handle = switch (self.findParentSplit(
|
const parent_handle = switch (self.findParentSplit(
|
||||||
layout,
|
layout,
|
||||||
from,
|
from,
|
||||||
0,
|
.root,
|
||||||
)) {
|
)) {
|
||||||
.deadend, .backtrack => return result,
|
.deadend, .backtrack => return result,
|
||||||
.result => |v| v,
|
.result => |v| v,
|
||||||
|
|
@ -755,11 +814,11 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
// own but I'm trying to avoid that word: its the ratio of
|
// own but I'm trying to avoid that word: its the ratio of
|
||||||
// our spatial width/height to the total.
|
// our spatial width/height to the total.
|
||||||
const scale = switch (layout) {
|
const scale = switch (layout) {
|
||||||
.horizontal => sp.slots[parent_handle].width / sp.slots[0].width,
|
.horizontal => sp.slots[parent_handle.idx()].width / sp.slots[0].width,
|
||||||
.vertical => sp.slots[parent_handle].height / sp.slots[0].height,
|
.vertical => sp.slots[parent_handle.idx()].height / sp.slots[0].height,
|
||||||
};
|
};
|
||||||
|
|
||||||
const current = result.nodes[parent_handle].split.ratio;
|
const current = result.nodes[parent_handle.idx()].split.ratio;
|
||||||
break :full_ratio current * scale;
|
break :full_ratio current * scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -778,7 +837,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
current: Node.Handle,
|
current: Node.Handle,
|
||||||
) Backtrack {
|
) Backtrack {
|
||||||
if (from == current) return .backtrack;
|
if (from == current) return .backtrack;
|
||||||
return switch (self.nodes[current]) {
|
return switch (self.nodes[current.idx()]) {
|
||||||
.leaf => .deadend,
|
.leaf => .deadend,
|
||||||
.split => |s| switch (self.findParentSplit(
|
.split => |s| switch (self.findParentSplit(
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -861,7 +920,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
if (self.nodes.len == 0) return .empty;
|
if (self.nodes.len == 0) return .empty;
|
||||||
|
|
||||||
// Get our total dimensions.
|
// Get our total dimensions.
|
||||||
const dim = self.dimensions(0);
|
const dim = self.dimensions(.root);
|
||||||
|
|
||||||
// Create our slots which will match our nodes exactly.
|
// Create our slots which will match our nodes exactly.
|
||||||
const slots = try alloc.alloc(Spatial.Slot, self.nodes.len);
|
const slots = try alloc.alloc(Spatial.Slot, self.nodes.len);
|
||||||
|
|
@ -872,7 +931,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
.width = @floatFromInt(dim.width),
|
.width = @floatFromInt(dim.width),
|
||||||
.height = @floatFromInt(dim.height),
|
.height = @floatFromInt(dim.height),
|
||||||
};
|
};
|
||||||
self.fillSpatialSlots(slots, 0);
|
self.fillSpatialSlots(slots, .root);
|
||||||
|
|
||||||
// Normalize the dimensions to 1x1 grid.
|
// Normalize the dimensions to 1x1 grid.
|
||||||
for (slots) |*slot| {
|
for (slots) |*slot| {
|
||||||
|
|
@ -888,10 +947,10 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
fn fillSpatialSlots(
|
fn fillSpatialSlots(
|
||||||
self: *const Self,
|
self: *const Self,
|
||||||
slots: []Spatial.Slot,
|
slots: []Spatial.Slot,
|
||||||
current: Node.Handle,
|
current_: Node.Handle,
|
||||||
) void {
|
) void {
|
||||||
|
const current = current_.idx();
|
||||||
assert(slots[current].width >= 0 and slots[current].height >= 0);
|
assert(slots[current].width >= 0 and slots[current].height >= 0);
|
||||||
|
|
||||||
switch (self.nodes[current]) {
|
switch (self.nodes[current]) {
|
||||||
// Leaf node, current slot is already filled by caller.
|
// Leaf node, current slot is already filled by caller.
|
||||||
.leaf => {},
|
.leaf => {},
|
||||||
|
|
@ -899,13 +958,13 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
.split => |s| {
|
.split => |s| {
|
||||||
switch (s.layout) {
|
switch (s.layout) {
|
||||||
.horizontal => {
|
.horizontal => {
|
||||||
slots[s.left] = .{
|
slots[s.left.idx()] = .{
|
||||||
.x = slots[current].x,
|
.x = slots[current].x,
|
||||||
.y = slots[current].y,
|
.y = slots[current].y,
|
||||||
.width = slots[current].width * s.ratio,
|
.width = slots[current].width * s.ratio,
|
||||||
.height = slots[current].height,
|
.height = slots[current].height,
|
||||||
};
|
};
|
||||||
slots[s.right] = .{
|
slots[s.right.idx()] = .{
|
||||||
.x = slots[current].x + slots[current].width * s.ratio,
|
.x = slots[current].x + slots[current].width * s.ratio,
|
||||||
.y = slots[current].y,
|
.y = slots[current].y,
|
||||||
.width = slots[current].width * (1 - s.ratio),
|
.width = slots[current].width * (1 - s.ratio),
|
||||||
|
|
@ -914,13 +973,13 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
},
|
},
|
||||||
|
|
||||||
.vertical => {
|
.vertical => {
|
||||||
slots[s.left] = .{
|
slots[s.left.idx()] = .{
|
||||||
.x = slots[current].x,
|
.x = slots[current].x,
|
||||||
.y = slots[current].y,
|
.y = slots[current].y,
|
||||||
.width = slots[current].width,
|
.width = slots[current].width,
|
||||||
.height = slots[current].height * s.ratio,
|
.height = slots[current].height * s.ratio,
|
||||||
};
|
};
|
||||||
slots[s.right] = .{
|
slots[s.right.idx()] = .{
|
||||||
.x = slots[current].x,
|
.x = slots[current].x,
|
||||||
.y = slots[current].y + slots[current].height * s.ratio,
|
.y = slots[current].y + slots[current].height * s.ratio,
|
||||||
.width = slots[current].width,
|
.width = slots[current].width,
|
||||||
|
|
@ -943,7 +1002,7 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
width: u16,
|
width: u16,
|
||||||
height: u16,
|
height: u16,
|
||||||
} {
|
} {
|
||||||
return switch (self.nodes[current]) {
|
return switch (self.nodes[current.idx()]) {
|
||||||
.leaf => .{ .width = 1, .height = 1 },
|
.leaf => .{ .width = 1, .height = 1 },
|
||||||
.split => |s| split: {
|
.split => |s| split: {
|
||||||
const left = self.dimensions(s.left);
|
const left = self.dimensions(s.left);
|
||||||
|
|
@ -988,10 +1047,10 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
self.formatDiagram(writer) catch
|
self.formatDiagram(writer) catch
|
||||||
try writer.writeAll("failed to draw split tree diagram");
|
try writer.writeAll("failed to draw split tree diagram");
|
||||||
} else if (std.mem.eql(u8, fmt, "text")) {
|
} else if (std.mem.eql(u8, fmt, "text")) {
|
||||||
try self.formatText(writer, 0, 0);
|
try self.formatText(writer, .root, 0);
|
||||||
} else if (fmt.len == 0) {
|
} else if (fmt.len == 0) {
|
||||||
self.formatDiagram(writer) catch {};
|
self.formatDiagram(writer) catch {};
|
||||||
try self.formatText(writer, 0, 0);
|
try self.formatText(writer, .root, 0);
|
||||||
} else {
|
} else {
|
||||||
return error.InvalidFormat;
|
return error.InvalidFormat;
|
||||||
}
|
}
|
||||||
|
|
@ -1005,7 +1064,11 @@ pub fn SplitTree(comptime V: type) type {
|
||||||
) !void {
|
) !void {
|
||||||
for (0..depth) |_| try writer.writeAll(" ");
|
for (0..depth) |_| try writer.writeAll(" ");
|
||||||
|
|
||||||
switch (self.nodes[current]) {
|
if (self.zoomed) |zoomed| if (zoomed == current) {
|
||||||
|
try writer.writeAll("(zoomed) ");
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (self.nodes[current.idx()]) {
|
||||||
.leaf => |v| if (@hasDecl(View, "splitTreeLabel"))
|
.leaf => |v| if (@hasDecl(View, "splitTreeLabel"))
|
||||||
try writer.print("leaf: {s}\n", .{v.splitTreeLabel()})
|
try writer.print("leaf: {s}\n", .{v.splitTreeLabel()})
|
||||||
else
|
else
|
||||||
|
|
@ -1312,7 +1375,7 @@ test "SplitTree: split horizontal" {
|
||||||
defer t2.deinit();
|
defer t2.deinit();
|
||||||
var t3 = try t1.split(
|
var t3 = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
0.5,
|
0.5,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1416,7 +1479,7 @@ test "SplitTree: split horizontal" {
|
||||||
} else return error.NotFound,
|
} else return error.NotFound,
|
||||||
).?;
|
).?;
|
||||||
|
|
||||||
const entry = t5.nodes[handle].leaf;
|
const entry = t5.nodes[handle.idx()].leaf;
|
||||||
try testing.expectEqualStrings(
|
try testing.expectEqualStrings(
|
||||||
entry.label,
|
entry.label,
|
||||||
&.{current - 1},
|
&.{current - 1},
|
||||||
|
|
@ -1446,7 +1509,7 @@ test "SplitTree: split horizontal" {
|
||||||
} else return error.NotFound,
|
} else return error.NotFound,
|
||||||
).?;
|
).?;
|
||||||
|
|
||||||
const entry = t5.nodes[handle].leaf;
|
const entry = t5.nodes[handle.idx()].leaf;
|
||||||
try testing.expectEqualStrings(
|
try testing.expectEqualStrings(
|
||||||
entry.label,
|
entry.label,
|
||||||
&.{current + 1},
|
&.{current + 1},
|
||||||
|
|
@ -1477,7 +1540,7 @@ test "SplitTree: split vertical" {
|
||||||
|
|
||||||
var t3 = try t1.split(
|
var t3 = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.down, // split down
|
.down, // split down
|
||||||
0.5,
|
0.5,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1511,7 +1574,7 @@ test "SplitTree: split horizontal with zero ratio" {
|
||||||
// A | B horizontal
|
// A | B horizontal
|
||||||
var splitAB = try t1.split(
|
var splitAB = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
0,
|
0,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1545,7 +1608,7 @@ test "SplitTree: split vertical with zero ratio" {
|
||||||
// A | B horizontal
|
// A | B horizontal
|
||||||
var splitAB = try t1.split(
|
var splitAB = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.down, // split right
|
.down, // split right
|
||||||
0,
|
0,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1579,7 +1642,7 @@ test "SplitTree: split horizontal with full width" {
|
||||||
// A | B horizontal
|
// A | B horizontal
|
||||||
var splitAB = try t1.split(
|
var splitAB = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
1,
|
1,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1613,7 +1676,7 @@ test "SplitTree: split vertical with full width" {
|
||||||
// A | B horizontal
|
// A | B horizontal
|
||||||
var splitAB = try t1.split(
|
var splitAB = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.down, // split right
|
.down, // split right
|
||||||
1,
|
1,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1645,7 +1708,7 @@ test "SplitTree: remove leaf" {
|
||||||
defer t2.deinit();
|
defer t2.deinit();
|
||||||
var t3 = try t1.split(
|
var t3 = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
0.5,
|
0.5,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1691,7 +1754,7 @@ test "SplitTree: split twice, remove intermediary" {
|
||||||
// A | B horizontal.
|
// A | B horizontal.
|
||||||
var split1 = try t1.split(
|
var split1 = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
0.5,
|
0.5,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1701,7 +1764,7 @@ test "SplitTree: split twice, remove intermediary" {
|
||||||
// Insert C below that.
|
// Insert C below that.
|
||||||
var split2 = try split1.split(
|
var split2 = try split1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.down, // split down
|
.down, // split down
|
||||||
0.5,
|
0.5,
|
||||||
&t3, // insert t3
|
&t3, // insert t3
|
||||||
|
|
@ -1752,7 +1815,7 @@ test "SplitTree: split twice, remove intermediary" {
|
||||||
// never crash. We don't test the result is correct, this just verifies
|
// never crash. We don't test the result is correct, this just verifies
|
||||||
// we don't hit any assertion failures.
|
// we don't hit any assertion failures.
|
||||||
for (0..split2.nodes.len) |i| {
|
for (0..split2.nodes.len) |i| {
|
||||||
var t = try split2.remove(alloc, @intCast(i));
|
var t = try split2.remove(alloc, @enumFromInt(i));
|
||||||
t.deinit();
|
t.deinit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1777,7 +1840,7 @@ test "SplitTree: spatial goto" {
|
||||||
// A | B horizontal
|
// A | B horizontal
|
||||||
var splitAB = try t1.split(
|
var splitAB = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
0.5,
|
0.5,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1853,7 +1916,7 @@ test "SplitTree: spatial goto" {
|
||||||
},
|
},
|
||||||
.{ .spatial = .right },
|
.{ .spatial = .right },
|
||||||
)).?;
|
)).?;
|
||||||
const view = split.nodes[target].leaf;
|
const view = split.nodes[target.idx()].leaf;
|
||||||
try testing.expectEqualStrings(view.label, "D");
|
try testing.expectEqualStrings(view.label, "D");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1871,7 +1934,7 @@ test "SplitTree: spatial goto" {
|
||||||
},
|
},
|
||||||
.{ .spatial = .left },
|
.{ .spatial = .left },
|
||||||
)).?;
|
)).?;
|
||||||
const view = split.nodes[target].leaf;
|
const view = split.nodes[target.idx()].leaf;
|
||||||
try testing.expectEqualStrings("A", view.label);
|
try testing.expectEqualStrings("A", view.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1908,7 +1971,7 @@ test "SplitTree: resize" {
|
||||||
// A | B horizontal
|
// A | B horizontal
|
||||||
var split = try t1.split(
|
var split = try t1.split(
|
||||||
alloc,
|
alloc,
|
||||||
0, // at root
|
.root, // at root
|
||||||
.right, // split right
|
.right, // split right
|
||||||
0.5,
|
0.5,
|
||||||
&t2, // insert t2
|
&t2, // insert t2
|
||||||
|
|
@ -1970,3 +2033,179 @@ test "SplitTree: clone empty tree" {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "SplitTree: zoom" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var v1: TestTree.View = .{ .label = "A" };
|
||||||
|
var t1: TestTree = try .init(alloc, &v1);
|
||||||
|
defer t1.deinit();
|
||||||
|
var v2: TestTree.View = .{ .label = "B" };
|
||||||
|
var t2: TestTree = try .init(alloc, &v2);
|
||||||
|
defer t2.deinit();
|
||||||
|
|
||||||
|
// A | B horizontal
|
||||||
|
var split = try t1.split(
|
||||||
|
alloc,
|
||||||
|
.root, // at root
|
||||||
|
.right, // split right
|
||||||
|
0.5,
|
||||||
|
&t2, // insert t2
|
||||||
|
);
|
||||||
|
defer split.deinit();
|
||||||
|
split.zoom(at: {
|
||||||
|
var it = split.iterator();
|
||||||
|
break :at while (it.next()) |entry| {
|
||||||
|
if (std.mem.eql(u8, entry.view.label, "B")) {
|
||||||
|
break entry.handle;
|
||||||
|
}
|
||||||
|
} else return error.NotFound;
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const str = try std.fmt.allocPrint(alloc, "{text}", .{split});
|
||||||
|
defer alloc.free(str);
|
||||||
|
try testing.expectEqualStrings(str,
|
||||||
|
\\split (layout: horizontal, ratio: 0.50)
|
||||||
|
\\ leaf: A
|
||||||
|
\\ (zoomed) leaf: B
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone preserves zoom
|
||||||
|
var clone = try split.clone(alloc);
|
||||||
|
defer clone.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
const str = try std.fmt.allocPrint(alloc, "{text}", .{clone});
|
||||||
|
defer alloc.free(str);
|
||||||
|
try testing.expectEqualStrings(str,
|
||||||
|
\\split (layout: horizontal, ratio: 0.50)
|
||||||
|
\\ leaf: A
|
||||||
|
\\ (zoomed) leaf: B
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "SplitTree: split resets zoom" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var v1: TestTree.View = .{ .label = "A" };
|
||||||
|
var t1: TestTree = try .init(alloc, &v1);
|
||||||
|
defer t1.deinit();
|
||||||
|
var v2: TestTree.View = .{ .label = "B" };
|
||||||
|
var t2: TestTree = try .init(alloc, &v2);
|
||||||
|
defer t2.deinit();
|
||||||
|
|
||||||
|
// Zoom A
|
||||||
|
t1.zoom(at: {
|
||||||
|
var it = t1.iterator();
|
||||||
|
break :at while (it.next()) |entry| {
|
||||||
|
if (std.mem.eql(u8, entry.view.label, "A")) {
|
||||||
|
break entry.handle;
|
||||||
|
}
|
||||||
|
} else return error.NotFound;
|
||||||
|
});
|
||||||
|
|
||||||
|
// A | B horizontal
|
||||||
|
var split = try t1.split(
|
||||||
|
alloc,
|
||||||
|
.root, // at root
|
||||||
|
.right, // split right
|
||||||
|
0.5,
|
||||||
|
&t2, // insert t2
|
||||||
|
);
|
||||||
|
defer split.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
const str = try std.fmt.allocPrint(alloc, "{text}", .{split});
|
||||||
|
defer alloc.free(str);
|
||||||
|
try testing.expectEqualStrings(str,
|
||||||
|
\\split (layout: horizontal, ratio: 0.50)
|
||||||
|
\\ leaf: A
|
||||||
|
\\ leaf: B
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "SplitTree: remove and zoom" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var v1: TestTree.View = .{ .label = "A" };
|
||||||
|
var t1: TestTree = try .init(alloc, &v1);
|
||||||
|
defer t1.deinit();
|
||||||
|
var v2: TestTree.View = .{ .label = "B" };
|
||||||
|
var t2: TestTree = try .init(alloc, &v2);
|
||||||
|
defer t2.deinit();
|
||||||
|
|
||||||
|
// A | B horizontal
|
||||||
|
var split = try t1.split(
|
||||||
|
alloc,
|
||||||
|
.root, // at root
|
||||||
|
.right, // split right
|
||||||
|
0.5,
|
||||||
|
&t2, // insert t2
|
||||||
|
);
|
||||||
|
defer split.deinit();
|
||||||
|
split.zoom(at: {
|
||||||
|
var it = split.iterator();
|
||||||
|
break :at while (it.next()) |entry| {
|
||||||
|
if (std.mem.eql(u8, entry.view.label, "A")) {
|
||||||
|
break entry.handle;
|
||||||
|
}
|
||||||
|
} else return error.NotFound;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove A, should unzoom
|
||||||
|
{
|
||||||
|
var removed = try split.remove(
|
||||||
|
alloc,
|
||||||
|
at: {
|
||||||
|
var it = split.iterator();
|
||||||
|
break :at while (it.next()) |entry| {
|
||||||
|
if (std.mem.eql(u8, entry.view.label, "A")) {
|
||||||
|
break entry.handle;
|
||||||
|
}
|
||||||
|
} else return error.NotFound;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
defer removed.deinit();
|
||||||
|
try testing.expect(removed.zoomed == null);
|
||||||
|
|
||||||
|
const str = try std.fmt.allocPrint(alloc, "{text}", .{removed});
|
||||||
|
defer alloc.free(str);
|
||||||
|
try testing.expectEqualStrings(str,
|
||||||
|
\\leaf: B
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove B, should keep zoom
|
||||||
|
{
|
||||||
|
var removed = try split.remove(
|
||||||
|
alloc,
|
||||||
|
at: {
|
||||||
|
var it = split.iterator();
|
||||||
|
break :at while (it.next()) |entry| {
|
||||||
|
if (std.mem.eql(u8, entry.view.label, "B")) {
|
||||||
|
break entry.handle;
|
||||||
|
}
|
||||||
|
} else return error.NotFound;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
defer removed.deinit();
|
||||||
|
|
||||||
|
const str = try std.fmt.allocPrint(alloc, "{text}", .{removed});
|
||||||
|
defer alloc.free(str);
|
||||||
|
try testing.expectEqualStrings(str,
|
||||||
|
\\(zoomed) leaf: A
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue