summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig57
1 files changed, 56 insertions, 1 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 8d9c29ab..dc58ee3f 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -5827,6 +5827,17 @@ fn renderFrame(
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);
+ // G13: a body's dots follow its text at a quarter of its speed: its
+ // first line, and a glide's picture offset, in rows.
+ if (chrome.fx & 8 != 0 and chrome.decor_dots != null) for (surface.regionList()) |region| {
+ if (region.kind != .body or cell_decor.nbodies == cell_decor.bodies.len) continue;
+ var rows: f32 = @floatFromInt(region.line);
+ if (g.glide.moving() and g.glide.pane == region.owner) rows += g.glide.offset;
+ const period = cell_decor.uniforms[4];
+ cell_decor.bodies[cell_decor.nbodies] = bodyPixels(region, layout);
+ cell_decor.shifts[cell_decor.nbodies] = @mod(-rows * layout.h * 0.25, period);
+ cell_decor.nbodies += 1;
+ };
// Plaques need an opaque window: with WindowOpacity under 100 the cells
// are drawn as they are, and the plaques not at all.
cell_decor.plaques_off = backgroundOpacity(g.applied_window_opacity) < 1;
@@ -7004,7 +7015,13 @@ fn emitInstance(
(cell_decor.inPlaque(px0 + 0.5, py0 + 0.5) or cell_decor.inPlaque(px0 + layout.w - 0.5, py0 + 0.5)))
instances[idx].effect |= clear_bg_bit;
if (cell_decor.dots) |dotted| {
- if (!resolved.clear_bg and std.mem.eql(u8, &resolved.bg, &dotted)) instances[idx].effect |= dotted_bit;
+ if (!resolved.clear_bg and std.mem.eql(u8, &resolved.bg, &dotted)) {
+ instances[idx].effect |= dotted_bit;
+ // G13: the dots under a body move with its text, a quarter
+ // as far; the serial slot carries it (the shader reads it
+ // as the dots' offset alone).
+ instances[idx].serial = @bitCast(cell_decor.parallaxAt(px0 + 0.5, py0 + 0.5));
+ }
}
if (cell_decor.title) |inks| {
if (resolved.role == .tagline and (std.mem.eql(u8, &resolved.fg, &inks[0]) or std.mem.eql(u8, &resolved.fg, &inks[1]))) instances[idx].effect |= title_bit;
@@ -8954,6 +8971,17 @@ const CellDecor = struct {
grounds: [2][3]u8 = .{ .{ 0, 0, 0 }, .{ 0, 0, 0 } },
plaques_off: bool = false,
+ /// G13: each body's rectangle and how far its dots have moved.
+ bodies: [pardes.MAX_PANES][4]f32 = undefined,
+ shifts: [pardes.MAX_PANES]f32 = undefined,
+ nbodies: usize = 0,
+
+ fn parallaxAt(decor: *const CellDecor, x: f32, y: f32) f32 {
+ for (decor.bodies[0..decor.nbodies], decor.shifts[0..decor.nbodies]) |band, shift|
+ if (x >= band[0] and x < band[2] and y >= band[1] and y < band[3]) return shift;
+ return 0;
+ }
+
fn inPlaque(decor: *const CellDecor, x: f32, y: f32) bool {
for (decor.plaques[0..decor.nplaques]) |band|
if (x >= band[0] and x < band[2] and y >= band[1] and y < band[3]) return true;
@@ -9740,6 +9768,33 @@ test "occlusion shades each body's edges, inside it, never a tag, a grip or the
try std.testing.expect(shades >= 4);
}
+test "a body tells its first line, and lapis's dots under it move a quarter as far" {
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 });
+ defer core.deinit();
+ const pane = try core.setTestFile("line\n" ** 80);
+ pane.scrollBy(8);
+ try std.testing.expect(core.executeBuiltinLine(0, "Theme lapis"));
+ core.settings.parallax = true;
+ for (0..40) |_| core.update(.tick);
+ pardes.test_api.sync(core);
+ const s = try core.render(core.scratch.allocator());
+ const body = for (s.regionList()) |region| {
+ if (region.kind == .body) break region;
+ } else return error.NoBody;
+ try std.testing.expectEqual(@as(i32, 8), body.line);
+ try std.testing.expect(s.chrome.fx & 8 != 0);
+ // The shift the cell shader is handed: a quarter of 8 rows of 20 px,
+ // upward, within the dots' period.
+ var decor = CellDecor.of(&s.chrome);
+ const period = decor.uniforms[4];
+ decor.bodies[0] = .{ 0, 0, 800, 480 };
+ decor.shifts[0] = @mod(-8.0 * 20.0 * 0.25, period);
+ decor.nbodies = 1;
+ try std.testing.expectApproxEqAbs(@mod(-40.0, period), decor.parallaxAt(10, 10), 1e-4);
+ try std.testing.expectEqual(@as(f32, 0), decor.parallaxAt(900, 10));
+}
+
/// 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