mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
libghostty: add z-layer filtered placement iterator
Add a placement_iterator_set function that configures iterator properties via an enum, following the same pattern as other set functions in the C API (e.g. render_state_set). The first settable option is a z-layer filter. The GhosttyKittyPlacementLayer enum classifies placements into three layers based on kitty protocol z-index conventions: below background (z < INT32_MIN/2), below text (INT32_MIN/2 <= z < 0), and above text (z >= 0). The default is ALL which preserves existing behavior. When a layer filter is set, placement_iterator_next automatically skips non-matching placements, so embedders no longer need to reimplement the z-index bucketing logic or iterate all placements three times per frame just to filter by layer.
This commit is contained in:
@@ -138,6 +138,38 @@ typedef enum {
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12,
|
||||
} GhosttyKittyGraphicsPlacementData;
|
||||
|
||||
/**
|
||||
* Z-layer classification for kitty graphics placements.
|
||||
*
|
||||
* Based on the kitty protocol z-index conventions:
|
||||
* - BELOW_BG: z < INT32_MIN/2 (drawn below cell background)
|
||||
* - BELOW_TEXT: INT32_MIN/2 <= z < 0 (above background, below text)
|
||||
* - ABOVE_TEXT: z >= 0 (above text)
|
||||
* - ALL: no filtering (current behavior)
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_KITTY_PLACEMENT_LAYER_ALL = 0,
|
||||
GHOSTTY_KITTY_PLACEMENT_LAYER_BELOW_BG = 1,
|
||||
GHOSTTY_KITTY_PLACEMENT_LAYER_BELOW_TEXT = 2,
|
||||
GHOSTTY_KITTY_PLACEMENT_LAYER_ABOVE_TEXT = 3,
|
||||
} GhosttyKittyPlacementLayer;
|
||||
|
||||
/**
|
||||
* Settable options for ghostty_kitty_graphics_placement_iterator_set().
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Set the z-layer filter for the iterator.
|
||||
*
|
||||
* Input type: GhosttyKittyPlacementLayer *
|
||||
*/
|
||||
GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_LAYER = 0,
|
||||
} GhosttyKittyGraphicsPlacementIteratorOption;
|
||||
|
||||
/**
|
||||
* Pixel format of a Kitty graphics image.
|
||||
*
|
||||
@@ -310,9 +342,36 @@ GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_iterator_new(
|
||||
GHOSTTY_API void ghostty_kitty_graphics_placement_iterator_free(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator);
|
||||
|
||||
/**
|
||||
* Set an option on a placement iterator.
|
||||
*
|
||||
* Use GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_LAYER with a
|
||||
* GhosttyKittyPlacementLayer value to filter placements by z-layer.
|
||||
* The filter is applied during iteration: ghostty_kitty_graphics_placement_next()
|
||||
* will skip placements that do not match the configured layer.
|
||||
*
|
||||
* The default layer is GHOSTTY_KITTY_PLACEMENT_LAYER_ALL (no filtering).
|
||||
*
|
||||
* @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param option The option to set
|
||||
* @param value Pointer to the value (type depends on option; NULL returns
|
||||
* GHOSTTY_INVALID_VALUE)
|
||||
* @return GHOSTTY_SUCCESS on success
|
||||
*
|
||||
* @ingroup kitty_graphics
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_iterator_set(
|
||||
GhosttyKittyGraphicsPlacementIterator iterator,
|
||||
GhosttyKittyGraphicsPlacementIteratorOption option,
|
||||
const void* value);
|
||||
|
||||
/**
|
||||
* Advance the placement iterator to the next placement.
|
||||
*
|
||||
* If a layer filter has been set via
|
||||
* ghostty_kitty_graphics_placement_iterator_set(), only placements
|
||||
* matching that layer are returned.
|
||||
*
|
||||
* @param iterator The iterator handle (may be NULL)
|
||||
* @return true if advanced to the next placement, false if at the end
|
||||
*
|
||||
|
||||
@@ -239,6 +239,7 @@ comptime {
|
||||
@export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" });
|
||||
@export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" });
|
||||
@export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" });
|
||||
@export(&c.kitty_graphics_placement_iterator_set, .{ .name = "ghostty_kitty_graphics_placement_iterator_set" });
|
||||
@export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" });
|
||||
@export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" });
|
||||
@export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" });
|
||||
|
||||
@@ -42,6 +42,7 @@ const PlacementIteratorWrapper = if (build_options.kitty_graphics)
|
||||
alloc: std.mem.Allocator,
|
||||
inner: PlacementMap.Iterator = undefined,
|
||||
entry: ?PlacementMap.Entry = null,
|
||||
layer_filter: PlacementLayer = .all,
|
||||
}
|
||||
else
|
||||
void;
|
||||
@@ -130,6 +131,34 @@ fn getTyped(
|
||||
return .success;
|
||||
}
|
||||
|
||||
/// C: GhosttyKittyPlacementLayer
|
||||
pub const PlacementLayer = enum(c_int) {
|
||||
all = 0,
|
||||
below_bg = 1,
|
||||
below_text = 2,
|
||||
above_text = 3,
|
||||
|
||||
fn matches(self: PlacementLayer, z: i32) bool {
|
||||
return switch (self) {
|
||||
.all => true,
|
||||
.below_bg => z < std.math.minInt(i32) / 2,
|
||||
.below_text => z >= std.math.minInt(i32) / 2 and z < 0,
|
||||
.above_text => z >= 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyKittyGraphicsPlacementIteratorOption
|
||||
pub const PlacementIteratorOption = enum(c_int) {
|
||||
layer = 0,
|
||||
|
||||
pub fn InType(comptime self: PlacementIteratorOption) type {
|
||||
return switch (self) {
|
||||
.layer => PlacementLayer,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyKittyImageFormat
|
||||
pub const ImageFormat = kitty_cmd.Transmission.Format;
|
||||
|
||||
@@ -233,12 +262,51 @@ pub fn placement_iterator_free(iter_: PlacementIterator) callconv(lib.calling_co
|
||||
iter.alloc.destroy(iter);
|
||||
}
|
||||
|
||||
pub fn placement_iterator_set(
|
||||
iter_: PlacementIterator,
|
||||
option: PlacementIteratorOption,
|
||||
value: ?*const anyopaque,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime !build_options.kitty_graphics) return .no_value;
|
||||
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(PlacementIteratorOption, @intFromEnum(option)) catch {
|
||||
return .invalid_value;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (option) {
|
||||
inline else => |comptime_option| placementIteratorSetTyped(
|
||||
iter_,
|
||||
comptime_option,
|
||||
@ptrCast(@alignCast(value orelse return .invalid_value)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn placementIteratorSetTyped(
|
||||
iter_: PlacementIterator,
|
||||
comptime option: PlacementIteratorOption,
|
||||
value: *const option.InType(),
|
||||
) Result {
|
||||
const iter = iter_ orelse return .invalid_value;
|
||||
switch (option) {
|
||||
.layer => iter.layer_filter = value.*,
|
||||
}
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn placement_iterator_next(iter_: PlacementIterator) callconv(lib.calling_conv) bool {
|
||||
if (comptime !build_options.kitty_graphics) return false;
|
||||
|
||||
const iter = iter_ orelse return false;
|
||||
iter.entry = iter.inner.next() orelse return false;
|
||||
return true;
|
||||
while (iter.inner.next()) |entry| {
|
||||
if (iter.layer_filter.matches(entry.value_ptr.z)) {
|
||||
iter.entry = entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn placement_get(
|
||||
@@ -537,6 +605,96 @@ test "placement_iterator with multiple placements" {
|
||||
try testing.expect(seen_p2);
|
||||
}
|
||||
|
||||
test "placement_iterator_set layer filter" {
|
||||
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);
|
||||
|
||||
// Transmit image 1.
|
||||
const transmit = "\x1b_Ga=t,t=d,f=24,i=1,s=1,v=2;////////\x1b\\";
|
||||
terminal_c.vt_write(t, transmit.ptr, transmit.len);
|
||||
|
||||
// Display with z=5 (above text), z=-1 (below text), z=-1073741825 (below bg).
|
||||
// INT32_MIN/2 = -1073741824, so -1073741825 < INT32_MIN/2.
|
||||
const d1 = "\x1b_Ga=p,i=1,p=1,z=5;\x1b\\";
|
||||
const d2 = "\x1b_Ga=p,i=1,p=2,z=-1;\x1b\\";
|
||||
const d3 = "\x1b_Ga=p,i=1,p=3,z=-1073741825;\x1b\\";
|
||||
terminal_c.vt_write(t, d1.ptr, d1.len);
|
||||
terminal_c.vt_write(t, d2.ptr, d2.len);
|
||||
terminal_c.vt_write(t, d3.ptr, d3.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);
|
||||
|
||||
// Filter: above_text (z >= 0) — should yield only p=1.
|
||||
var layer = PlacementLayer.above_text;
|
||||
try testing.expectEqual(Result.success, placement_iterator_set(iter, .layer, @ptrCast(&layer)));
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
|
||||
var count: u32 = 0;
|
||||
while (placement_iterator_next(iter)) {
|
||||
var z: i32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .z, @ptrCast(&z)));
|
||||
try testing.expect(z >= 0);
|
||||
count += 1;
|
||||
}
|
||||
try testing.expectEqual(1, count);
|
||||
|
||||
// Filter: below_text (INT32_MIN/2 <= z < 0) — should yield only p=2.
|
||||
layer = .below_text;
|
||||
try testing.expectEqual(Result.success, placement_iterator_set(iter, .layer, @ptrCast(&layer)));
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
|
||||
count = 0;
|
||||
while (placement_iterator_next(iter)) {
|
||||
var z: i32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .z, @ptrCast(&z)));
|
||||
try testing.expect(z >= std.math.minInt(i32) / 2 and z < 0);
|
||||
count += 1;
|
||||
}
|
||||
try testing.expectEqual(1, count);
|
||||
|
||||
// Filter: below_bg (z < INT32_MIN/2) — should yield only p=3.
|
||||
layer = .below_bg;
|
||||
try testing.expectEqual(Result.success, placement_iterator_set(iter, .layer, @ptrCast(&layer)));
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
|
||||
count = 0;
|
||||
while (placement_iterator_next(iter)) {
|
||||
var z: i32 = undefined;
|
||||
try testing.expectEqual(Result.success, placement_get(iter, .z, @ptrCast(&z)));
|
||||
try testing.expect(z < std.math.minInt(i32) / 2);
|
||||
count += 1;
|
||||
}
|
||||
try testing.expectEqual(1, count);
|
||||
|
||||
// Filter: all — should yield all 3.
|
||||
layer = .all;
|
||||
try testing.expectEqual(Result.success, placement_iterator_set(iter, .layer, @ptrCast(&layer)));
|
||||
try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter)));
|
||||
|
||||
count = 0;
|
||||
while (placement_iterator_next(iter)) count += 1;
|
||||
try testing.expectEqual(3, count);
|
||||
}
|
||||
|
||||
test "image_get_handle returns null for missing id" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ pub const kitty_graphics_image = kitty_graphics.image_get_handle;
|
||||
pub const kitty_graphics_image_get = kitty_graphics.image_get;
|
||||
pub const kitty_graphics_placement_iterator_new = kitty_graphics.placement_iterator_new;
|
||||
pub const kitty_graphics_placement_iterator_free = kitty_graphics.placement_iterator_free;
|
||||
pub const kitty_graphics_placement_iterator_set = kitty_graphics.placement_iterator_set;
|
||||
pub const kitty_graphics_placement_next = kitty_graphics.placement_iterator_next;
|
||||
pub const kitty_graphics_placement_get = kitty_graphics.placement_get;
|
||||
pub const kitty_graphics_placement_rect = kitty_graphics.placement_rect;
|
||||
|
||||
Reference in New Issue
Block a user