diff options
Diffstat (limited to 'src/layout.zig')
| -rw-r--r-- | src/layout.zig | 103 |
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]); +} |
