mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
libghostty: add convenience accessors for kitty graphics (#12229)
Add three sized structs that let callers fetch all image, placement, or rendering metadata in a single call instead of many individual queries. This is an optimization for environments with high per-call overhead such as FFI or Cgo. GhosttyKittyGraphicsImageInfo is returned via image_get() with the new GHOSTTY_KITTY_IMAGE_DATA_INFO data kind. It bundles id, number, width, height, format, compression, data pointer, and data length. GhosttyKittyGraphicsPlacementInfo is returned via placement_get() with the new GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO data kind. It bundles image id, placement id, virtual flag, offsets, source rect, columns, rows, and z-index. GhosttyKittyGraphicsPlacementRenderInfo is returned by the new ghostty_kitty_graphics_placement_render_info() function, which combines pixel size, grid size, viewport position, and resolved source rectangle. This one requires image and terminal handles so it does not fit the existing _get() pattern and is a dedicated function. All three use the sized-struct ABI pattern with GHOSTTY_INIT_SIZED for forward compatibility.
This commit is contained in:
@@ -214,6 +214,18 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
* Output type: int32_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12,
|
||||
|
||||
/**
|
||||
* All placement metadata as a sized struct.
|
||||
*
|
||||
* This is an optimization over querying each field individually,
|
||||
* particularly useful in environments with high per-call overhead
|
||||
* such as FFI or Cgo.
|
||||
*
|
||||
* Output type: GhosttyKittyGraphicsPlacementInfo *
|
||||
* (initialize with GHOSTTY_INIT_SIZED)
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO = 13,
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyKittyGraphicsPlacementData;
|
||||
|
||||
@@ -341,9 +353,141 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
* Output type: size_t *
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8,
|
||||
|
||||
/**
|
||||
* All image metadata as a sized struct.
|
||||
*
|
||||
* This is an optimization over querying each field individually,
|
||||
* particularly useful in environments with high per-call overhead
|
||||
* such as FFI or Cgo.
|
||||
*
|
||||
* Output type: GhosttyKittyGraphicsImageInfo *
|
||||
* (initialize with GHOSTTY_INIT_SIZED)
|
||||
*/
|
||||
GHOSTTY_KITTY_IMAGE_DATA_INFO = 9,
|
||||
GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyKittyGraphicsImageData;
|
||||
|
||||
/**
|
||||
* All image metadata in a single sized struct.
|
||||
*
|
||||
* Returned by ghostty_kitty_graphics_image_get() with
|
||||
* GHOSTTY_KITTY_IMAGE_DATA_INFO. This is an optimization over
|
||||
* querying each field individually, particularly useful in
|
||||
* environments with high per-call overhead such as FFI or Cgo.
|
||||
*
|
||||
* This struct uses the sized-struct ABI pattern. Initialize with
|
||||
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsImageInfo).
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsImageInfo). */
|
||||
size_t size;
|
||||
/** The image ID. */
|
||||
uint32_t id;
|
||||
/** The image number. */
|
||||
uint32_t number;
|
||||
/** Image width in pixels. */
|
||||
uint32_t width;
|
||||
/** Image height in pixels. */
|
||||
uint32_t height;
|
||||
/** Pixel format of the image. */
|
||||
GhosttyKittyImageFormat format;
|
||||
/** Compression of the image. */
|
||||
GhosttyKittyImageCompression compression;
|
||||
/** Borrowed pointer to the raw pixel data. */
|
||||
const uint8_t* data_ptr;
|
||||
/** Length of the raw pixel data in bytes. */
|
||||
size_t data_len;
|
||||
} GhosttyKittyGraphicsImageInfo;
|
||||
|
||||
/**
|
||||
* All placement metadata in a single sized struct.
|
||||
*
|
||||
* Returned by ghostty_kitty_graphics_placement_get() with
|
||||
* GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO. This is an optimization
|
||||
* over querying each field individually, particularly useful in
|
||||
* environments with high per-call overhead such as FFI or Cgo.
|
||||
*
|
||||
* This struct uses the sized-struct ABI pattern. Initialize with
|
||||
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementInfo).
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsPlacementInfo). */
|
||||
size_t size;
|
||||
/** The image ID this placement belongs to. */
|
||||
uint32_t image_id;
|
||||
/** The placement ID. */
|
||||
uint32_t placement_id;
|
||||
/** Whether this is a virtual placement (unicode placeholder). */
|
||||
bool is_virtual;
|
||||
/** Pixel offset from the left edge of the cell. */
|
||||
uint32_t x_offset;
|
||||
/** Pixel offset from the top edge of the cell. */
|
||||
uint32_t y_offset;
|
||||
/** Source rectangle x origin in pixels. */
|
||||
uint32_t source_x;
|
||||
/** Source rectangle y origin in pixels. */
|
||||
uint32_t source_y;
|
||||
/** Source rectangle width in pixels (0 = full image width). */
|
||||
uint32_t source_width;
|
||||
/** Source rectangle height in pixels (0 = full image height). */
|
||||
uint32_t source_height;
|
||||
/** Number of columns this placement occupies. */
|
||||
uint32_t columns;
|
||||
/** Number of rows this placement occupies. */
|
||||
uint32_t rows;
|
||||
/** Z-index for this placement. */
|
||||
int32_t z;
|
||||
} GhosttyKittyGraphicsPlacementInfo;
|
||||
|
||||
/**
|
||||
* Combined rendering geometry for a placement in a single sized struct.
|
||||
*
|
||||
* Combines the results of ghostty_kitty_graphics_placement_pixel_size(),
|
||||
* ghostty_kitty_graphics_placement_grid_size(),
|
||||
* ghostty_kitty_graphics_placement_viewport_pos(), and
|
||||
* ghostty_kitty_graphics_placement_source_rect() into one call. This is
|
||||
* an optimization over calling those four functions individually,
|
||||
* particularly useful in environments with high per-call overhead such
|
||||
* as FFI or Cgo.
|
||||
*
|
||||
* This struct uses the sized-struct ABI pattern. Initialize with
|
||||
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo) before calling
|
||||
* ghostty_kitty_graphics_placement_render_info().
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsPlacementRenderInfo). */
|
||||
size_t size;
|
||||
/** Rendered width in pixels. */
|
||||
uint32_t pixel_width;
|
||||
/** Rendered height in pixels. */
|
||||
uint32_t pixel_height;
|
||||
/** Number of grid columns the placement occupies. */
|
||||
uint32_t grid_cols;
|
||||
/** Number of grid rows the placement occupies. */
|
||||
uint32_t grid_rows;
|
||||
/** Viewport-relative column (may be negative for partially visible placements). */
|
||||
int32_t viewport_col;
|
||||
/** Viewport-relative row (may be negative for partially visible placements). */
|
||||
int32_t viewport_row;
|
||||
/** False when the placement is fully off-screen or virtual. */
|
||||
bool viewport_visible;
|
||||
/** Resolved source rectangle x origin in pixels. */
|
||||
uint32_t source_x;
|
||||
/** Resolved source rectangle y origin in pixels. */
|
||||
uint32_t source_y;
|
||||
/** Resolved source rectangle width in pixels. */
|
||||
uint32_t source_width;
|
||||
/** Resolved source rectangle height in pixels. */
|
||||
uint32_t source_height;
|
||||
} GhosttyKittyGraphicsPlacementRenderInfo;
|
||||
|
||||
/**
|
||||
* Get data from a kitty graphics storage instance.
|
||||
*
|
||||
@@ -627,6 +771,31 @@ GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_source_rect(
|
||||
uint32_t* out_width,
|
||||
uint32_t* out_height);
|
||||
|
||||
/**
|
||||
* Get all rendering geometry for a placement in a single call.
|
||||
*
|
||||
* Combines pixel size, grid size, viewport position, and source
|
||||
* rectangle into one struct. Initialize with
|
||||
* GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo).
|
||||
*
|
||||
* When viewport_visible is false, the placement is fully off-screen
|
||||
* or is a virtual placement; viewport_col and viewport_row may
|
||||
* contain meaningless values in that case.
|
||||
*
|
||||
* @param iterator The iterator positioned on a placement
|
||||
* @param image The image handle for this placement's image
|
||||
* @param terminal The terminal handle
|
||||
* @param[out] out_info Pointer to receive the rendering geometry
|
||||
* @return GHOSTTY_SUCCESS on success
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_render_info(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator,
|
||||
GhosttyKittyGraphicsImage image,
|
||||
GhosttyTerminal terminal,
|
||||
GhosttyKittyGraphicsPlacementRenderInfo* out_info);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -248,6 +248,7 @@ comptime {
|
||||
@export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" });
|
||||
@export(&c.kitty_graphics_placement_viewport_pos, .{ .name = "ghostty_kitty_graphics_placement_viewport_pos" });
|
||||
@export(&c.kitty_graphics_placement_source_rect, .{ .name = "ghostty_kitty_graphics_placement_source_rect" });
|
||||
@export(&c.kitty_graphics_placement_render_info, .{ .name = "ghostty_kitty_graphics_placement_render_info" });
|
||||
@export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" });
|
||||
@export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" });
|
||||
@export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" });
|
||||
|
||||
@@ -9,6 +9,7 @@ const Image = @import("../kitty/graphics_image.zig").Image;
|
||||
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 Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttyKittyGraphics
|
||||
@@ -75,6 +76,7 @@ pub const PlacementData = enum(c_int) {
|
||||
columns = 10,
|
||||
rows = 11,
|
||||
z = 12,
|
||||
info = 13,
|
||||
|
||||
pub fn OutType(comptime self: PlacementData) type {
|
||||
return switch (self) {
|
||||
@@ -91,6 +93,7 @@ pub const PlacementData = enum(c_int) {
|
||||
.rows,
|
||||
=> u32,
|
||||
.z => i32,
|
||||
.info => PlacementInfo,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -177,6 +180,7 @@ pub const ImageData = enum(c_int) {
|
||||
compression = 6,
|
||||
data_ptr = 7,
|
||||
data_len = 8,
|
||||
info = 9,
|
||||
|
||||
pub fn OutType(comptime self: ImageData) type {
|
||||
return switch (self) {
|
||||
@@ -186,6 +190,7 @@ pub const ImageData = enum(c_int) {
|
||||
.compression => ImageCompression,
|
||||
.data_ptr => [*]const u8,
|
||||
.data_len => usize,
|
||||
.info => ImageInfo,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -234,6 +239,17 @@ fn imageGetTyped(
|
||||
.compression => out.* = image.compression,
|
||||
.data_ptr => out.* = image.data.ptr,
|
||||
.data_len => out.* = image.data.len,
|
||||
.info => {
|
||||
if (out.size < @sizeOf(ImageInfo)) return .invalid_value;
|
||||
out.id = image.id;
|
||||
out.number = image.number;
|
||||
out.width = image.width;
|
||||
out.height = image.height;
|
||||
out.format = image.format;
|
||||
out.compression = image.compression;
|
||||
out.data_ptr = image.data.ptr;
|
||||
out.data_len = image.data.len;
|
||||
},
|
||||
}
|
||||
|
||||
return .success;
|
||||
@@ -335,20 +351,38 @@ fn placementGetTyped(
|
||||
const iter = iter_ orelse return .invalid_value;
|
||||
const entry = iter.entry orelse return .invalid_value;
|
||||
|
||||
const key = entry.key_ptr;
|
||||
const val = entry.value_ptr;
|
||||
|
||||
switch (data) {
|
||||
.invalid => return .invalid_value,
|
||||
.image_id => out.* = entry.key_ptr.image_id,
|
||||
.placement_id => out.* = entry.key_ptr.placement_id.id,
|
||||
.is_virtual => out.* = entry.value_ptr.location == .virtual,
|
||||
.x_offset => out.* = entry.value_ptr.x_offset,
|
||||
.y_offset => out.* = entry.value_ptr.y_offset,
|
||||
.source_x => out.* = entry.value_ptr.source_x,
|
||||
.source_y => out.* = entry.value_ptr.source_y,
|
||||
.source_width => out.* = entry.value_ptr.source_width,
|
||||
.source_height => out.* = entry.value_ptr.source_height,
|
||||
.columns => out.* = entry.value_ptr.columns,
|
||||
.rows => out.* = entry.value_ptr.rows,
|
||||
.z => out.* = entry.value_ptr.z,
|
||||
.image_id => out.* = key.image_id,
|
||||
.placement_id => out.* = key.placement_id.id,
|
||||
.is_virtual => out.* = val.location == .virtual,
|
||||
.x_offset => out.* = val.x_offset,
|
||||
.y_offset => out.* = val.y_offset,
|
||||
.source_x => out.* = val.source_x,
|
||||
.source_y => out.* = val.source_y,
|
||||
.source_width => out.* = val.source_width,
|
||||
.source_height => out.* = val.source_height,
|
||||
.columns => out.* = val.columns,
|
||||
.rows => out.* = val.rows,
|
||||
.z => out.* = val.z,
|
||||
.info => {
|
||||
if (out.size < @sizeOf(PlacementInfo)) return .invalid_value;
|
||||
out.image_id = key.image_id;
|
||||
out.placement_id = key.placement_id.id;
|
||||
out.is_virtual = val.location == .virtual;
|
||||
out.x_offset = val.x_offset;
|
||||
out.y_offset = val.y_offset;
|
||||
out.source_x = val.source_x;
|
||||
out.source_y = val.source_y;
|
||||
out.source_width = val.source_width;
|
||||
out.source_height = val.source_height;
|
||||
out.columns = val.columns;
|
||||
out.rows = val.rows;
|
||||
out.z = val.z;
|
||||
},
|
||||
}
|
||||
|
||||
return .success;
|
||||
@@ -435,35 +469,12 @@ pub fn placement_viewport_pos(
|
||||
const image = image_ orelse return .invalid_value;
|
||||
const iter = iter_ orelse return .invalid_value;
|
||||
const entry = iter.entry orelse return .invalid_value;
|
||||
const pin = switch (entry.value_ptr.location) {
|
||||
.pin => |p| p,
|
||||
.virtual => return .no_value,
|
||||
};
|
||||
|
||||
const pages = &wrapper.terminal.screens.active.pages;
|
||||
const vp = computeViewportPos(entry.value_ptr, image, wrapper.terminal);
|
||||
if (!vp.visible) return .no_value;
|
||||
|
||||
// Get screen-absolute coordinates for both the pin and the
|
||||
// viewport origin, then subtract to get viewport-relative
|
||||
// coordinates that can be negative for partially visible
|
||||
// placements above the viewport.
|
||||
const pin_screen = pages.pointFromPin(.screen, pin.*) orelse return .no_value;
|
||||
const vp_tl = pages.getTopLeft(.viewport);
|
||||
const vp_screen = pages.pointFromPin(.screen, vp_tl) orelse return .no_value;
|
||||
|
||||
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);
|
||||
|
||||
// Check if the placement is fully off-screen. A placement is
|
||||
// invisible if its bottom edge is above the viewport or its
|
||||
// top edge is at or below the viewport's last row.
|
||||
const grid_size = entry.value_ptr.gridSize(image.*, wrapper.terminal);
|
||||
const rows_i32: i32 = @intCast(grid_size.rows);
|
||||
const term_rows: i32 = @intCast(wrapper.terminal.rows);
|
||||
if (vp_row + rows_i32 <= 0 or vp_row >= term_rows) return .no_value;
|
||||
|
||||
out_col.* = vp_col;
|
||||
out_row.* = vp_row;
|
||||
out_col.* = vp.col;
|
||||
out_row.* = vp.row;
|
||||
|
||||
return .success;
|
||||
}
|
||||
@@ -497,6 +508,144 @@ pub fn placement_source_rect(
|
||||
return .success;
|
||||
}
|
||||
|
||||
/// C: GhosttyKittyGraphicsImageInfo
|
||||
pub const ImageInfo = extern struct {
|
||||
size: usize = @sizeOf(ImageInfo),
|
||||
id: u32 = 0,
|
||||
number: u32 = 0,
|
||||
width: u32 = 0,
|
||||
height: u32 = 0,
|
||||
format: ImageFormat = .rgb,
|
||||
compression: ImageCompression = .none,
|
||||
data_ptr: [*]const u8 = @as([*]const u8, @ptrFromInt(1)),
|
||||
data_len: usize = 0,
|
||||
};
|
||||
|
||||
/// C: GhosttyKittyGraphicsPlacementInfo
|
||||
pub const PlacementInfo = extern struct {
|
||||
size: usize = @sizeOf(PlacementInfo),
|
||||
image_id: u32 = 0,
|
||||
placement_id: u32 = 0,
|
||||
is_virtual: bool = false,
|
||||
x_offset: u32 = 0,
|
||||
y_offset: u32 = 0,
|
||||
source_x: u32 = 0,
|
||||
source_y: u32 = 0,
|
||||
source_width: u32 = 0,
|
||||
source_height: u32 = 0,
|
||||
columns: u32 = 0,
|
||||
rows: u32 = 0,
|
||||
z: i32 = 0,
|
||||
};
|
||||
|
||||
/// C: GhosttyKittyGraphicsPlacementRenderInfo
|
||||
pub const PlacementRenderInfo = extern struct {
|
||||
size: usize = @sizeOf(PlacementRenderInfo),
|
||||
pixel_width: u32 = 0,
|
||||
pixel_height: u32 = 0,
|
||||
grid_cols: u32 = 0,
|
||||
grid_rows: u32 = 0,
|
||||
viewport_col: i32 = 0,
|
||||
viewport_row: i32 = 0,
|
||||
viewport_visible: bool = false,
|
||||
source_x: u32 = 0,
|
||||
source_y: u32 = 0,
|
||||
source_width: u32 = 0,
|
||||
source_height: u32 = 0,
|
||||
};
|
||||
|
||||
pub fn placement_render_info(
|
||||
iter_: PlacementIterator,
|
||||
image_: ImageHandle,
|
||||
terminal_: terminal_c.Terminal,
|
||||
out_: ?*PlacementRenderInfo,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
|
||||
const wrapper = terminal_ orelse return .invalid_value;
|
||||
const image = image_ orelse return .invalid_value;
|
||||
const iter = iter_ orelse return .invalid_value;
|
||||
const entry = iter.entry orelse return .invalid_value;
|
||||
const out = out_ orelse return .invalid_value;
|
||||
if (out.size < @sizeOf(PlacementRenderInfo)) return .invalid_value;
|
||||
|
||||
const p = entry.value_ptr;
|
||||
|
||||
const ps = p.pixelSize(image.*, wrapper.terminal);
|
||||
out.pixel_width = ps.width;
|
||||
out.pixel_height = ps.height;
|
||||
|
||||
const gs = p.gridSize(image.*, wrapper.terminal);
|
||||
out.grid_cols = gs.cols;
|
||||
out.grid_rows = gs.rows;
|
||||
|
||||
const vp = computeViewportPos(p, image, wrapper.terminal);
|
||||
out.viewport_col = vp.col;
|
||||
out.viewport_row = vp.row;
|
||||
out.viewport_visible = vp.visible;
|
||||
|
||||
const x = @min(p.source_x, image.width);
|
||||
const y = @min(p.source_y, image.height);
|
||||
out.source_x = x;
|
||||
out.source_y = y;
|
||||
out.source_width = @min(if (p.source_width > 0) p.source_width else image.width, image.width - x);
|
||||
out.source_height = @min(if (p.source_height > 0) p.source_height else image.height, image.height - y);
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
/// Compute viewport-relative position of a placement.
|
||||
///
|
||||
/// Converts the placement's internal pin to viewport-relative column
|
||||
/// and row coordinates by getting screen-absolute coordinates for
|
||||
/// both the pin and the viewport origin, then subtracting to get
|
||||
/// viewport-relative coordinates. The row value can be negative when
|
||||
/// the placement's origin has scrolled above the top of the viewport.
|
||||
///
|
||||
/// A placement is considered not visible if it is a virtual (unicode
|
||||
/// placeholder) placement, or if it is fully off-screen (its bottom
|
||||
/// edge is above the viewport or its top edge is at or below the
|
||||
/// viewport's last row).
|
||||
fn computeViewportPos(
|
||||
p: *const kitty_storage.ImageStorage.Placement,
|
||||
image: *const Image,
|
||||
t: *Terminal,
|
||||
) 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 => return .{ .col = 0, .row = 0, .visible = false },
|
||||
};
|
||||
|
||||
// Convert both the placement's pin and the viewport's top-left
|
||||
// corner to screen-absolute coordinates so we can subtract them
|
||||
// to get viewport-relative coordinates.
|
||||
const pages = &t.screens.active.pages;
|
||||
const pin_screen = pages.pointFromPin(.screen, pin.*) orelse
|
||||
return .{ .col = 0, .row = 0, .visible = false };
|
||||
const vp_tl = pages.getTopLeft(.viewport);
|
||||
const vp_screen = pages.pointFromPin(.screen, vp_tl) orelse
|
||||
return .{ .col = 0, .row = 0, .visible = false };
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.
|
||||
const grid_size = p.gridSize(image.*, t);
|
||||
const rows_i32: i32 = @intCast(grid_size.rows);
|
||||
const term_rows: i32 = @intCast(t.rows);
|
||||
const visible = vp_row + rows_i32 > 0 and vp_row < term_rows;
|
||||
|
||||
return .{ .col = vp_col, .row = vp_row, .visible = visible };
|
||||
}
|
||||
|
||||
test "placement_iterator new/free" {
|
||||
var iter: PlacementIterator = null;
|
||||
try testing.expectEqual(Result.success, placement_iterator_new(
|
||||
@@ -1373,3 +1522,168 @@ test "image_get on null returns invalid_value" {
|
||||
var id: u32 = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, image_get(null, .id, @ptrCast(&id)));
|
||||
}
|
||||
|
||||
test "image_get info returns all fields" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
|
||||
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\x1b\\";
|
||||
terminal_c.vt_write(t, cmd.ptr, cmd.len);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
|
||||
const img = image_get_handle(graphics, 1);
|
||||
try testing.expect(img != null);
|
||||
|
||||
var info: ImageInfo = .{};
|
||||
try testing.expectEqual(Result.success, image_get(img, .info, @ptrCast(&info)));
|
||||
try testing.expectEqual(1, info.id);
|
||||
try testing.expectEqual(1, info.width);
|
||||
try testing.expectEqual(2, info.height);
|
||||
try testing.expectEqual(ImageFormat.rgb, info.format);
|
||||
try testing.expectEqual(ImageCompression.none, info.compression);
|
||||
try testing.expectEqual(6, info.data_len);
|
||||
}
|
||||
|
||||
test "image_get info null returns invalid_value" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var info: ImageInfo = .{};
|
||||
try testing.expectEqual(Result.invalid_value, image_get(null, .info, @ptrCast(&info)));
|
||||
}
|
||||
|
||||
test "placement_get info returns all fields" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
|
||||
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\";
|
||||
terminal_c.vt_write(t, cmd.ptr, cmd.len);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
|
||||
|
||||
var iter: PlacementIterator = null;
|
||||
try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter));
|
||||
defer placement_iterator_free(iter);
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
try testing.expect(placement_iterator_next(iter));
|
||||
|
||||
var info: PlacementInfo = .{};
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .info, @ptrCast(&info)));
|
||||
try testing.expectEqual(1, info.image_id);
|
||||
try testing.expectEqual(1, info.placement_id);
|
||||
try testing.expect(!info.is_virtual);
|
||||
try testing.expectEqual(10, info.columns);
|
||||
try testing.expectEqual(1, info.rows);
|
||||
}
|
||||
|
||||
test "placement_get info null returns invalid_value" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var info: PlacementInfo = .{};
|
||||
try testing.expectEqual(Result.invalid_value, placement_get(null, .info, @ptrCast(&info)));
|
||||
}
|
||||
|
||||
test "placement_render_info returns all fields" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
|
||||
const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\";
|
||||
terminal_c.vt_write(t, cmd.ptr, cmd.len);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
|
||||
const img = image_get_handle(graphics, 1);
|
||||
try testing.expect(img != null);
|
||||
|
||||
var iter: PlacementIterator = null;
|
||||
try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter));
|
||||
defer placement_iterator_free(iter);
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
try testing.expect(placement_iterator_next(iter));
|
||||
|
||||
var ri: PlacementRenderInfo = .{};
|
||||
try testing.expectEqual(Result.success, placement_render_info(iter, img, t, &ri));
|
||||
try testing.expect(ri.viewport_visible);
|
||||
try testing.expectEqual(0, ri.viewport_col);
|
||||
try testing.expectEqual(0, ri.viewport_row);
|
||||
try testing.expectEqual(10, ri.grid_cols);
|
||||
try testing.expectEqual(1, ri.grid_rows);
|
||||
try testing.expectEqual(0, ri.source_x);
|
||||
try testing.expectEqual(0, ri.source_y);
|
||||
try testing.expectEqual(1, ri.source_width);
|
||||
try testing.expectEqual(2, ri.source_height);
|
||||
}
|
||||
|
||||
test "placement_render_info off-screen sets viewport_visible false" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 5, .max_scrollback = 100 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
|
||||
|
||||
const transmit = "\x1b_Ga=t,t=d,f=24,i=1,s=1,v=2;////////\x1b\\";
|
||||
const display = "\x1b_Ga=p,i=1,p=1,c=1,r=1;\x1b\\";
|
||||
terminal_c.vt_write(t, transmit.ptr, transmit.len);
|
||||
terminal_c.vt_write(t, display.ptr, display.len);
|
||||
|
||||
// Scroll the image completely off-screen.
|
||||
const scroll = "\n\n\n\n\n\n\n\n\n\n";
|
||||
terminal_c.vt_write(t, scroll.ptr, scroll.len);
|
||||
|
||||
var graphics: KittyGraphics = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics)));
|
||||
const img = image_get_handle(graphics, 1);
|
||||
try testing.expect(img != null);
|
||||
|
||||
var iter: PlacementIterator = null;
|
||||
try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter));
|
||||
defer placement_iterator_free(iter);
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
try testing.expect(placement_iterator_next(iter));
|
||||
|
||||
var ri: PlacementRenderInfo = .{};
|
||||
try testing.expectEqual(Result.success, placement_render_info(iter, img, t, &ri));
|
||||
try testing.expect(!ri.viewport_visible);
|
||||
// Other fields should still be populated.
|
||||
try testing.expectEqual(1, ri.grid_cols);
|
||||
try testing.expectEqual(1, ri.grid_rows);
|
||||
try testing.expectEqual(1, ri.source_width);
|
||||
try testing.expectEqual(2, ri.source_height);
|
||||
}
|
||||
|
||||
test "placement_render_info null returns invalid_value" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var ri: PlacementRenderInfo = .{};
|
||||
try testing.expectEqual(Result.invalid_value, placement_render_info(null, null, null, &ri));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub const kitty_graphics_placement_pixel_size = kitty_graphics.placement_pixel_s
|
||||
pub const kitty_graphics_placement_grid_size = kitty_graphics.placement_grid_size;
|
||||
pub const kitty_graphics_placement_viewport_pos = kitty_graphics.placement_viewport_pos;
|
||||
pub const kitty_graphics_placement_source_rect = kitty_graphics.placement_source_rect;
|
||||
pub const kitty_graphics_placement_render_info = kitty_graphics.placement_render_info;
|
||||
pub const types = @import("types.zig");
|
||||
pub const modes = @import("modes.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
|
||||
Reference in New Issue
Block a user