diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 07:47:39 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | ab668719385c94df3e274f71cb744f5917c1da7e (patch) | |
| tree | 3f3002e8a7dec61e0b2e467828490405e0a14463 /src/gui/gui.zig | |
| parent | 87ad46cb7e7410c53419c20cef7df53ed4c15f60 (diff) | |
| download | pardes-ab668719385c94df3e274f71cb744f5917c1da7e.tar.gz pardes-ab668719385c94df3e274f71cb744f5917c1da7e.zip | |
G12: Occlusion, pane bodies darkening faintly toward their edges
Occlusion on|off (off, a pixel shell's; Chrome.fx bit 2 — the glow bits
field is now `fx`): four soft strips cast from just outside each pane body
inward, clipped to the body and round a cursor, black pre-warped to
linear light, 2% at most (§9.1) and under the lift's contrast ceiling,
in the pane's own group.
Tests: the shade stays inside the body, off the cursor, at most 2%.
Gates as G11. Feel review: .scratch/render/occlusion/ (with an x40 diff).
Diffstat (limited to 'src/gui/gui.zig')
| -rw-r--r-- | src/gui/gui.zig | 91 |
1 files changed, 85 insertions, 6 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 45861ea4..6bde8b60 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -5815,9 +5815,9 @@ fn renderFrame( if (g.glide.pending) g.glide.measure(gpa, surface); g.sel_glow.now_ns = if (core) |p| p.now_ns else c.SDL_GetTicksNS(); // Off, or while panes move, no glow and nothing fading. - if (surface.chrome.glows & 1 == 0 or surface.panelTracks().len != 0) g.sel_glow = .{ .now_ns = g.sel_glow.now_ns }; + if (surface.chrome.fx & 1 == 0 or surface.panelTracks().len != 0) g.sel_glow = .{ .now_ns = g.sel_glow.now_ns }; g.hover_glow.now_ns = g.sel_glow.now_ns; - if (surface.chrome.glows & 2 == 0 or surface.panelTracks().len != 0) g.hover_glow = .{ .now_ns = g.sel_glow.now_ns }; + if (surface.chrome.fx & 2 == 0 or surface.panelTracks().len != 0) g.hover_glow = .{ .now_ns = g.sel_glow.now_ns }; if (g.glide.moving()) g.glide.offset = g.glide.spring.value(g.glide.now_ns); cell_decor = CellDecor.of(chrome); // Plaques need an opaque window: with WindowOpacity under 100 the cells @@ -8708,9 +8708,11 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g const bodies = body_buf[0..nbodies]; try paneDecor(g, gpa, placed, groups, index, chrome, layout, surface.cols, surface.rows, win_w, win_h, track); // G9, while nothing moves: a halo round the body's selection. - if (index == 0 and groups.tracks == 0 and chrome.glows & 1 != 0) try glowBlocks(g, gpa, surface, .selection, layout, win_w, win_h); + if (index == 0 and groups.tracks == 0 and chrome.fx & 1 != 0) try glowBlocks(g, gpa, surface, .selection, layout, win_w, win_h); // G11: a glow under the word a look-hover would open. - if (index == 0 and groups.tracks == 0 and chrome.glows & 2 != 0) try glowBlocks(g, gpa, surface, .hover, layout, win_w, win_h); + if (index == 0 and groups.tracks == 0 and chrome.fx & 2 != 0) try glowBlocks(g, gpa, surface, .hover, layout, win_w, win_h); + // G12: the bodies' edges shaded, a pane's in its own group. + if (chrome.fx & 4 != 0) try occlusion(g, gpa, placed, groups, index, surface, ceilings, layout, win_w, win_h, track); // A single spine runs down each column, through every tag and body. if (index == 0) for (regions) |column| { if (column.kind != .column) continue; @@ -9092,6 +9094,44 @@ fn glowBlocks(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, k tracker.* = next; } +/// G12: ambient occlusion, a pane body darkening faintly toward its edges +/// (§9.1: an inner shadow of 1 to 2%): four soft strips cast from just +/// outside the body inward, clipped to it and round a cursor, black in +/// linear light, under the same contrast ceiling as a lift. +fn occlusion(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, groups: *const Groups, index: usize, surface: *const pardes.Surface, ceilings: LiftCeilings, layout: CellLayout, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { + const strength = @min(0.02, ceilings.shadow); + if (strength <= 0.002) return; + const reach = 0.5 * layout.h; + for (regions) |body| { + if (body.kind != .body or body.rect.w == 0 or body.rect.h == 0 or groupOf(groups, body.serial) != index) continue; + const b = bodyPixels(body, layout); + // The body, less a cursor's cell in it: left, right, above, below. + var parts: [4][4]f32 = undefined; + var nparts: usize = 0; + const cursor: ?[4]f32 = if (surface.cursor) |cu| .{ @as(f32, @floatFromInt(cu.x)) * layout.w, @as(f32, @floatFromInt(cu.y)) * layout.h, @as(f32, @floatFromInt(cu.x + 1)) * layout.w, @as(f32, @floatFromInt(cu.y + 1)) * layout.h } else null; + if (cursor) |cb| if (!(cb[0] >= b[2] or cb[2] <= b[0] or cb[1] >= b[3] or cb[3] <= b[1])) { + parts = .{ .{ b[0], b[1], cb[0], b[3] }, .{ cb[2], b[1], b[2], b[3] }, .{ cb[0], b[1], cb[2], cb[1] }, .{ cb[0], cb[3], cb[2], b[3] } }; + nparts = 4; + }; + if (nparts == 0) { + parts[0] = b; + nparts = 1; + } + const t = reach / 3; + const strips = [_][4]f32{ + .{ b[0], b[1] - t, b[2], b[1] }, + .{ b[0], b[3], b[2], b[3] + t }, + .{ b[0] - t, b[1], b[0], b[3] }, + .{ b[2], b[1], b[2] + t, b[3] }, + }; + for (strips) |strip| { + const first = g.decor.items.len; + try addSoft(g, gpa, strip, reach / 3, 0, 0, strength, .{ 0, 0, 0 }, track, win_w, win_h); + try clipToBodies(g, gpa, first, parts[0..nparts]); + } + } +} + /// A theme's tag plaques (Chrome.decor_box_*), drawn under the cells of /// group `index`: each pane tag, and with the still panes the column and /// workspace tags, becomes a framed box inside its own band with a hard @@ -9536,7 +9576,7 @@ test "the selection glows round its block, on the body only, fading in over 100 core.settings.selection_glow = true; pardes.test_api.sync(core); const s = try core.render(core.scratch.allocator()); - try std.testing.expectEqual(@as(u8, 1), s.chrome.glows); + try std.testing.expectEqual(@as(u8, 1), s.chrome.fx); // Two rows of a selection in the body, as the core paints one. const body = for (s.regionList()) |region| { if (region.kind == .body) break region; @@ -9586,7 +9626,7 @@ test "a hovered word glows along its foot, never over the cursor, fading in over core.settings.hover_glow = true; pardes.test_api.sync(core); const s = try core.render(core.scratch.allocator()); - try std.testing.expectEqual(@as(u8, 2), s.chrome.glows); + try std.testing.expectEqual(@as(u8, 2), s.chrome.fx); const body = for (s.regionList()) |region| { if (region.kind == .body) break region; } else return error.NoBody; @@ -9628,6 +9668,45 @@ test "a hovered word glows along its foot, never over the cursor, fading in over try std.testing.expect(!g.hover_glow.fading); } +test "occlusion shades each body's edges, inside it, never a tag, a grip or the cursor" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.setTestFile("some text on a line\n" ** 30); + core.settings.occlusion = true; + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + try std.testing.expectEqual(@as(u8, 4), s.chrome.fx); + const body = for (s.regionList()) |region| { + if (region.kind == .body) break region; + } else return error.NoBody; + // The cursor on the body's top-left cell, where the shade is. + s.cursor = .{ .x = body.rect.x, .y = body.rect.y }; + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = 12; + g.sel_glow = .{}; + g.hover_glow = .{}; + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, fixedCellLayout(&g), 800, 480, true); + const r = bodyPixels(body, fixedCellLayout(&g)); + var shades: usize = 0; + for (g.decor.items) |item| { + if (item.effect & soft_shadow_bit == 0) continue; + shades += 1; + try std.testing.expect(item.fr <= 0.02 + 1e-6); + try std.testing.expect(item.clip_x0 >= r[0] - 0.01 and item.clip_x1 <= r[2] + 0.01 and item.clip_y0 >= r[1] - 0.01 and item.clip_y1 <= r[3] + 0.01); + const cx: f32 = @floatFromInt(@as(u32, s.cursor.?.x) * 10); + const cy: f32 = @floatFromInt(@as(u32, s.cursor.?.y) * 20); + try std.testing.expect(item.clip_x1 <= cx + 0.01 or item.clip_x0 >= cx + 10 - 0.01 or item.clip_y1 <= cy + 0.01 or item.clip_y0 >= cy + 20 - 0.01); + } + try std.testing.expect(shades >= 4); +} + /// What a bundled post pass leaves alone (Post.Frame.spare), in window /// pixels: every tag with its grip, the column and workspace tags, and the /// notices, the chrome the focus indicators live in. A grip, the gap after |
