From f39f2219f2688b71a01c2f17f191ca9c5450b44b Mon Sep 17 00:00:00 2001 From: foxnne Date: Fri, 16 Oct 2026 14:19:12 -0500 Subject: [PATCH] layout: a split of a carded place opens even halves A place's recorded size was its content rect, but a split's extent is the new half's whole reach, padding included. Every split of a carded place (fizzy's Main and Panel, a float's places) gave the new half the card's padding twice over: 886 | 854 px for Main, 494 / 462 px for its new half. `State.setPlaceMetrics` now records the border box, which is all a split reads it for. Co-Authored-By: Claude Opus 5.5 --- app/layout/Region.zig | 12 +++----- app/layout/State.zig | 10 ++++-- app/layout/ViewDrag.zig | 2 +- tests/integration.zig | 67 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 11 deletions(-) diff --git a/app/layout/Region.zig b/app/layout/Region.zig index d269ef9aa..6d72020b3 100644 --- a/app/layout/Region.zig +++ b/app/layout/Region.zig @@ -68,7 +68,8 @@ resize: bool = false, forget_when_empty: bool = false, /// How this place lays its children. dir: dvui.enums.Direction = .vertical, -/// This frame's content size, in points. A menu split halves one axis. +/// This frame's border box, in its parent's units (points, unless something above it scales): +/// the reach a split of it halves along one axis. size: dvui.Size = .{}, /// This frame's border in physical pixels, for hit-testing a view drag. bounds: dvui.Rect.Physical = .{}, @@ -580,8 +581,7 @@ pub fn init(self: *Layout, src: std.builtin.SourceLocation, init_opts: InitOptio const box = dvui.box(src, .{ .dir = init_opts.dir }, box_opts); if (init_opts.resize) Split.recordEdges(id, box.data(), axis); if (init_opts.name.len > 0) { - const cr = box.data().contentRect(); - self.state.setPlaceMetrics(init_opts.name, .{ .w = cr.w, .h = cr.h }, box.data().borderRectScale().r); + self.state.setPlaceMetrics(init_opts.name, box.data()); core.anchor.mark(box.data(), "region:{s}", .{init_opts.name}); } self.containers[self.depth] = .{ @@ -1304,8 +1304,7 @@ fn drawTreeNode( .forget_when_empty = true, .dir = b.dir, }); - const cr = box.data().contentRect(); - self.state.setPlaceMetrics(b.origin, .{ .w = cr.w, .h = cr.h }, box.data().borderRectScale().r); + self.state.setPlaceMetrics(b.origin, box.data()); } self.containers[self.depth] = .{ .dir = b.dir, @@ -1354,8 +1353,7 @@ pub fn fillInBox(self: *Layout, init_opts: InitOptions, box: *dvui.BoxWidget) !v .dir = init_opts.dir, }); - const cr = box.data().contentRect(); - self.state.setPlaceMetrics(init_opts.name, .{ .w = cr.w, .h = cr.h }, box.data().borderRectScale().r); + self.state.setPlaceMetrics(init_opts.name, box.data()); if (self.depth >= Layout.max_nesting) return; self.containers[self.depth] = .{ diff --git a/app/layout/State.zig b/app/layout/State.zig index 8c2040bef..2685eab02 100644 --- a/app/layout/State.zig +++ b/app/layout/State.zig @@ -791,13 +791,17 @@ fn forgetWidgetSize(id: dvui.Id) void { dvui.dataRemove(null, id, "_chooser_press"); } -pub fn setPlaceMetrics(self: *State, name: []const u8, size: dvui.Size, bounds: dvui.Rect.Physical) void { +/// Where place `name` was drawn this frame, from the box it was drawn in: its border box, in +/// points and in physical pixels. The border box, not the content: a split's extent is the whole +/// reach of the place it opens, padding included, so a split measured from the content rect gave +/// a carded place's new half the card's padding twice over. +pub fn setPlaceMetrics(self: *State, name: []const u8, data: *const dvui.WidgetData) void { var i = self.regions_building.items.len; while (i > 0) { i -= 1; if (!std.mem.eql(u8, self.regions_building.items[i].name, name)) continue; - self.regions_building.items[i].size = size; - self.regions_building.items[i].bounds = bounds; + self.regions_building.items[i].size = data.borderRect().size(); + self.regions_building.items[i].bounds = data.borderRectScale().r; return; } } diff --git a/app/layout/ViewDrag.zig b/app/layout/ViewDrag.zig index 517a3f00b..7661f7628 100644 --- a/app/layout/ViewDrag.zig +++ b/app/layout/ViewDrag.zig @@ -225,7 +225,7 @@ pub const Target = struct { /// Interned, so it outlives the frame the map was taken on. name: []const u8, bounds: dvui.Rect.Physical, - /// Content size in points, for the extent a landing split settles at. + /// Border-box size in points (`Region.size`), for the extent a landing split settles at. size: dvui.Size, /// The window it is drawn in (`Region.layer`): 0 the main window, `n` the `n`th float. layer: u16 = 0, diff --git a/tests/integration.zig b/tests/integration.zig index 1280b0f9e..1ee1d592d 100644 --- a/tests/integration.zig +++ b/tests/integration.zig @@ -2797,6 +2797,73 @@ test "a fizzy Main split keeps the workspace on the leftover side" { try std.testing.expectEqual(@as(usize, 0), created.len); } +/// fizzy's own Main: a carded place that hands its card to its panes, in a column of the window. +const ColumnCardFrame = struct { + var editor: ?*fizzy.Editor = null; + + fn frame() anyerror!dvui.App.Result { + const e = editor.?; + var layout = fizzy.Editor.Layout.init(&e.app.host, &e.app.layout, e.app.gpa, dvui.currentWindow().arena()); + layout.card = .{ .background = true, .padding = .all(8) }; + { + var column = try layout.region(@src(), .{ .dir = .vertical }, .{ .expand = .both }); + defer column.deinit(); + var main = try layout.region(@src(), .{ .name = "Main", .keywords = fizzy.sdk.keywords.ide.main, .card = true, .pane_cards = true }, .{ .expand = .both }); + defer main.deinit(); + } + e.app.layout.publishRegions(); + return .ok; + } + + /// What the place takes on screen: its border box in physical pixels, measured where it was + /// drawn rather than read back from the `size` a split halves. + fn sizeOf(e: *fizzy.Editor, name: []const u8) !dvui.Size { + for (e.app.layout.regions.items) |r| { + if (std.mem.eql(u8, r.name, name)) return .{ .w = r.bounds.w, .h = r.bounds.h }; + } + return error.TestExpectedEqual; + } +}; + +// A split's extent is the new half's whole reach, card and all. Measured from the place's content +// instead, every split of a carded place came out uneven by the card's padding, twice over. +test "a split of a carded place, and of its new half, opens even halves" { + 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; + + 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); + } + try dvui.testing.settle(ColumnCardFrame.frame); + const left = try ColumnCardFrame.sizeOf(editor, "Main"); + const right = try ColumnCardFrame.sizeOf(editor, "Main/r1"); + try std.testing.expect(left.w > 40); + try std.testing.expectApproxEqAbs(left.w, right.w, 1); + + { + var layout = fizzy.Editor.Layout.init(&editor.app.host, &editor.app.layout, editor.app.gpa, dvui.currentWindow().arena()); + try std.testing.expectEqualStrings("Main/r1/b1", layout.splitOn("Main/r1", .bottom) orelse return error.TestExpectedEqual); + } + try dvui.testing.settle(ColumnCardFrame.frame); + const top = try ColumnCardFrame.sizeOf(editor, "Main/r1"); + const bottom = try ColumnCardFrame.sizeOf(editor, "Main/r1/b1"); + try std.testing.expect(top.h > 40); + try std.testing.expectApproxEqAbs(top.h, bottom.h, 1); +} + test "a view-drag split opens on the dropped edge" { var ctx = try shim.init(std.testing.allocator); defer ctx.deinit(std.testing.allocator);