summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 09:24:20 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit544ba807b4d33835a953d80b44bc49e146bb940b (patch)
tree3edc8050b234686fa4ed78d48028ce20241fe16d /src
parentab31baff1fbfd38180be8eed921b97082184f185 (diff)
downloadpardes-544ba807b4d33835a953d80b44bc49e146bb940b.tar.gz
pardes-544ba807b4d33835a953d80b44bc49e146bb940b.zip
Lapis plaques stacked on each other share one edge and line up
A column's plaque now starts at its grip's end, where the pane plaque under it starts, and every plaque in a column ends at the same x, far enough short of the next column's rule that no shadow meets it. Where tag bands meet (the workspace's over the columns', a column's over its first pane's), the upper box runs down to the ruled edge with no shadow down, and the lower box lays its top frame over that edge, so there is one line between them, not two frames and a gap.
Diffstat (limited to 'src')
-rw-r--r--src/gui/gui.zig84
1 files changed, 73 insertions, 11 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index b631a1cb..127f9b5d 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -7114,11 +7114,12 @@ test "lapis plaques stay in their tag's band, clear of every grip; only the focu
for (under) |item| {
const r: [4]f32 = .{ (item.x0 + 1) / 2 * 1280, (1 - item.y0) / 2 * 768, (item.x1 + 1) / 2 * 1280, (1 - item.y1) / 2 * 768 };
// Inside one tag's band, whole: never on a body, a grip or the
- // next pane.
+ // next pane. A box stacked under another tag's lays its top frame
+ // over their shared edge, its own width up into the band above.
const home = for (s.regionList()) |region| {
if (region.kind != .tag and region.kind != .column_tag and region.kind != .workspace_tag) continue;
const band = plaqueBox(&g, &s.chrome, s.regionList(), region, layout, 1280, 768) orelse continue;
- if (r[0] >= band.band[0] - 0.01 and r[1] >= band.band[1] - 0.01 and r[2] <= band.band[2] + 0.01 and r[3] <= band.band[3] + 0.01) break region;
+ if (r[0] >= band.band[0] - 0.01 and r[1] >= band.band[1] - band.border - 0.01 and r[2] <= band.band[2] + 0.01 and r[3] <= band.band[3] + 0.01) break region;
} else return error.PlaqueOutsideItsBand;
for (s.regionList()) |grip| {
if (grip.kind != .grip and grip.kind != .column_grip) continue;
@@ -7137,6 +7138,26 @@ test "lapis plaques stay in their tag's band, clear of every grip; only the focu
}
try std.testing.expectEqual(@as(usize, 1), stripes);
try std.testing.expect(shadows >= 2);
+ // Stacked boxes share an edge and line up: a column's box spans its
+ // first pane's across, runs down to the edge that pane's frame lies
+ // over, and no shadow reaches the next column.
+ var stacks: usize = 0;
+ for (s.regionList()) |column| {
+ if (column.kind != .column_tag) continue;
+ const upper = plaqueBox(&g, &s.chrome, s.regionList(), column, layout, 1280, 768).?;
+ for (s.regionList()) |tag| {
+ if (tag.kind != .tag or tag.rect.y != column.rect.y + column.rect.h or tag.rect.x + tag.rect.w != column.rect.x + column.rect.w) continue;
+ const lower = plaqueBox(&g, &s.chrome, s.regionList(), tag, layout, 1280, 768).?;
+ try std.testing.expectEqual(upper.box[0], lower.box[0]);
+ try std.testing.expectEqual(upper.box[2], lower.box[2]);
+ try std.testing.expectEqual(upper.band[3], upper.box[3]);
+ try std.testing.expectEqual(lower.band[1] - lower.border, lower.box[1]);
+ try std.testing.expectEqual(@as(f32, 0), upper.shadow[1]);
+ if (lower.band[2] < 1280) try std.testing.expect(lower.box[2] + lower.shadow[0] < lower.band[2]);
+ stacks += 1;
+ }
+ }
+ try std.testing.expectEqual(@as(usize, 2), stacks);
// The scroll track is chequered.
var checkers: usize = 0;
for (g.decor.items[groups.items[0].decor_start + groups.items[0].under_count ..]) |item| checkers += @intFromBool(item.effect & checker_bit != 0);
@@ -9296,8 +9317,11 @@ fn tagPlaques(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, g
const border_rgb = chrome.decor_box_border orelse return;
if (cell_decor.plaques_off) return;
const page = chrome.page orelse bg_default;
- for (regions) |tag| {
+ // The workspace and column plaques first: a pane's plaque right under
+ // them shares their bottom edge (plaqueBox) and draws its frame last.
+ for ([_]bool{ false, true }) |panes| for (regions) |tag| {
const pane = tag.kind == .tag;
+ if (pane != panes) continue;
if (!pane and tag.kind != .column_tag and tag.kind != .workspace_tag) continue;
if (tag.rect.w == 0 or tag.rect.h == 0) continue;
if (pane and groupOf(groups, tag.serial) != index) continue;
@@ -9316,7 +9340,7 @@ fn tagPlaques(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, g
try addPattern(g, gpa, inner[0], inner[1], inner[2], inner[3], chrome.decor_box_stripe.?, fill, dp(@floatFromInt(@max(1, chrome.decor_box_stripe_px))), true, track, win_w, win_h)
else
try addDecor(g, gpa, inner[0], inner[1], inner[2], inner[3], fill, 1, track, win_w, win_h);
- }
+ };
}
const Plaque = struct { band: [4]f32, box: [4]f32, border: f32, shadow: [2]f32 };
@@ -9325,16 +9349,28 @@ const Plaque = struct { band: [4]f32, box: [4]f32, border: f32, shadow: [2]f32 }
/// grip to the end), the framed box round the tagline's text, and the
/// frame's and the shadow's widths, the shadow cut to what the band's slack
/// below the text and its right end leave. Null where there is no room.
+/// Tags stacked on each other (the workspace's over the columns', a
+/// column's over its first pane's) share one edge, the ruled one between
+/// their bands: the upper box runs down to it and casts no shadow down, the
+/// lower one's frame lies over it. Every box in a column spans the same
+/// pixels across, right of the grips, its shadow short of the next column.
fn plaqueBox(g: *const Gui, chrome: *const pardes.Chrome, regions: []const pardes.Region, tag: pardes.Region, layout: CellLayout, win_w: f32, win_h: f32) ?Plaque {
const pane = tag.kind == .tag;
const first = tag.rect.y;
const last = tag.rect.y + tag.rect.h - 1;
// From its start, right of its grip: the gap cell before its text is
- // the plaque's margin, so its frame is never under the first letter.
- _ = regions;
- const x0 = @as(f32, @floatFromInt(tag.rect.x)) * layout.w;
+ // the plaque's margin, so its frame is never under the first letter. A
+ // column's text sits a cell further in than a pane's; its plaque starts
+ // where the pane's under it does, at its grip's end.
+ const start = if (tag.kind == .column_tag) for (regions) |grip| {
+ if (grip.kind == .column_grip and grip.owner == tag.owner) break grip.rect.x + grip.rect.w;
+ } else tag.rect.x else tag.rect.x;
+ const x0 = @as(f32, @floatFromInt(@min(start, tag.rect.x))) * layout.w;
const x1_cells = @as(f32, @floatFromInt(tag.rect.x + tag.rect.w)) * layout.w;
- const x1 = if (win_w - x1_cells < layout.w) @max(x1_cells, win_w) else x1_cells;
+ const window_edge = win_w - x1_cells < layout.w;
+ const x1 = if (window_edge) @max(x1_cells, win_w) else x1_cells;
+ const stacked_on = tagBandAt(regions, tag, .above);
+ const stacked_under = tagBandAt(regions, tag, .below);
const row_top = @as(f32, @floatFromInt(first)) * layout.h;
const row_bottom = @as(f32, @floatFromInt(last + 1)) * layout.h;
const text_top = row_top + @as(f32, @floatFromInt(taglineBandOffset(first, win_h, g.cell_h, g.tagline_height)));
@@ -9344,16 +9380,42 @@ fn plaqueBox(g: *const Gui, chrome: *const pardes.Chrome, regions: []const parde
// The shadow's full offset to the right; down, what the slack under the
// text leaves (a pixel or three at a tagline under the body's size).
const want: f32 = if (pane) dp(@floatFromInt(chrome.decor_box_shadow_px)) else dp(2);
- const drop = std.math.clamp(@floor(row_bottom - text_bottom - border), 0, want);
- if (x1 - x0 < 4 * border + want) return null;
+ const drop = if (stacked_under) 0 else std.math.clamp(@floor(row_bottom - text_bottom - border), 0, want);
+ // Every box ends where a pane's does, short of the next column's rule
+ // by a pane frame's width past its shadow, so no shadow meets the rule
+ // and a column's box lines up with its panes'; the window's edge needs
+ // no gap.
+ const pane_shadow = dp(@floatFromInt(chrome.decor_box_shadow_px));
+ const gap = @max(0, pane_shadow - want) + if (window_edge) 0 else dp(@floatFromInt(@max(1, chrome.decor_box_border_px)));
+ if (x1 - x0 < 4 * border + want + gap) return null;
return .{
.band = .{ x0, row_top, x1, row_bottom },
- .box = .{ x0, text_top - border, x1 - want, text_bottom + border },
+ .box = .{
+ x0,
+ if (stacked_on) row_top - border else text_top - border,
+ x1 - want - gap,
+ if (stacked_under) row_bottom else text_bottom + border,
+ },
.border = border,
.shadow = .{ want, drop },
};
}
+/// Whether another tag's band (the workspace's, a column's, a pane's) meets
+/// `tag`'s right above or below it, across some of its width.
+fn tagBandAt(regions: []const pardes.Region, tag: pardes.Region, side: enum { above, below }) bool {
+ for (regions) |other| {
+ if (other.kind != .tag and other.kind != .column_tag and other.kind != .workspace_tag) continue;
+ if (other.rect.h == 0 or std.meta.eql(other.rect, tag.rect)) continue;
+ const meets = switch (side) {
+ .above => other.rect.y + other.rect.h == tag.rect.y,
+ .below => tag.rect.y + tag.rect.h == other.rect.y,
+ };
+ if (meets and other.rect.x < tag.rect.x + tag.rect.w and tag.rect.x < other.rect.x + other.rect.w) return true;
+ }
+ return false;
+}
+
/// The two short bars on each row of a grip with no mode glyph there,
/// centered in its anchor. Only the panes of group `index`.
fn gripDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, cells: []const pardes.Cell, groups: *const Groups, index: usize, chrome: *const pardes.Chrome, layout: CellLayout, cols: u16, rows: u16, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void {