images: kitty image protocol works
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
cc75fe6272
commit
f901dde2a0
11 changed files with 167 additions and 265 deletions
|
@ -43,8 +43,9 @@ pub fn main() !void {
|
||||||
// _always_ be called, but is left to the application to decide when
|
// _always_ be called, but is left to the application to decide when
|
||||||
try vx.queryTerminal();
|
try vx.queryTerminal();
|
||||||
|
|
||||||
var img = try vaxis.Image.init(alloc, .{ .path = "vaxis.png" }, 1, .kitty);
|
const img = try vx.loadImage(alloc, .{ .path = "vaxis.png" });
|
||||||
defer img.deinit();
|
|
||||||
|
var n: usize = 0;
|
||||||
|
|
||||||
// The main event loop. Vaxis provides a thread safe, blocking, buffered
|
// The main event loop. Vaxis provides a thread safe, blocking, buffered
|
||||||
// queue which can serve as the primary event queue for an application
|
// queue which can serve as the primary event queue for an application
|
||||||
|
@ -56,6 +57,7 @@ pub fn main() !void {
|
||||||
// enum has the fields for those events (ie "key_press", "winsize")
|
// 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;
|
break :outer;
|
||||||
} else if (key.matches('l', .{ .ctrl = true })) {
|
} else if (key.matches('l', .{ .ctrl = true })) {
|
||||||
|
@ -79,7 +81,9 @@ pub fn main() !void {
|
||||||
// the old and only updated cells will be drawn
|
// the old and only updated cells will be drawn
|
||||||
win.clear();
|
win.clear();
|
||||||
|
|
||||||
try img.draw(win);
|
const child = win.initChild(n, n, .expand, .expand);
|
||||||
|
|
||||||
|
img.draw(child, false, 0);
|
||||||
|
|
||||||
// Render the screen
|
// Render the screen
|
||||||
try vx.render();
|
try vx.render();
|
||||||
|
|
61
src/Image.zig
Normal file
61
src/Image.zig
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const fmt = std.fmt;
|
||||||
|
const math = std.math;
|
||||||
|
const testing = std.testing;
|
||||||
|
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);
|
||||||
|
|
||||||
|
const Image = @This();
|
||||||
|
|
||||||
|
const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};";
|
||||||
|
|
||||||
|
pub const Source = union(enum) {
|
||||||
|
path: []const u8,
|
||||||
|
mem: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Placement = struct {
|
||||||
|
img_id: u32,
|
||||||
|
z_index: i32,
|
||||||
|
scale: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CellSize = struct {
|
||||||
|
rows: usize,
|
||||||
|
cols: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// unique identifier for this image. This will be managed by the screen.
|
||||||
|
id: u32,
|
||||||
|
|
||||||
|
// width in pixels
|
||||||
|
width: usize,
|
||||||
|
// height in pixels
|
||||||
|
height: usize,
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
win.writeCell(0, 0, .{ .image = p });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cellSize(self: Image, winsize: Winsize) !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 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;
|
||||||
|
return .{
|
||||||
|
.rows = cell_height,
|
||||||
|
.cols = cell_width,
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ const assert = std.debug.assert;
|
||||||
const Style = @import("cell.zig").Style;
|
const Style = @import("cell.zig").Style;
|
||||||
const Cell = @import("cell.zig").Cell;
|
const Cell = @import("cell.zig").Cell;
|
||||||
const Shape = @import("Mouse.zig").Shape;
|
const Shape = @import("Mouse.zig").Shape;
|
||||||
const Image = @import("image/image.zig").Image;
|
|
||||||
const Placement = @import("Screen.zig").Placement;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.internal_screen);
|
const log = std.log.scoped(.internal_screen);
|
||||||
|
|
||||||
|
@ -37,13 +35,10 @@ cursor_vis: bool = false,
|
||||||
|
|
||||||
mouse_shape: Shape = .default,
|
mouse_shape: Shape = .default,
|
||||||
|
|
||||||
images: std.ArrayList(Placement) = undefined,
|
|
||||||
|
|
||||||
/// sets each cell to the default cell
|
/// sets each cell to the default cell
|
||||||
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
||||||
var screen = InternalScreen{
|
var screen = InternalScreen{
|
||||||
.buf = try alloc.alloc(InternalCell, w * h),
|
.buf = try alloc.alloc(InternalCell, w * h),
|
||||||
.images = std.ArrayList(Placement).init(alloc),
|
|
||||||
};
|
};
|
||||||
for (screen.buf, 0..) |_, i| {
|
for (screen.buf, 0..) |_, i| {
|
||||||
screen.buf[i] = .{
|
screen.buf[i] = .{
|
||||||
|
@ -58,7 +53,6 @@ pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
|
||||||
self.images.deinit();
|
|
||||||
for (self.buf, 0..) |_, i| {
|
for (self.buf, 0..) |_, i| {
|
||||||
self.buf[i].char.deinit();
|
self.buf[i].char.deinit();
|
||||||
self.buf[i].uri.deinit();
|
self.buf[i].uri.deinit();
|
||||||
|
|
|
@ -3,30 +3,19 @@ const assert = std.debug.assert;
|
||||||
|
|
||||||
const Cell = @import("cell.zig").Cell;
|
const Cell = @import("cell.zig").Cell;
|
||||||
const Shape = @import("Mouse.zig").Shape;
|
const Shape = @import("Mouse.zig").Shape;
|
||||||
const Image = @import("image/image.zig").Image;
|
const Image = @import("Image.zig");
|
||||||
|
const Winsize = @import("Tty.zig").Winsize;
|
||||||
|
|
||||||
const log = std.log.scoped(.screen);
|
const log = std.log.scoped(.screen);
|
||||||
|
|
||||||
const Screen = @This();
|
const Screen = @This();
|
||||||
|
|
||||||
pub const Placement = struct {
|
|
||||||
img: Image,
|
|
||||||
placement_id: u32,
|
|
||||||
col: usize,
|
|
||||||
row: usize,
|
|
||||||
|
|
||||||
/// two placements are considered equal if their image id and their
|
|
||||||
/// placement id are equal
|
|
||||||
pub fn eql(self: Placement, tgt: Placement) bool {
|
|
||||||
if (self.img.getId() != tgt.img.getId()) return false;
|
|
||||||
if (self.placement_id != tgt.placement_id) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
width: usize = 0,
|
width: usize = 0,
|
||||||
height: usize = 0,
|
height: usize = 0,
|
||||||
|
|
||||||
|
width_pix: usize = 0,
|
||||||
|
height_pix: usize = 0,
|
||||||
|
|
||||||
buf: []Cell = undefined,
|
buf: []Cell = undefined,
|
||||||
|
|
||||||
cursor_row: usize = 0,
|
cursor_row: usize = 0,
|
||||||
|
@ -37,14 +26,15 @@ unicode: bool = false,
|
||||||
|
|
||||||
mouse_shape: Shape = .default,
|
mouse_shape: Shape = .default,
|
||||||
|
|
||||||
images: std.ArrayList(Placement) = undefined,
|
pub fn init(alloc: std.mem.Allocator, winsize: Winsize) !Screen {
|
||||||
|
const w = winsize.cols;
|
||||||
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
|
const h = winsize.rows;
|
||||||
var self = Screen{
|
var self = Screen{
|
||||||
.buf = try alloc.alloc(Cell, w * h),
|
.buf = try alloc.alloc(Cell, w * h),
|
||||||
.width = w,
|
.width = w,
|
||||||
.height = h,
|
.height = h,
|
||||||
.images = std.ArrayList(Placement).init(alloc),
|
.width_pix = winsize.x_pixel,
|
||||||
|
.height_pix = winsize.y_pixel,
|
||||||
};
|
};
|
||||||
for (self.buf, 0..) |_, i| {
|
for (self.buf, 0..) |_, i| {
|
||||||
self.buf[i] = .{};
|
self.buf[i] = .{};
|
||||||
|
@ -53,7 +43,6 @@ pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void {
|
||||||
alloc.free(self.buf);
|
alloc.free(self.buf);
|
||||||
self.images.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// writes a cell to a location. 0 indexed
|
/// writes a cell to a location. 0 indexed
|
||||||
|
@ -70,19 +59,3 @@ pub fn writeCell(self: *Screen, col: usize, row: usize, cell: Cell) void {
|
||||||
assert(i < self.buf.len);
|
assert(i < self.buf.len);
|
||||||
self.buf[i] = cell;
|
self.buf[i] = cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeImage(
|
|
||||||
self: *Screen,
|
|
||||||
col: usize,
|
|
||||||
row: usize,
|
|
||||||
img: Image,
|
|
||||||
placement_id: u32,
|
|
||||||
) !void {
|
|
||||||
const p = Placement{
|
|
||||||
.img = img,
|
|
||||||
.placement_id = placement_id,
|
|
||||||
.col = col,
|
|
||||||
.row = row,
|
|
||||||
};
|
|
||||||
try self.images.append(p);
|
|
||||||
}
|
|
||||||
|
|
|
@ -69,27 +69,9 @@ pub fn writeCell(self: Window, col: usize, row: usize, cell: Cell) void {
|
||||||
self.screen.writeCell(col + self.x_off, row + self.y_off, cell);
|
self.screen.writeCell(col + self.x_off, row + self.y_off, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// writes an image to the location in the window
|
|
||||||
pub fn writeImage(
|
|
||||||
self: Window,
|
|
||||||
img: Image,
|
|
||||||
placement_id: u32,
|
|
||||||
) !void {
|
|
||||||
if (self.height == 0 or self.width == 0) return;
|
|
||||||
try self.screen.writeImage(self.x_off, self.y_off, img, placement_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// fills the window with the default cell
|
/// fills the window with the default cell
|
||||||
pub fn clear(self: Window) void {
|
pub fn clear(self: Window) void {
|
||||||
self.fill(.{});
|
self.fill(.{});
|
||||||
// we clear any image with it's first cell within this window
|
|
||||||
for (self.screen.images.items, 0..) |p, i| {
|
|
||||||
if (p.col >= self.x_off and p.col < self.width and
|
|
||||||
p.row >= self.y_off and p.row < self.height)
|
|
||||||
{
|
|
||||||
_ = self.screen.images.swapRemove(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns the width of the grapheme. This depends on the terminal capabilities
|
/// returns the width of the grapheme. This depends on the terminal capabilities
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
const Image = @import("Image.zig");
|
||||||
|
|
||||||
pub const Cell = struct {
|
pub const Cell = struct {
|
||||||
char: Character = .{},
|
char: Character = .{},
|
||||||
style: Style = .{},
|
style: Style = .{},
|
||||||
link: Hyperlink = .{},
|
link: Hyperlink = .{},
|
||||||
|
image: ?Image.Placement = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Character = struct {
|
pub const Character = struct {
|
||||||
|
|
|
@ -89,3 +89,8 @@ pub const osc8_clear = "\x1b]8;;\x1b\\";
|
||||||
pub const osc9_notify = "\x1b]9;{s}\x1b\\";
|
pub const osc9_notify = "\x1b]9;{s}\x1b\\";
|
||||||
pub const osc777_notify = "\x1b]777;notify;{s};{s}\x1b\\";
|
pub const osc777_notify = "\x1b]777;notify;{s};{s}\x1b\\";
|
||||||
pub const osc22_mouse_shape = "\x1b]22;{s}\x1b\\";
|
pub const osc22_mouse_shape = "\x1b]22;{s}\x1b\\";
|
||||||
|
|
||||||
|
// Kitty graphics
|
||||||
|
pub const kitty_graphics_clear = "\x1b_Ga=d\x1b\\";
|
||||||
|
pub const kitty_graphics_place = "\x1b_Ga=p,i={d},z={d},C=1\x1b\\";
|
||||||
|
pub const kitty_graphics_scale = "\x1b_Ga=p,i={d},z={d},c={d},r={d},C=1\x1b\\";
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const fmt = std.fmt;
|
|
||||||
const math = std.math;
|
|
||||||
const testing = std.testing;
|
|
||||||
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(.kitty);
|
|
||||||
|
|
||||||
const Kitty = @This();
|
|
||||||
|
|
||||||
const max_chunk: usize = 4096;
|
|
||||||
|
|
||||||
const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};";
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
/// the decoded image
|
|
||||||
img: zigimg.Image,
|
|
||||||
|
|
||||||
/// unique identifier for this image. This will be managed by the screen. The ID
|
|
||||||
/// is only null for images which have not been transmitted to the screen
|
|
||||||
id: u32,
|
|
||||||
|
|
||||||
pub fn deinit(self: *const Kitty) void {
|
|
||||||
var img = self.img;
|
|
||||||
img.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// transmit encodes and transmits the image to the terminal
|
|
||||||
pub fn transmit(self: Kitty, writer: anytype) !void {
|
|
||||||
var alloc = self.alloc;
|
|
||||||
const png_buf = try alloc.alloc(u8, self.img.imageByteSize());
|
|
||||||
defer alloc.free(png_buf);
|
|
||||||
const png = try self.img.writeToMemory(png_buf, .{ .png = .{} });
|
|
||||||
const b64_buf = try alloc.alloc(u8, base64.calcSize(png.len));
|
|
||||||
const encoded = base64.encode(b64_buf, png);
|
|
||||||
defer alloc.free(b64_buf);
|
|
||||||
|
|
||||||
log.debug("transmitting kitty image: id={d}, len={d}", .{ self.id, encoded.len });
|
|
||||||
|
|
||||||
if (encoded.len < max_chunk) {
|
|
||||||
try fmt.format(
|
|
||||||
writer,
|
|
||||||
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
|
||||||
.{
|
|
||||||
self.id,
|
|
||||||
encoded,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
var n: usize = max_chunk;
|
|
||||||
|
|
||||||
try fmt.format(
|
|
||||||
writer,
|
|
||||||
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
|
||||||
.{ self.id, encoded[0..n] },
|
|
||||||
);
|
|
||||||
while (n < encoded.len) : (n += max_chunk) {
|
|
||||||
const end: usize = @min(n + max_chunk, encoded.len);
|
|
||||||
const m: u2 = if (end == encoded.len) 0 else 1;
|
|
||||||
try fmt.format(
|
|
||||||
writer,
|
|
||||||
"\x1b_Gm={d};{s}\x1b\\",
|
|
||||||
.{
|
|
||||||
m,
|
|
||||||
encoded[n..end],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const math = std.math;
|
|
||||||
const testing = std.testing;
|
|
||||||
const zigimg = @import("zigimg");
|
|
||||||
|
|
||||||
const Winsize = @import("../Tty.zig").Winsize;
|
|
||||||
const Window = @import("../Window.zig");
|
|
||||||
|
|
||||||
const Kitty = @import("Kitty.zig");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.image);
|
|
||||||
|
|
||||||
pub const Image = union(enum) {
|
|
||||||
kitty: Kitty,
|
|
||||||
|
|
||||||
pub const Protocol = enum {
|
|
||||||
kitty,
|
|
||||||
// TODO: sixel, full block, half block, quad block
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const CellSize = struct {
|
|
||||||
rows: usize,
|
|
||||||
cols: usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Source = union(enum) {
|
|
||||||
path: []const u8,
|
|
||||||
mem: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// initialize a new image
|
|
||||||
pub fn init(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
src: Source,
|
|
||||||
id: u32,
|
|
||||||
protocol: Protocol,
|
|
||||||
) !Image {
|
|
||||||
const img = switch (src) {
|
|
||||||
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
|
||||||
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (protocol) {
|
|
||||||
.kitty => {
|
|
||||||
return .{
|
|
||||||
.kitty = Kitty{
|
|
||||||
.alloc = alloc,
|
|
||||||
.img = img,
|
|
||||||
.id = id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Image) void {
|
|
||||||
switch (self) {
|
|
||||||
inline else => |*case| case.deinit(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: Image, win: Window) !void {
|
|
||||||
switch (self) {
|
|
||||||
.kitty => {
|
|
||||||
const row: u16 = @truncate(win.y_off);
|
|
||||||
const col: u16 = @truncate(win.x_off);
|
|
||||||
// the placement id has the high 16 bits as the column and the low 16
|
|
||||||
// bits as the row. This means we can only place this image one time at
|
|
||||||
// the same location - which is completely sane
|
|
||||||
const pid: u32 = col << 15 | row;
|
|
||||||
try win.writeImage(self, pid);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transmit(self: Image, writer: anytype) !void {
|
|
||||||
switch (self) {
|
|
||||||
.kitty => |k| return k.transmit(writer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getId(self: Image) ?u32 {
|
|
||||||
switch (self) {
|
|
||||||
.kitty => |k| return k.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cellSize(self: Image, winsize: Winsize) !CellSize {
|
|
||||||
// cell geometry
|
|
||||||
const pix_per_col = try math.divCeil(usize, winsize.x_pixel, winsize.cols);
|
|
||||||
const pix_per_row = try math.divCeil(usize, winsize.y_pixel, winsize.rows);
|
|
||||||
|
|
||||||
const cell_width = math.divCeil(usize, self.img.width, pix_per_col) catch 0;
|
|
||||||
const cell_height = math.divCeil(usize, self.img.height, pix_per_row) catch 0;
|
|
||||||
|
|
||||||
return CellSize{
|
|
||||||
.rows = cell_height,
|
|
||||||
.cols = cell_width,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -10,7 +10,7 @@ pub const Winsize = @import("Tty.zig").Winsize;
|
||||||
|
|
||||||
pub const widgets = @import("widgets/main.zig");
|
pub const widgets = @import("widgets/main.zig");
|
||||||
|
|
||||||
pub const Image = @import("image/image.zig").Image;
|
pub const Image = @import("Image.zig");
|
||||||
|
|
||||||
/// Initialize a Vaxis application.
|
/// Initialize a Vaxis application.
|
||||||
pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
||||||
|
|
104
src/vaxis.zig
104
src/vaxis.zig
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const atomic = std.atomic;
|
const atomic = std.atomic;
|
||||||
|
const base64 = std.base64.standard.Encoder;
|
||||||
|
|
||||||
const Queue = @import("queue.zig").Queue;
|
const Queue = @import("queue.zig").Queue;
|
||||||
const ctlseqs = @import("ctlseqs.zig");
|
const ctlseqs = @import("ctlseqs.zig");
|
||||||
|
@ -14,7 +15,8 @@ const Style = @import("cell.zig").Style;
|
||||||
const Hyperlink = @import("cell.zig").Hyperlink;
|
const Hyperlink = @import("cell.zig").Hyperlink;
|
||||||
const gwidth = @import("gwidth.zig");
|
const gwidth = @import("gwidth.zig");
|
||||||
const Shape = @import("Mouse.zig").Shape;
|
const Shape = @import("Mouse.zig").Shape;
|
||||||
const Placement = Screen.Placement;
|
const Image = @import("Image.zig");
|
||||||
|
const zigimg = @import("zigimg");
|
||||||
|
|
||||||
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should
|
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should
|
||||||
/// be a tagged union which contains all of the events the application will
|
/// be a tagged union which contains all of the events the application will
|
||||||
|
@ -58,6 +60,8 @@ 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,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
@ -71,6 +75,9 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
/// futex times out
|
/// futex times out
|
||||||
query_futex: atomic.Value(u32) = atomic.Value(u32).init(0),
|
query_futex: atomic.Value(u32) = atomic.Value(u32).init(0),
|
||||||
|
|
||||||
|
// images
|
||||||
|
next_img_id: u32 = 1,
|
||||||
|
|
||||||
// statistics
|
// statistics
|
||||||
renders: usize = 0,
|
renders: usize = 0,
|
||||||
render_dur: i128 = 0,
|
render_dur: i128 = 0,
|
||||||
|
@ -151,7 +158,7 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
||||||
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
|
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
|
||||||
self.screen.deinit(alloc);
|
self.screen.deinit(alloc);
|
||||||
self.screen = try Screen.init(alloc, winsize.cols, winsize.rows);
|
self.screen = try Screen.init(alloc, winsize);
|
||||||
self.screen.unicode = self.caps.unicode;
|
self.screen.unicode = self.caps.unicode;
|
||||||
// try self.screen.int(alloc, winsize.cols, winsize.rows);
|
// try self.screen.int(alloc, winsize.cols, winsize.rows);
|
||||||
// we only init our current screen. This has the effect of redrawing
|
// we only init our current screen. This has the effect of redrawing
|
||||||
|
@ -243,7 +250,6 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
// the next render call will refresh the entire screen
|
// the next render call will refresh the entire screen
|
||||||
pub fn queueRefresh(self: *Self) void {
|
pub fn queueRefresh(self: *Self) void {
|
||||||
self.refresh = true;
|
self.refresh = true;
|
||||||
self.screen_last.images.clearRetainingCapacity();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draws the screen to the terminal
|
/// draws the screen to the terminal
|
||||||
|
@ -280,26 +286,8 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
var cursor: Style = .{};
|
var cursor: Style = .{};
|
||||||
var link: Hyperlink = .{};
|
var link: Hyperlink = .{};
|
||||||
|
|
||||||
// remove images from the screen by looping through the last state
|
// Clear all images
|
||||||
// and comparing to the next state
|
_ = try tty.write(ctlseqs.kitty_graphics_clear);
|
||||||
for (self.screen_last.images.items) |last_img| {
|
|
||||||
const keep: bool = for (self.screen.images.items) |next_img| {
|
|
||||||
if (last_img.eql(next_img)) break true;
|
|
||||||
} else false;
|
|
||||||
if (keep) continue;
|
|
||||||
// TODO: remove image placements
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new images. Could slightly optimize by knowing which images
|
|
||||||
// we need to keep from the remove loop
|
|
||||||
for (self.screen.images.items) |img| {
|
|
||||||
const transmit: bool = for (self.screen_last.images.items) |last_img| {
|
|
||||||
if (last_img.eql(img)) break false;
|
|
||||||
} else true;
|
|
||||||
if (!transmit) continue;
|
|
||||||
// TODO: transmit the new image to the screen
|
|
||||||
try img.img.transmit(tty.buffered_writer.writer());
|
|
||||||
}
|
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.screen.buf.len) {
|
while (i < self.screen.buf.len) {
|
||||||
|
@ -326,7 +314,7 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
// If cell is the same as our last frame, we don't need to do
|
// If cell is the same as our last frame, we don't need to do
|
||||||
// anything
|
// anything
|
||||||
const last = self.screen_last.buf[i];
|
const last = self.screen_last.buf[i];
|
||||||
if (!self.refresh and last.eql(cell) and !last.skipped) {
|
if (!self.refresh and last.eql(cell) and !last.skipped and cell.image == null) {
|
||||||
reposition = true;
|
reposition = true;
|
||||||
// Close any osc8 sequence we might be in before
|
// Close any osc8 sequence we might be in before
|
||||||
// repositioning
|
// repositioning
|
||||||
|
@ -348,6 +336,10 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cell.image) |img| {
|
||||||
|
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
|
||||||
// find out what
|
// find out what
|
||||||
|
|
||||||
|
@ -581,6 +573,70 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
try tty.flush();
|
try tty.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn loadImage(
|
||||||
|
self: *Self,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
src: Image.Source,
|
||||||
|
) !Image {
|
||||||
|
var tty = self.tty orelse return error.NoTTY;
|
||||||
|
defer self.next_img_id += 1;
|
||||||
|
|
||||||
|
const writer = tty.buffered_writer.writer();
|
||||||
|
|
||||||
|
var img = switch (src) {
|
||||||
|
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
||||||
|
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
||||||
|
};
|
||||||
|
defer img.deinit();
|
||||||
|
const png_buf = try alloc.alloc(u8, img.imageByteSize());
|
||||||
|
defer alloc.free(png_buf);
|
||||||
|
const png = try img.writeToMemory(png_buf, .{ .png = .{} });
|
||||||
|
const b64_buf = try alloc.alloc(u8, base64.calcSize(png.len));
|
||||||
|
const encoded = base64.encode(b64_buf, png);
|
||||||
|
defer alloc.free(b64_buf);
|
||||||
|
|
||||||
|
const id = self.next_img_id;
|
||||||
|
|
||||||
|
log.debug("transmitting kitty image: id={d}, len={d}", .{ id, encoded.len });
|
||||||
|
|
||||||
|
if (encoded.len < 4096) {
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
||||||
|
.{
|
||||||
|
id,
|
||||||
|
encoded,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var n: usize = 4096;
|
||||||
|
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
||||||
|
.{ id, encoded[0..n] },
|
||||||
|
);
|
||||||
|
while (n < encoded.len) : (n += 4096) {
|
||||||
|
const end: usize = @min(n + 4096, encoded.len);
|
||||||
|
const m: u2 = if (end == encoded.len) 0 else 1;
|
||||||
|
try std.fmt.format(
|
||||||
|
writer,
|
||||||
|
"\x1b_Gm={d};{s}\x1b\\",
|
||||||
|
.{
|
||||||
|
m,
|
||||||
|
encoded[n..end],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try tty.buffered_writer.flush();
|
||||||
|
return Image{
|
||||||
|
.id = id,
|
||||||
|
.width = img.width,
|
||||||
|
.height = img.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue