diff --git a/README.md b/README.md index 4ce26c7..e8e722c 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ Contributions are welcome. | Images (half block) | ✅ | planned | ✅ | | Images (quadrant) | ✅ | planned | ✅ | | Images (sextant) | ❌ | ❌ | ✅ | -| Images (sixel) | ✅ | planned | ✅ | -| Images (kitty) | ✅ | planned | ✅ | +| Images (sixel) | ✅ | debating | ✅ | +| Images (kitty) | ✅ | ✅ | ✅ | | Images (iterm2) | ❌ | ❌ | ✅ | | Video | ❌ | ❌ | ✅ | | Dank | 🆗 | 🆗 | ✅ | diff --git a/examples/image.zig b/examples/image.zig index 0d32e8e..aba084d 100644 --- a/examples/image.zig +++ b/examples/image.zig @@ -3,89 +3,62 @@ const vaxis = @import("vaxis"); 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) { key_press: vaxis.Key, winsize: vaxis.Winsize, - focus_in, - focus_out, - foo: u8, }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer { const deinit_status = gpa.deinit(); - //fail test; can't try in defer as defer is executed after we return if (deinit_status == .leak) { log.err("memory leak", .{}); } } const alloc = gpa.allocator(); - // Initialize Vaxis with our event type 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); - // Start the read loop. This puts the terminal in raw mode and begins - // reading user input try vx.startReadThread(); defer vx.stopReadThread(); - // Optionally enter the alternate screen 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(); - 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; - // The main event loop. Vaxis provides a thread safe, blocking, buffered - // queue which can serve as the primary event queue for an application - outer: while (true) { - // nextEvent blocks until an event is in the queue + while (true) { 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) { .key_press => |key| { - n += 1; if (key.matches('c', .{ .ctrl = true })) { - break :outer; + return; } else if (key.matches('l', .{ .ctrl = true })) { vx.queueRefresh(); - } else if (key.matches('n', .{ .ctrl = true })) { - try vx.notify("vaxis", "hello from vaxis"); - } else {} + } }, - .winsize => |ws| try vx.resize(alloc, ws), - else => {}, } - // vx.window() returns the root window. This window is the size of the - // terminal and can spawn child windows as logical areas. Child windows - // cannot draw outside of their bounds + n = (n + 1) % imgs.len; 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(); - 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(); } } diff --git a/vaxis.png b/examples/vaxis.png similarity index 100% rename from vaxis.png rename to examples/vaxis.png diff --git a/examples/zig.png b/examples/zig.png new file mode 100644 index 0000000..934c23f Binary files /dev/null and b/examples/zig.png differ diff --git a/src/Image.zig b/src/Image.zig index 04d6204..ad45172 100644 --- a/src/Image.zig +++ b/src/Image.zig @@ -6,7 +6,6 @@ const base64 = std.base64.standard.Encoder; const zigimg = @import("zigimg"); const Window = @import("Window.zig"); -const Winsize = @import("Tty.zig").Winsize; const log = std.log.scoped(.image); @@ -22,7 +21,7 @@ pub const Source = union(enum) { pub const Placement = struct { img_id: u32, z_index: i32, - scale: bool, + size: ?CellSize = null, }; pub const CellSize = struct { @@ -42,15 +41,26 @@ pub fn draw(self: Image, win: Window, scale: bool, z_index: i32) void { const p = Placement{ .img_id = self.id, .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 }); } -pub fn cellSize(self: Image, winsize: Winsize) !CellSize { +pub fn cellSize(self: Image, win: Window) !CellSize { // cell geometry - const pix_per_col = try std.math.divCeil(usize, winsize.x_pixel, winsize.cols); - const pix_per_row = try std.math.divCeil(usize, winsize.y_pixel, winsize.rows); + const x_pix = win.screen.width_pix; + 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_height = std.math.divCeil(usize, self.height, pix_per_row) catch 0; diff --git a/src/main.zig b/src/main.zig index e0c30df..63e2c00 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,6 +9,8 @@ pub const Key = @import("Key.zig"); pub const Winsize = @import("Tty.zig").Winsize; pub const widgets = @import("widgets/main.zig"); +pub const alignment = widgets.alignment; +pub const border = widgets.border; pub const Image = @import("Image.zig"); @@ -30,7 +32,6 @@ test { _ = @import("ctlseqs.zig"); _ = @import("event.zig"); _ = @import("gwidth.zig"); - _ = @import("image/image.zig"); _ = @import("queue.zig"); _ = @import("vaxis.zig"); } diff --git a/src/vaxis.zig b/src/vaxis.zig index 39f12d2..0ca3001 100644 --- a/src/vaxis.zig +++ b/src/vaxis.zig @@ -38,6 +38,7 @@ pub fn Vaxis(comptime T: type) type { pub const Capabilities = struct { kitty_keyboard: bool = false, + kitty_graphics: bool = false, rgb: bool = false, unicode: bool = false, }; @@ -60,8 +61,6 @@ pub fn Vaxis(comptime T: type) type { alt_screen: bool = false, /// if we have entered kitty keyboard kitty_keyboard: bool = false, - // TODO: should be false but we aren't querying yet - kitty_graphics: bool = true, bracketed_paste: bool = false, mouse: bool = false, } = .{}, @@ -337,7 +336,19 @@ pub fn Vaxis(comptime T: type) type { } 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 diff --git a/src/widgets/align.zig b/src/widgets/align.zig new file mode 100644 index 0000000..f714479 --- /dev/null +++ b/src/widgets/align.zig @@ -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 }); +} diff --git a/src/widgets/main.zig b/src/widgets/main.zig index 1eabb24..714b3bd 100644 --- a/src/widgets/main.zig +++ b/src/widgets/main.zig @@ -1,2 +1,3 @@ pub const TextInput = @import("TextInput.zig"); pub const border = @import("border.zig"); +pub const alignment = @import("align.zig");