Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/layout/Region.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,9 @@ fn initTree(
/// A turn is the new half sliding shut along the old axis (`_fold`, which its place reads as a
/// target of zero), then easing open along the new one. Its share of the box is kept through the
/// turn (`_share`, taken from the extent when that was last chosen, not from a box that has since
/// changed shape), and follows a drag while turned.
/// changed shape), and follows a drag while turned. That drag is shown and nothing more: turning
/// back takes the share from the saved one again (`_seen` is unread while turned, so dvui drops it
/// and the first frame back reads the saved share anew).
fn orientBranch(self: *Layout, saved: SplitTree.Branch, id: dvui.Id) SplitTree.Branch {
if (!saved.adaptive) return saved;
const dims = dvui.dataGet(null, id, "_dims", dvui.Size) orelse return saved;
Expand All @@ -1462,10 +1464,10 @@ fn orientBranch(self: *Layout, saved: SplitTree.Branch, id: dvui.Id) SplitTree.B
}
}
} else if (chosen) |extent| {
// Dragged since the branch set it: the drag is the new share.
// Dragged since the branch set it: the drag is the share drawn, and is not saved.
const set = dvui.dataGet(null, id, "_set", f32) orelse extent;
const room = alongSize(drawn.dir, dims);
if (set != extent and room > 0) setShare(self, saved.created, id, std.math.clamp(extent / room, 0, 1));
if (set != extent and room > 0) dvui.dataSet(null, id, "_share", std.math.clamp(extent / room, 0, 1));
}
const share = dvui.dataGet(null, id, "_share", f32) orelse return drawn;

Expand Down
30 changes: 21 additions & 9 deletions plugins/workbench/src/Workbench.zig
Original file line number Diff line number Diff line change
Expand Up @@ -231,20 +231,32 @@ pub fn sideBeside(self: *const Workbench, anchor: u64) Panes.Side {
}

/// What opening beside the person's pane asks of `core.placement.place`: the other pane that is
/// there, if any, and the answer over the person's room and that pane's. Null before the person's
/// shown, if any, and the answer over the person's room and that pane's. Null before the person's
/// pane has drawn, with no room to judge.
const Beside = struct { other: ?u64, room: ?core.placement.Size, answer: ?core.placement.Result };

/// A pane other than `here` that is not sliding shut.
fn otherPane(self: *Workbench, here: u64) ?u64 {
for (self.workspaces.values()) |ws| {
if (ws.grouping != here and !self.paneClosing(ws.grouping)) return ws.grouping;
}
return null;
}

fn besideAnswer(self: *Workbench, here: u64) Beside {
const other = for (self.workspaces.values()) |ws| {
if (ws.grouping != here and !self.paneClosing(ws.grouping)) break ws.grouping;
} else null;
const room = self.paneSize(here) orelse return .{ .other = other, .room = null, .answer = null };
const room = self.paneSize(here) orelse return .{ .other = self.otherPane(here), .room = null, .answer = null };
// `place` takes the first of these whatever its size, so a pane is a candidate only if the
// person can be sent to it: drawn, and in a place of the app's.
var others: [1]core.placement.Size = undefined;
const places: []const core.placement.Size = if (other) |g| blk: {
others[0] = self.paneSize(g) orelse .{ .w = 0, .h = 0 };
break :blk &others;
} else &.{};
var other: ?u64 = null;
for (self.workspaces.values()) |ws| {
if (ws.grouping == here or self.paneClosing(ws.grouping)) continue;
if (self.places) |p| if (!p.placed(p.ctx, ws.grouping)) continue;
others[0] = self.paneSize(ws.grouping) orelse continue;
other = ws.grouping;
break;
}
const places: []const core.placement.Size = if (other != null) &others else &.{};
return .{ .other = other, .room = room, .answer = core.placement.place(room, places) };
}

Expand Down
17 changes: 10 additions & 7 deletions src/editor/Workspaces.zig
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,18 @@ pub fn sync(self: *Workspaces) void {
self.settle();
}
var first: ?u64 = null;
var l = layoutOf(editor);
for (wb.workspaces.keys()) |g| {
if (self.assigned(g)) continue;
if (first == null) {
first = g;
continue;
}
// The anchor's room asks the side, when it has one yet.
var l = layoutOf(editor);
var abuf: [64]u8 = undefined;
const room = if (Layout.ViewDrag.holderOf(&l, viewId(&abuf, first.?))) |holder| Layout.ViewDrag.placeSize(l.state, holder) else null;
const side = if (room) |r| Workbench.sideFor(r.w, r.h) else .right;
_ = beside(self, g, first.?, side, true);
_ = besideIn(&l, g, first.?, side, true);
}
}

Expand Down Expand Up @@ -307,15 +307,18 @@ pub fn groupingIn(self: *Workspaces, name: []const u8) ?u64 {

fn beside(ctx: *anyopaque, grouping: u64, anchor: u64, side: Workbench.Panes.Side, adaptive: bool) bool {
const self: *Workspaces = @ptrCast(@alignCast(ctx));
const editor = self.editorOf();
var l = layoutOf(editor);
var l = layoutOf(self.editorOf());
return besideIn(&l, grouping, anchor, side, adaptive);
}

fn besideIn(l: *Layout, grouping: u64, anchor: u64, side: Workbench.Panes.Side, adaptive: bool) bool {
var abuf: [64]u8 = undefined;
const place = Layout.ViewDrag.holderOf(&l, viewId(&abuf, anchor)) orelse return false;
const new = Layout.splitOn(&l, place, side) orelse return false;
const place = Layout.ViewDrag.holderOf(l, viewId(&abuf, anchor)) orelse return false;
const new = Layout.splitOn(l, place, side) orelse return false;
var buf: [64]u8 = undefined;
const id = l.arena.dupe(u8, viewId(&buf, grouping)) catch return false;
l.state.assign(l.gpa, new, &.{id}) catch return false;
if (adaptive) Layout.markAdaptive(&l, new);
if (adaptive) Layout.markAdaptive(l, new);
l.state.markDirty();
return true;
}
Expand Down
78 changes: 78 additions & 0 deletions tests/integration.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4060,6 +4060,74 @@ test "an adaptive split saved beside comes back as the same share stacked" {
try std.testing.expectApproxEqAbs(0.5, bottom.h / (top.h + bottom.h), 0.03);
}

// A sash dragged while an adaptive split is turned is shown and nothing more: the share the saved
// extent gave comes back with the turn back, and the next turn uses it, not the drag's.
test "a drag while an adaptive split is turned is not kept when it turns back" {
var ctx = try shim.init(std.testing.allocator);
defer ctx.deinit(std.testing.allocator);

const editor = ctx.editor;
editor.app.gpa = std.testing.allocator;
defer editor.app.layout.regions.deinit(editor.app.gpa);
defer editor.app.layout.regions_building.deinit(editor.app.gpa);
defer editor.app.layout.deinitExtents(editor.app.gpa);
defer editor.app.layout.deinitAssignments(editor.app.gpa);
defer editor.app.layout.deinitQualified(editor.app.gpa);

ColumnCardFrame.editor = editor;
defer ColumnCardFrame.editor = null;

ctx.t.backend.size = .{ .w = 1180, .h = 820 };
ctx.t.backend.size_pixels = .{ .w = 1180, .h = 820 };
_ = try dvui.testing.step(ColumnCardFrame.frame);
try dvui.testing.settle(ColumnCardFrame.frame);
{
var layout = fizzy.Editor.Layout.init(&editor.app.host, &editor.app.layout, editor.app.gpa, dvui.currentWindow().arena());
try std.testing.expectEqualStrings("Main/r1", layout.splitOn("Main", .right) orelse return error.TestExpectedEqual);
layout.markAdaptive("Main/r1");
}
try dvui.testing.settle(ColumnCardFrame.frame);
const old = try ColumnCardFrame.sizeOf(editor, "Main");
const new = try ColumnCardFrame.sizeOf(editor, "Main/r1");
const share = new.w / (old.w + new.w);
const saved_extent = editor.app.layout.extent("Main/r1", 0);
editor.app.layout.dirty = false;

ctx.t.backend.size = .{ .w = 820, .h = 1180 };
ctx.t.backend.size_pixels = .{ .w = 820, .h = 1180 };
for (0..40) |_| _ = try dvui.testing.step(ColumnCardFrame.frame);

// The sash dragged to a third of the room, as `Split` writes a drag.
const room = (try ColumnCardFrame.sizeOf(editor, "Main")).h + (try ColumnCardFrame.sizeOf(editor, "Main/r1")).h;
const leaf = for (editor.app.layout.regions.items) |r| {
if (std.mem.eql(u8, r.name, "Main/r1")) break r.id;
} else return error.TestExpectedEqual;
dvui.dataSet(null, leaf, "_size", room / 3);
dvui.dataSet(null, leaf, "_shown", room / 3);
for (0..4) |_| _ = try dvui.testing.step(ColumnCardFrame.frame);
const dragged = try ColumnCardFrame.sizeOf(editor, "Main/r1");
try std.testing.expectApproxEqAbs(room / 3, dragged.h, room * 0.05);
try std.testing.expect(!editor.app.layout.dirty);
try std.testing.expectEqual(saved_extent, editor.app.layout.extent("Main/r1", 0));

// Back across: the saved share, not the drag's.
ctx.t.backend.size = .{ .w = 1180, .h = 820 };
ctx.t.backend.size_pixels = .{ .w = 1180, .h = 820 };
for (0..40) |_| _ = try dvui.testing.step(ColumnCardFrame.frame);
const left = try ColumnCardFrame.sizeOf(editor, "Main");
const right = try ColumnCardFrame.sizeOf(editor, "Main/r1");
try std.testing.expectApproxEqAbs(share, right.w / (left.w + right.w), 0.03);

// And upright again: the share is still the saved one.
ctx.t.backend.size = .{ .w = 820, .h = 1180 };
ctx.t.backend.size_pixels = .{ .w = 820, .h = 1180 };
for (0..40) |_| _ = try dvui.testing.step(ColumnCardFrame.frame);
const top = try ColumnCardFrame.sizeOf(editor, "Main");
const bottom = try ColumnCardFrame.sizeOf(editor, "Main/r1");
try std.testing.expectApproxEqAbs(share, bottom.h / (top.h + bottom.h), 0.03);
try std.testing.expect(!editor.app.layout.dirty);
}

test "an adaptive split saved without a share loads with the ratio absent" {
var ctx = try shim.init(std.testing.allocator);
defer ctx.deinit(std.testing.allocator);
Expand Down Expand Up @@ -11350,6 +11418,16 @@ test "headless: the whole editor comes up, opens a file, plays the tour and goes
// With another pane there, it takes the view; without, the person's own does.
const taken = wb.besideGrouping();
if (others > 0) try std.testing.expect(taken != here) else try std.testing.expectEqual(here, taken);
// A pane that has not drawn is no place to send the view: it joins the person's pane.
if (others > 0) {
const other = for (wb.workspaces.values()) |*o| {
if (o.grouping != here) break o;
} else return error.TestUnexpectedResult;
const other_rect = other.pane_rect_physical;
defer other.pane_rect_physical = other_rect;
other.pane_rect_physical = null;
try std.testing.expectEqual(here, wb.besideGrouping());
}
// A demo's `drop:beside` is the answer for the place it is aimed at, not the person's pane's
// for every place: Main's is the pane's edge in its own room, which turns with the room,
// and a place that holds no documents has none.
Expand Down
Loading