terminal/c: resolve relative placement viewport positions

This commit is contained in:
Mitchell Hashimoto
2026-08-20 14:54:04 -07:00
parent cd5f9eef0a
commit f5b3efe452

View File

@@ -10,6 +10,7 @@ const grid_ref = @import("grid_ref.zig");
const selection_c = @import("selection.zig");
const terminal_c = @import("terminal.zig");
const Terminal = @import("../Terminal.zig");
const PageList = @import("../PageList.zig");
const Result = @import("result.zig").Result;
/// C: GhosttyKittyGraphics
@@ -600,10 +601,35 @@ fn computeViewportPos(
) struct { col: i32, row: i32, visible: bool } {
// Virtual placements use unicode placeholders and don't have a
// screen position — they are rendered inline by the text layout.
const pin = switch (p.location) {
.pin => |pin| pin,
.virtual, .relative => return .{ .col = 0, .row = 0, .visible = false },
// Relative placements are anchored at the root of their parent
// chain, offset by the accumulated chain offsets. A chain rooted
// at a virtual placement has no resolvable position here: its
// origin is the parent's placeholder cells, which only a renderer
// scanning the screen can locate.
const origin: struct {
pin: *const PageList.Pin,
col_offset: i32 = 0,
row_offset: i32 = 0,
} = switch (p.location) {
.pin => |pin| .{ .pin = pin },
.virtual => return .{ .col = 0, .row = 0, .visible = false },
.relative => |rel| origin: {
const storage = &t.screens.active.kitty_images;
const chain = storage.resolveChain(rel) orelse
return .{ .col = 0, .row = 0, .visible = false };
switch (chain.root.location) {
.pin => |root_pin| break :origin .{
.pin = root_pin,
.col_offset = chain.horizontal_offset,
.row_offset = chain.vertical_offset,
},
.virtual => return .{ .col = 0, .row = 0, .visible = false },
.relative => unreachable, // resolveChain roots are never relative
}
},
};
const pin = origin.pin;
if (pin.garbage) return .{ .col = 0, .row = 0, .visible = false };
// Convert both the placement's pin and the viewport's top-left
@@ -618,17 +644,23 @@ fn computeViewportPos(
// Subtracting viewport origin from the pin gives us viewport-
// relative coordinates. The row can be negative when the
// placement has partially scrolled above the viewport.
const vp_row: i32 = @as(i32, @intCast(pin_screen.screen.y)) -
@as(i32, @intCast(vp_screen.screen.y));
const vp_col: i32 = @intCast(pin_screen.screen.x);
// placement has partially scrolled above the viewport, and both
// can be negative for relative placements with negative offsets.
const vp_row: i32 = (@as(i32, @intCast(pin_screen.screen.y)) -
@as(i32, @intCast(vp_screen.screen.y))) +| origin.row_offset;
const vp_col: i32 = @as(i32, @intCast(pin_screen.screen.x)) +|
origin.col_offset;
// A placement is invisible if its bottom edge (row + height)
// is above the viewport, or its top edge is at or below the
// viewport's last row.
// A placement is invisible if its bottom edge (row + height) is
// above the viewport, or its top edge is at or below the viewport's
// last row. The same applies horizontally: a pin's column is always
// in bounds, but a relative placement's offsets can push it fully
// off either side.
const grid_size = p.gridSize(image.*, t);
const bottom_row = @as(i64, vp_row) + @as(i64, grid_size.rows);
const visible = bottom_row > 0 and vp_row < @as(i32, t.rows);
const right_col = @as(i64, vp_col) + @as(i64, grid_size.cols);
const visible = bottom_row > 0 and vp_row < @as(i32, t.rows) and
right_col > 0 and vp_col < @as(i32, t.cols);
return .{ .col = vp_col, .row = vp_row, .visible = visible };
}