images: kitty support works well

We still need to handle querying for support.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
Tim Culverhouse 2024-01-30 20:51:54 -06:00
parent ee85f49bde
commit 23a00ede55
9 changed files with 56 additions and 53 deletions

View file

@ -33,8 +33,8 @@ Contributions are welcome.
| Images (half block) | ✅ | planned | ✅ | | Images (half block) | ✅ | planned | ✅ |
| Images (quadrant) | ✅ | planned | ✅ | | Images (quadrant) | ✅ | planned | ✅ |
| Images (sextant) | ❌ | ❌ | ✅ | | Images (sextant) | ❌ | ❌ | ✅ |
| Images (sixel) | ✅ | planned | ✅ | | Images (sixel) | ✅ | debating | ✅ |
| Images (kitty) | ✅ | planned | ✅ | | Images (kitty) | ✅ | | ✅ |
| Images (iterm2) | ❌ | ❌ | ✅ | | Images (iterm2) | ❌ | ❌ | ✅ |
| Video | ❌ | ❌ | ✅ | | Video | ❌ | ❌ | ✅ |
| Dank | 🆗 | 🆗 | ✅ | | Dank | 🆗 | 🆗 | ✅ |

View file

@ -3,89 +3,62 @@ const vaxis = @import("vaxis");
const log = std.log.scoped(.main); const log = std.log.scoped(.main);
// Our EventType. This can contain internal events as well as Vaxis events.
// Internal events can be posted into the same queue as vaxis events to allow
// for a single event loop with exhaustive switching. Booya
const Event = union(enum) { const Event = union(enum) {
key_press: vaxis.Key, key_press: vaxis.Key,
winsize: vaxis.Winsize, winsize: vaxis.Winsize,
focus_in,
focus_out,
foo: u8,
}; };
pub fn main() !void { pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer { defer {
const deinit_status = gpa.deinit(); const deinit_status = gpa.deinit();
//fail test; can't try in defer as defer is executed after we return
if (deinit_status == .leak) { if (deinit_status == .leak) {
log.err("memory leak", .{}); log.err("memory leak", .{});
} }
} }
const alloc = gpa.allocator(); const alloc = gpa.allocator();
// Initialize Vaxis with our event type
var vx = try vaxis.init(Event, .{}); var vx = try vaxis.init(Event, .{});
// deinit takes an optional allocator. If your program is exiting, you can
// choose to pass a null allocator to save some exit time.
defer vx.deinit(alloc); defer vx.deinit(alloc);
// Start the read loop. This puts the terminal in raw mode and begins
// reading user input
try vx.startReadThread(); try vx.startReadThread();
defer vx.stopReadThread(); defer vx.stopReadThread();
// Optionally enter the alternate screen
try vx.enterAltScreen(); try vx.enterAltScreen();
// Sends queries to terminal to detect certain features. This should
// _always_ be called, but is left to the application to decide when
try vx.queryTerminal(); try vx.queryTerminal();
const img = try vx.loadImage(alloc, .{ .path = "vaxis.png" }); const imgs = [_]vaxis.Image{
try vx.loadImage(alloc, .{ .path = "examples/zig.png" }),
try vx.loadImage(alloc, .{ .path = "examples/vaxis.png" }),
};
var n: usize = 0; var n: usize = 0;
// The main event loop. Vaxis provides a thread safe, blocking, buffered while (true) {
// queue which can serve as the primary event queue for an application
outer: while (true) {
// nextEvent blocks until an event is in the queue
const event = vx.nextEvent(); const event = vx.nextEvent();
log.debug("event: {}\r\n", .{event});
// exhaustive switching ftw. Vaxis will send events if your EventType
// enum has the fields for those events (ie "key_press", "winsize")
switch (event) { switch (event) {
.key_press => |key| { .key_press => |key| {
n += 1;
if (key.matches('c', .{ .ctrl = true })) { if (key.matches('c', .{ .ctrl = true })) {
break :outer; return;
} else if (key.matches('l', .{ .ctrl = true })) { } else if (key.matches('l', .{ .ctrl = true })) {
vx.queueRefresh(); vx.queueRefresh();
} else if (key.matches('n', .{ .ctrl = true })) { }
try vx.notify("vaxis", "hello from vaxis");
} else {}
}, },
.winsize => |ws| try vx.resize(alloc, ws), .winsize => |ws| try vx.resize(alloc, ws),
else => {},
} }
// vx.window() returns the root window. This window is the size of the n = (n + 1) % imgs.len;
// terminal and can spawn child windows as logical areas. Child windows
// cannot draw outside of their bounds
const win = vx.window(); const win = vx.window();
// Clear the entire space because we are drawing in immediate mode.
// vaxis double buffers the screen. This new frame will be compared to
// the old and only updated cells will be drawn
win.clear(); win.clear();
const child = win.initChild(n, n, .expand, .expand); const img = imgs[n];
const dims = try img.cellSize(win);
const center = vaxis.alignment.center(win, dims.cols, dims.rows);
const scale = false;
const z_index = 0;
img.draw(center, scale, z_index);
img.draw(child, false, 0);
// Render the screen
try vx.render(); try vx.render();
} }
} }

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
examples/zig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -6,7 +6,6 @@ const base64 = std.base64.standard.Encoder;
const zigimg = @import("zigimg"); const zigimg = @import("zigimg");
const Window = @import("Window.zig"); const Window = @import("Window.zig");
const Winsize = @import("Tty.zig").Winsize;
const log = std.log.scoped(.image); const log = std.log.scoped(.image);
@ -22,7 +21,7 @@ pub const Source = union(enum) {
pub const Placement = struct { pub const Placement = struct {
img_id: u32, img_id: u32,
z_index: i32, z_index: i32,
scale: bool, size: ?CellSize = null,
}; };
pub const CellSize = struct { pub const CellSize = struct {
@ -42,15 +41,26 @@ pub fn draw(self: Image, win: Window, scale: bool, z_index: i32) void {
const p = Placement{ const p = Placement{
.img_id = self.id, .img_id = self.id,
.z_index = z_index, .z_index = z_index,
.scale = scale, .size = sz: {
if (!scale) break :sz null;
break :sz CellSize{
.rows = win.height,
.cols = win.width,
};
},
}; };
win.writeCell(0, 0, .{ .image = p }); win.writeCell(0, 0, .{ .image = p });
} }
pub fn cellSize(self: Image, winsize: Winsize) !CellSize { pub fn cellSize(self: Image, win: Window) !CellSize {
// cell geometry // cell geometry
const pix_per_col = try std.math.divCeil(usize, winsize.x_pixel, winsize.cols); const x_pix = win.screen.width_pix;
const pix_per_row = try std.math.divCeil(usize, winsize.y_pixel, winsize.rows); const y_pix = win.screen.height_pix;
const w = win.screen.width;
const h = win.screen.height;
const pix_per_col = try std.math.divCeil(usize, x_pix, w);
const pix_per_row = try std.math.divCeil(usize, y_pix, h);
const cell_width = std.math.divCeil(usize, self.width, pix_per_col) catch 0; const cell_width = std.math.divCeil(usize, self.width, pix_per_col) catch 0;
const cell_height = std.math.divCeil(usize, self.height, pix_per_row) catch 0; const cell_height = std.math.divCeil(usize, self.height, pix_per_row) catch 0;

View file

@ -9,6 +9,8 @@ pub const Key = @import("Key.zig");
pub const Winsize = @import("Tty.zig").Winsize; pub const Winsize = @import("Tty.zig").Winsize;
pub const widgets = @import("widgets/main.zig"); pub const widgets = @import("widgets/main.zig");
pub const alignment = widgets.alignment;
pub const border = widgets.border;
pub const Image = @import("Image.zig"); pub const Image = @import("Image.zig");
@ -30,7 +32,6 @@ test {
_ = @import("ctlseqs.zig"); _ = @import("ctlseqs.zig");
_ = @import("event.zig"); _ = @import("event.zig");
_ = @import("gwidth.zig"); _ = @import("gwidth.zig");
_ = @import("image/image.zig");
_ = @import("queue.zig"); _ = @import("queue.zig");
_ = @import("vaxis.zig"); _ = @import("vaxis.zig");
} }

View file

@ -38,6 +38,7 @@ pub fn Vaxis(comptime T: type) type {
pub const Capabilities = struct { pub const Capabilities = struct {
kitty_keyboard: bool = false, kitty_keyboard: bool = false,
kitty_graphics: bool = false,
rgb: bool = false, rgb: bool = false,
unicode: bool = false, unicode: bool = false,
}; };
@ -60,8 +61,6 @@ pub fn Vaxis(comptime T: type) type {
alt_screen: bool = false, alt_screen: bool = false,
/// if we have entered kitty keyboard /// if we have entered kitty keyboard
kitty_keyboard: bool = false, kitty_keyboard: bool = false,
// TODO: should be false but we aren't querying yet
kitty_graphics: bool = true,
bracketed_paste: bool = false, bracketed_paste: bool = false,
mouse: bool = false, mouse: bool = false,
} = .{}, } = .{},
@ -337,7 +336,19 @@ pub fn Vaxis(comptime T: type) type {
} }
if (cell.image) |img| { if (cell.image) |img| {
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.kitty_graphics_place, .{ img.img_id, img.z_index }); if (img.size) |size| {
try std.fmt.format(
tty.buffered_writer.writer(),
ctlseqs.kitty_graphics_scale,
.{ img.img_id, img.z_index, size.cols, size.rows },
);
} else {
try std.fmt.format(
tty.buffered_writer.writer(),
ctlseqs.kitty_graphics_place,
.{ img.img_id, img.z_index },
);
}
} }
// something is different, so let's loop throuugh everything and // something is different, so let's loop throuugh everything and

7
src/widgets/align.zig Normal file
View file

@ -0,0 +1,7 @@
const Window = @import("../Window.zig");
pub fn center(parent: Window, cols: usize, rows: usize) Window {
const y_off = (parent.height / 2) - (rows / 2);
const x_off = (parent.width / 2) - (cols / 2);
return parent.initChild(x_off, y_off, .{ .limit = cols }, .{ .limit = rows });
}

View file

@ -1,2 +1,3 @@
pub const TextInput = @import("TextInput.zig"); pub const TextInput = @import("TextInput.zig");
pub const border = @import("border.zig"); pub const border = @import("border.zig");
pub const alignment = @import("align.zig");