summaryrefslogtreecommitdiff
path: root/src/layout.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 14:46:38 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit40c19fd4f52d20df94c2dfa2d68102b89bd9d3f1 (patch)
tree642150be8703f1d9a0766ff18516f06bfdc8801a /src/layout.zig
parent379f4fe55c7c9c7c8cc7b97dc921c83e2c24698b (diff)
downloadpardes-40c19fd4f52d20df94c2dfa2d68102b89bd9d3f1.tar.gz
pardes-40c19fd4f52d20df94c2dfa2d68102b89bd9d3f1.zip
Add optional compact tagline styling for source context
Diffstat (limited to 'src/layout.zig')
-rw-r--r--src/layout.zig103
1 files changed, 103 insertions, 0 deletions
diff --git a/src/layout.zig b/src/layout.zig
index 2df64594..1c43bbd4 100644
--- a/src/layout.zig
+++ b/src/layout.zig
@@ -29,6 +29,7 @@ pub const Presentation = struct {
acknowledged: bool = false,
pending: bool = false,
previous_cells: []pardes.Cell = &.{},
+ previous_body_layers: [MAX_PANES]pardes.BodyLayer = @splat(.{}),
previous_cols: u16 = 0,
previous_rows: u16 = 0,
previous_valid: bool = false,
@@ -40,6 +41,7 @@ pub const Presentation = struct {
pub fn deinit(self: *Presentation, gpa: std.mem.Allocator) void {
if (self.previous_cells.len > 0) gpa.free(self.previous_cells);
+ for (&self.previous_body_layers) |*layer| gpa.free(layer.cells);
if (self.diffs.len > 0) gpa.free(self.diffs);
}
@@ -299,6 +301,17 @@ pub const Presentation = struct {
if (self.previous_cells.len > 0) gpa.free(self.previous_cells);
self.previous_cells = next;
}
+ for (&self.previous_body_layers, surface.bodyLayers()) |*old, *current| {
+ const count = if (current.rows > 0) current.cells.len else 0;
+ const storage = if (old.cells.len == count) old.cells else gpa.realloc(old.cells, count) catch {
+ self.previous_valid = false;
+ self.previous_layout = @splat(null);
+ return;
+ };
+ if (count > 0) @memcpy(storage, current.cells);
+ old.* = current.*;
+ old.cells = storage;
+ }
@memcpy(self.previous_cells, cells);
self.previous_cols = surface.cols;
self.previous_rows = surface.rows;
@@ -315,6 +328,55 @@ pub const Presentation = struct {
return self.diffs[@as(usize, row) * cols + col];
}
+ pub const Point = struct { x: f32, y: f32 };
+
+ /// Coordinates stay fractional until the host resolves its body font grid.
+ pub fn pointerFractional(self: *const Presentation, cols: u16, rows: u16, x: f32, y: f32) ?Point {
+ if (self.pending or !std.math.isFinite(x) or !std.math.isFinite(y) or
+ x < 0 or y < 0 or x >= @as(f32, @floatFromInt(cols)) or y >= @as(f32, @floatFromInt(rows))) return null;
+ var closing = self.shown_closing_len;
+ while (closing > 0) {
+ closing -= 1;
+ const track = self.shown_closing[closing];
+ if (track.active() and boxContainsPoint(track.contentBox(), x, y) and
+ boxContainsPoint(track.presented(), x, y)) return null;
+ }
+ for ([_]Phase{ .opening, .moving }) |phase| {
+ var index = self.shown_tracks.len;
+ while (index > 0) {
+ index -= 1;
+ const track = self.shown_tracks[index] orelse continue;
+ if (!track.active() or track.phase != phase) continue;
+ switch (track.effect) {
+ .slide, .zoom, .vertical => {
+ const target = track.contentBox();
+ if (track.effect == .vertical and !boxContainsPoint(target, x, y)) continue;
+ const shown = track.presented();
+ if (!boxContainsPoint(shown, x, y)) {
+ if (track.effect == .vertical) return null;
+ continue;
+ }
+ return .{
+ .x = target.x + (x - shown.x) / shown.w * target.w,
+ .y = target.y + (y - shown.y) / shown.h * target.h,
+ };
+ },
+ else => {
+ if (!boxContainsPoint(track.to, x, y)) continue;
+ _ = self.pointer(cols, rows, @intFromFloat(@floor(x)), @intFromFloat(@floor(y))) orelse return null;
+ return .{ .x = x, .y = y };
+ },
+ }
+ }
+ }
+ _ = self.pointer(cols, rows, @intFromFloat(@floor(x)), @intFromFloat(@floor(y))) orelse return null;
+ return .{ .x = x, .y = y };
+ }
+
+ fn boxContainsPoint(box: Box, x: f32, y: f32) bool {
+ return box.w > 0 and box.h > 0 and x >= box.x and y >= box.y and x < box.x + box.w and y < box.y + box.h;
+ }
+
pub fn pointer(self: *const Presentation, cols: u16, rows: u16, col: u16, row: u16) ?CellPosition {
if (self.pending) return null;
var closing = self.shown_closing_len;
@@ -487,6 +549,7 @@ pub const Presentation = struct {
}
if (self.diff_state == .ready) {
s.previous_cells = self.previous_cells;
+ s.previous_body_layers = &self.previous_body_layers;
s.cell_diffs = self.diffs;
}
s.npanel_tracks = paintOrder(
@@ -2017,3 +2080,43 @@ pub const Animation = struct {
try std.testing.expectEqual(third, tween.displayed);
}
};
+
+test "fractional panel inverse preserves compact header rows through slide and zoom" {
+ const layer: pardes.BodyLayer = .{ .serial = 7, .viewport = .{ .x = 4, .y = 3, .w = 30, .h = 10 }, .cols = 30, .rows = 15, .context_rows = 4 };
+ for ([_]Transition{ .slide, .zoom, .vertical }) |effect| {
+ var presentation: Presentation = .{};
+ const track: Track = .{ .serial = 7, .effect = effect, .frame = 4, .frame_count = 10, .from = .{ .x = 2, .y = 2, .w = 20, .h = 8 }, .to = .{ .x = 3, .y = 2, .w = 32, .h = 12 } };
+ presentation.shown_tracks[0] = track;
+ const shown = track.presented();
+ for (0..4) |index| {
+ const row: u16 = @intCast(index);
+ const source_x: f32 = 5.3;
+ const source_y = (layer.rowTop(row, 20, 10) + 5) / 20;
+ const x = shown.x + (source_x - track.to.x) / track.to.w * shown.w;
+ const y = shown.y + (source_y - track.to.y) / track.to.h * shown.h;
+ const point = presentation.pointerFractional(80, 30, x, y).?;
+ try std.testing.expectApproxEqAbs(source_x, point.x, 0.0001);
+ try std.testing.expectApproxEqAbs(source_y, point.y, 0.0001);
+ const body_hit = layer.hitAt(point.x * 10, point.y * 20, 10, 20, 8, 10).?;
+ try std.testing.expectEqual(row, body_hit.row);
+ }
+ }
+}
+
+test "presentation snapshots own compact body rows across later edits" {
+ var state: Presentation = .{};
+ defer state.deinit(std.testing.allocator);
+ var cells: [1]pardes.Cell = .{.{}};
+ var body: [2]pardes.Cell = .{ .{}, .{} };
+ body[0].text[0] = 'A';
+ var surface: pardes.Surface = .{ .cols = 1, .rows = 1, .cells = &cells };
+ surface.body_layers[0] = .{ .serial = 9, .cols = 1, .rows = 2, .context_rows = 1, .cells = &body };
+ state.capturePrevious(std.testing.allocator, &surface);
+ try std.testing.expect(state.previous_valid);
+ body[0].text[0] = 'B';
+ surface.body_layers[0].context_rows = 0;
+ try std.testing.expectEqual(@as(u8, 'A'), state.previous_body_layers[0].cells[0].text[0]);
+ try std.testing.expectEqual(@as(u16, 1), state.previous_body_layers[0].context_rows);
+ state.capturePrevious(std.testing.allocator, &surface);
+ try std.testing.expectEqual(@as(u8, 'B'), state.previous_body_layers[0].cells[0].text[0]);
+}