summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.zig12
-rw-r--r--src/detached/wire.zig4
-rw-r--r--src/draw.zig5
-rw-r--r--src/gui/gui.zig57
-rw-r--r--src/surface.zig6
5 files changed, 76 insertions, 8 deletions
diff --git a/src/config.zig b/src/config.zig
index 22e65c46..d11cbe76 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -125,6 +125,7 @@ pub const leader_path = paths: {
if (builtins.capabilities.lift) table.set(.SelectionGlow, null);
if (builtins.capabilities.lift) table.set(.HoverGlow, null);
if (builtins.capabilities.lift) table.set(.Occlusion, null);
+ if (builtins.capabilities.lift) table.set(.Parallax, null);
if (builtins.capabilities.lift) table.set(.Lift, null);
table.set(.Motion, null);
table.set(.InactiveDim, null);
@@ -678,6 +679,9 @@ pub const Runtime = struct {
/// G12: pane bodies darken faintly toward their edges (ambient
/// occlusion, at most 2%), a pixel shell's.
occlusion: bool = false,
+ /// G13: a theme's page pattern under the bodies (lapis's dots) moves
+ /// with the text at a quarter of its speed, a pixel shell's.
+ parallax: bool = false,
/// How long a message stays after the key or click that dismisses it.
message_linger_ms: u16 = 800,
/// How long its fall into the row takes, easing out (ยง8.1: arriving,
@@ -901,7 +905,7 @@ pub const Runtime = struct {
/// The message timings a setting word sets, each in milliseconds.
pub const MessageTime = enum { linger, fall, dissolve };
- pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, syntax_bold, verbose, message_animation, ligatures, cursor_blink, selection_glow, hover_glow, occlusion };
+ pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, syntax_bold, verbose, message_animation, ligatures, cursor_blink, selection_glow, hover_glow, occlusion, parallax };
/// A bundled post pass, a Shadertoy file in shaders/post/: the CRT, and
/// G4's bloom, vignette and grain (docs/effects.md).
pub const Scene = enum { crt, bloom, vignette, grain };
@@ -963,6 +967,7 @@ pub const Runtime = struct {
.{ .word = "SelectionGlow", .action = .{ .toggle = .selection_glow }, .availability = .lift, .doc = "A soft halo of the selection's colour round it in the body, on or off." },
.{ .word = "HoverGlow", .action = .{ .toggle = .hover_glow }, .availability = .lift, .doc = "A soft underline of light under the word a look-hover would open, on or off." },
.{ .word = "Occlusion", .action = .{ .toggle = .occlusion }, .availability = .lift, .doc = "Pane bodies darken faintly toward their edges, on or off." },
+ .{ .word = "Parallax", .action = .{ .toggle = .parallax }, .availability = .lift, .doc = "A theme's page pattern moves with the text at a quarter of its speed, on or off." },
.{ .word = "Lift", .action = .lift, .availability = .lift, .doc = "How the focused pane stands off the page: off, shadow, rim or auto." },
.{ .word = "Motion", .action = .motion, .doc = "How animations move: off, crisp, smooth, bouncy or playful." },
.{ .word = "InactiveDim", .action = .inactive_dim, .doc = "Fade the unfocused panes' text by a percentage, 0 through 100, 0 off." },
@@ -1039,9 +1044,9 @@ pub const Runtime = struct {
.toggle => |field| if (field == .ligatures) {
if (setting.availability != .ligatures)
@compileError("the ligatures toggle must use the ligatures capability");
- } else if (field == .cursor_blink or field == .selection_glow or field == .hover_glow or field == .occlusion) {
+ } else if (field == .cursor_blink or field == .selection_glow or field == .hover_glow or field == .occlusion or field == .parallax) {
if (setting.availability != .lift)
- @compileError("CursorBlink, SelectionGlow, HoverGlow and Occlusion must use the lift capability (a pixel shell's)");
+ @compileError("CursorBlink, SelectionGlow, HoverGlow, Occlusion and Parallax must use the lift capability (a pixel shell's)");
} else if (setting.availability != null)
@compileError("unconditional toggles cannot carry a backend capability"),
else => if (setting.availability != null)
@@ -1699,6 +1704,7 @@ pub const Runtime = struct {
.selection_glow => "SelectionGlow on\n",
.hover_glow => "HoverGlow on\n",
.occlusion => "Occlusion on\n",
+ .parallax => "Parallax on\n",
},
.shell => "Shell requested (new panes) fish\n",
.tagline_size => "TaglineSize 73\n",
diff --git a/src/detached/wire.zig b/src/detached/wire.zig
index 3a914e42..6903a1e8 100644
--- a/src/detached/wire.zig
+++ b/src/detached/wire.zig
@@ -270,6 +270,7 @@ pub const Frame = struct {
region.notice = try r.getByte();
region.clip = try r.getU16();
region.lift = @bitCast(try r.getU32());
+ region.line = @bitCast(try r.getU32());
// A springy flavour overshoots, and a wind-up dips below.
if (!(region.lift >= -1 and region.lift <= 2)) return error.BadValue;
if (region.rect.x > f.cols or region.rect.w > f.cols - region.rect.x or
@@ -754,7 +755,7 @@ pub fn layersBound(layers: []const pardes.Layer, tags: []const pardes.Layer, reg
return n + regions.len * region_len + chrome_len;
}
-const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2 + 4;
+const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2 + 4 + 4;
/// The palette: each colour three bytes, a theme's optional ones a flag
/// before them.
const chrome_len = @typeInfo(pardes.Chrome).@"struct".fields.len * 4 + 8 * 4;
@@ -839,6 +840,7 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
try w.putByte(region.notice);
try w.putU16(region.clip);
try w.putU32(@bitCast(region.lift));
+ try w.putU32(@bitCast(region.line));
}
inline for (@typeInfo(pardes.Chrome).@"struct".fields) |field| {
const rgb = @field(chrome, field.name);
diff --git a/src/draw.zig b/src/draw.zig
index 027326fa..aef93f51 100644
--- a/src/draw.zig
+++ b/src/draw.zig
@@ -63,7 +63,8 @@ pub fn place(p: *Pardes, s: *Surface) void {
// A folded pane is its tag rows and nothing else.
if (pane.collapsed) continue;
const body_h = r.h -| pane.tag_rows;
- s.addRegion(.{ .kind = .body, .owner = owner, .serial = pane.serial, .active = active, .rect = .{ .x = r.x + config.GUTTER, .y = body_y, .w = r.w - config.GUTTER, .h = body_h } });
+ const line: i32 = if (pane.file) |f| @intCast(@min(f.scroll, std.math.maxInt(i32))) else if (pane.isTerminal()) panes.Terminal.gridOffset(pane) else 0;
+ s.addRegion(.{ .kind = .body, .owner = owner, .serial = pane.serial, .active = active, .line = line, .rect = .{ .x = r.x + config.GUTTER, .y = body_y, .w = r.w - config.GUTTER, .h = body_h } });
var rail: Region = .{ .kind = .rail, .owner = owner, .serial = pane.serial, .active = active, .rect = .{ .x = r.x, .y = body_y, .w = config.GUTTER, .h = body_h } };
// An image's rail has no thumb; a native PDF's is measured as its
// pages are placed (Pdf.draw); every other pane's is here.
@@ -472,7 +473,7 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface {
.rail_px = @intCast(@min(255, @as(u32, p.theme().rail_px orelse 12) * p.settings.grip_width / 100)),
.name_ink = chrome.tag_name_fg,
.active_name_ink = if (p.settings.focus_tint) chrome.tag_active_name_fg else chrome.tag_name_fg,
- .fx = @as(u8, @intFromBool(p.settings.selection_glow)) | @as(u8, @intFromBool(p.settings.hover_glow)) << 1 | @as(u8, @intFromBool(p.settings.occlusion)) << 2,
+ .fx = @as(u8, @intFromBool(p.settings.selection_glow)) | @as(u8, @intFromBool(p.settings.hover_glow)) << 1 | @as(u8, @intFromBool(p.settings.occlusion)) << 2 | @as(u8, @intFromBool(p.settings.parallax)) << 3,
};
if (p.theme().decor) |decor| {
s.chrome.decor_dots = decor.page_dots;
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
diff --git a/src/surface.zig b/src/surface.zig
index 02e4bb5d..d1ea78b1 100644
--- a/src/surface.zig
+++ b/src/surface.zig
@@ -370,6 +370,10 @@ pub const Region = struct {
/// focus (Lift, on a spring), a notice's while Lift is on. A pixel shell
/// casts a soft shadow from it; the tier it is drawn in never changes.
lift: f32 = 0,
+ /// A body's view: the first line it shows (a file's scroll, a
+ /// terminal's grid offset), for what a pixel shell moves with the text
+ /// (G13's parallax).
+ line: i32 = 0,
/// Cells the column-move rail takes off the right edge while it runs
/// there: layers and pixel shells stop short of them, the grid does not.
clip: u16 = 0,
@@ -451,7 +455,7 @@ pub const Chrome = struct {
/// A pixel shell's small effects: bit 0 a glow round the body's
/// selection (G9, SelectionGlow), bit 1 one under the word a look-hover
/// would open (G11, HoverGlow), bit 2 the bodies' edge shade (G12,
- /// Occlusion).
+ /// Occlusion), bit 3 the page pattern's parallax (G13, Parallax).
fx: u8 = 0,
};