WIP: images
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
8f311da873
commit
c99dffaecf
9 changed files with 165 additions and 2 deletions
|
@ -12,6 +12,12 @@ pub fn build(b: *std.Build) void {
|
||||||
});
|
});
|
||||||
vaxis.addImport("ziglyph", ziglyph.module("ziglyph"));
|
vaxis.addImport("ziglyph", ziglyph.module("ziglyph"));
|
||||||
|
|
||||||
|
const zigimg = b.dependency("zigimg", .{
|
||||||
|
.optimize = optimize,
|
||||||
|
.target = target,
|
||||||
|
});
|
||||||
|
vaxis.addImport("zigimg", zigimg.module("zigimg"));
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "vaxis",
|
.name = "vaxis",
|
||||||
.root_source_file = .{ .path = "examples/text_input.zig" },
|
.root_source_file = .{ .path = "examples/text_input.zig" },
|
||||||
|
@ -39,6 +45,7 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
lib_unit_tests.root_module.addImport("ziglyph", ziglyph.module("ziglyph"));
|
lib_unit_tests.root_module.addImport("ziglyph", ziglyph.module("ziglyph"));
|
||||||
|
lib_unit_tests.root_module.addImport("zigimg", zigimg.module("zigimg"));
|
||||||
|
|
||||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
.url = "https://codeberg.org/dude_the_builder/ziglyph/archive/main.tar.gz",
|
.url = "https://codeberg.org/dude_the_builder/ziglyph/archive/main.tar.gz",
|
||||||
.hash = "12208553f3f47e51494e187f4c0e6f6b3844e3993436cad4a0e8c4db4e99645967b5",
|
.hash = "12208553f3f47e51494e187f4c0e6f6b3844e3993436cad4a0e8c4db4e99645967b5",
|
||||||
},
|
},
|
||||||
|
.zigimg = .{
|
||||||
|
.url = "https://github.com/zigimg/zigimg/archive/f6998808f283f8d3c2ef34e8b4af423bc1786f32.tar.gz",
|
||||||
|
.hash = "12202ee5d22ade0c300e9e7eae4c1951bda3d5f236fe1a139eb3613b43e2f12a88db",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|
83
src/Image.zig
Normal file
83
src/Image.zig
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const math = std.math;
|
||||||
|
const testing = std.testing;
|
||||||
|
const zigimg = @import("zigimg");
|
||||||
|
|
||||||
|
const Window = @import("Window.zig");
|
||||||
|
const Winsize = @import("Tty.zig").Winsize;
|
||||||
|
|
||||||
|
const Image = @This();
|
||||||
|
|
||||||
|
pub const Source = union(enum) {
|
||||||
|
/// loads an image from a path. path can be relative to cwd, or absolute
|
||||||
|
path: []const u8,
|
||||||
|
/// loads an image from raw bytes
|
||||||
|
mem: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Protocol = enum {
|
||||||
|
kitty,
|
||||||
|
// TODO: sixel, full block, half block, quad block
|
||||||
|
};
|
||||||
|
|
||||||
|
/// the decoded image
|
||||||
|
img: zigimg.Image,
|
||||||
|
|
||||||
|
/// unique identifier for this image
|
||||||
|
id: u32,
|
||||||
|
|
||||||
|
/// width of the image, in cells
|
||||||
|
cell_width: usize,
|
||||||
|
/// height of the image, in cells
|
||||||
|
cell_height: usize,
|
||||||
|
|
||||||
|
/// initialize a new image
|
||||||
|
pub fn init(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
winsize: Winsize,
|
||||||
|
src: Source,
|
||||||
|
id: u32,
|
||||||
|
) !Image {
|
||||||
|
const img = switch (src) {
|
||||||
|
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
||||||
|
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
||||||
|
};
|
||||||
|
// 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, img.width, pix_per_col) catch 0;
|
||||||
|
const cell_height = math.divCeil(usize, img.height, pix_per_row) catch 0;
|
||||||
|
|
||||||
|
return Image{
|
||||||
|
.img = img,
|
||||||
|
.cell_width = cell_width,
|
||||||
|
.cell_height = cell_height,
|
||||||
|
.id = id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Image) void {
|
||||||
|
self.img.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *Image, win: Window, placement_id: u32) !void {
|
||||||
|
try win.writeImage(win.x_off, win.y_off, self, placement_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "image" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var img = try init(
|
||||||
|
alloc,
|
||||||
|
.{
|
||||||
|
.rows = 1,
|
||||||
|
.cols = 1,
|
||||||
|
.x_pixel = 1,
|
||||||
|
.y_pixel = 1,
|
||||||
|
},
|
||||||
|
.{ .path = "vaxis.png" },
|
||||||
|
);
|
||||||
|
defer img.deinit();
|
||||||
|
try testing.expectEqual(1, img.cell_width);
|
||||||
|
try testing.expectEqual(1, img.cell_height);
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ 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.zig").Placement;
|
||||||
|
const Placement = @import("Screen.zig").Placement;
|
||||||
|
|
||||||
const log = std.log.scoped(.internal_screen);
|
const log = std.log.scoped(.internal_screen);
|
||||||
|
|
||||||
|
@ -35,10 +37,14 @@ 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{
|
||||||
screen.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] = .{
|
||||||
.char = try std.ArrayList(u8).initCapacity(alloc, 1),
|
.char = try std.ArrayList(u8).initCapacity(alloc, 1),
|
||||||
|
@ -52,6 +58,7 @@ 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,11 +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.zig");
|
||||||
|
|
||||||
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,
|
||||||
|
};
|
||||||
|
|
||||||
width: usize = 0,
|
width: usize = 0,
|
||||||
height: usize = 0,
|
height: usize = 0,
|
||||||
|
|
||||||
|
@ -21,11 +29,14 @@ unicode: bool = false,
|
||||||
|
|
||||||
mouse_shape: Shape = .default,
|
mouse_shape: Shape = .default,
|
||||||
|
|
||||||
|
images: std.ArrayList(Placement) = undefined,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
|
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
|
||||||
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),
|
||||||
};
|
};
|
||||||
for (self.buf, 0..) |_, i| {
|
for (self.buf, 0..) |_, i| {
|
||||||
self.buf[i] = .{};
|
self.buf[i] = .{};
|
||||||
|
@ -34,6 +45,7 @@ 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
|
||||||
|
@ -50,3 +62,19 @@ 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);
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
|
|
||||||
const Screen = @import("Screen.zig");
|
const Screen = @import("Screen.zig");
|
||||||
const Cell = @import("cell.zig").Cell;
|
const Cell = @import("cell.zig").Cell;
|
||||||
|
const Image = @import("Image.zig");
|
||||||
const gw = @import("gwidth.zig");
|
const gw = @import("gwidth.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.window);
|
const log = std.log.scoped(.window);
|
||||||
|
@ -68,9 +69,30 @@ 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 a cell to the location in the window
|
||||||
|
pub fn writeImage(
|
||||||
|
self: Window,
|
||||||
|
col: usize,
|
||||||
|
row: usize,
|
||||||
|
img: *Image,
|
||||||
|
placement_id: u32,
|
||||||
|
) !void {
|
||||||
|
if (self.height == 0 or self.width == 0) return;
|
||||||
|
if (self.height <= row or self.width <= col) return;
|
||||||
|
self.screen.writeImage(col, row, 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
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = @import("GraphemeCache.zig");
|
_ = @import("GraphemeCache.zig");
|
||||||
|
_ = @import("Image.zig");
|
||||||
_ = @import("Key.zig");
|
_ = @import("Key.zig");
|
||||||
_ = @import("Mouse.zig");
|
_ = @import("Mouse.zig");
|
||||||
_ = @import("Options.zig");
|
_ = @import("Options.zig");
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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;
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -242,6 +243,7 @@ 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
|
||||||
|
@ -278,6 +280,15 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
var cursor: Style = .{};
|
var cursor: Style = .{};
|
||||||
var link: Hyperlink = .{};
|
var link: Hyperlink = .{};
|
||||||
|
|
||||||
|
// delete remove images from the screen by looping through the
|
||||||
|
// current state and comparing to the next state
|
||||||
|
for (self.screen_last.images.items) |last_img| {
|
||||||
|
const keep: bool = for (self.screen.images.items) |next_img| {
|
||||||
|
if (std.meta.eql(last_img, next_img)) break true;
|
||||||
|
} else false;
|
||||||
|
if (keep) continue;
|
||||||
|
}
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.screen.buf.len) {
|
while (i < self.screen.buf.len) {
|
||||||
const cell = self.screen.buf[i];
|
const cell = self.screen.buf[i];
|
||||||
|
|
BIN
vaxis.png
Normal file
BIN
vaxis.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
Reference in a new issue