libvaxis/src/Window.zig
Tim Culverhouse c99dffaecf WIP: images
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
2024-01-25 13:32:22 -06:00

183 lines
5 KiB
Zig

const std = @import("std");
const Screen = @import("Screen.zig");
const Cell = @import("cell.zig").Cell;
const Image = @import("Image.zig");
const gw = @import("gwidth.zig");
const log = std.log.scoped(.window);
const Window = @This();
pub const Size = union(enum) {
expand,
limit: usize,
};
/// horizontal offset from the screen
x_off: usize,
/// vertical offset from the screen
y_off: usize,
/// width of the window. This can't be larger than the terminal screen
width: usize,
/// height of the window. This can't be larger than the terminal screen
height: usize,
screen: *Screen,
/// Creates a new window with offset relative to parent and size clamped to the
/// parent's size. Windows do not retain a reference to their parent and are
/// unaware of resizes.
pub fn initChild(
self: Window,
x_off: usize,
y_off: usize,
width: Size,
height: Size,
) Window {
const resolved_width = switch (width) {
.expand => self.width - x_off,
.limit => |w| blk: {
if (w + x_off > self.width) {
break :blk self.width - x_off;
}
break :blk w;
},
};
const resolved_height = switch (height) {
.expand => self.height - y_off,
.limit => |h| blk: {
if (h + y_off > self.height) {
break :blk self.height - y_off;
}
break :blk h;
},
};
return Window{
.x_off = x_off + self.x_off,
.y_off = y_off + self.y_off,
.width = resolved_width,
.height = resolved_height,
.screen = self.screen,
};
}
/// writes a cell to the location in the window
pub fn writeCell(self: Window, col: usize, row: usize, cell: Cell) void {
if (self.height == 0 or self.width == 0) return;
if (self.height <= row or self.width <= col) return;
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
pub fn clear(self: Window) void {
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
pub fn gwidth(self: Window, str: []const u8) usize {
const m: gw.Method = if (self.screen.unicode) .unicode else .wcwidth;
return gw.gwidth(str, m) catch 1;
}
/// fills the window with the provided cell
pub fn fill(self: Window, cell: Cell) void {
var row: usize = self.y_off;
while (row < (self.height + self.y_off)) : (row += 1) {
var col: usize = self.x_off;
while (col < (self.width + self.x_off)) : (col += 1) {
self.screen.writeCell(col, row, cell);
}
}
}
/// hide the cursor
pub fn hideCursor(self: Window) void {
self.screen.cursor_vis = false;
}
/// show the cursor at the given coordinates, 0 indexed
pub fn showCursor(self: Window, col: usize, row: usize) void {
if (self.height == 0 or self.width == 0) return;
if (self.height <= row or self.width <= col) return;
self.screen.cursor_vis = true;
self.screen.cursor_row = row + self.y_off;
self.screen.cursor_col = col + self.x_off;
}
test "Window size set" {
var parent = Window{
.x_off = 0,
.y_off = 0,
.width = 20,
.height = 20,
.screen = undefined,
};
const child = parent.initChild(1, 1, .expand, .expand);
try std.testing.expectEqual(19, child.width);
try std.testing.expectEqual(19, child.height);
}
test "Window size set too big" {
var parent = Window{
.x_off = 0,
.y_off = 0,
.width = 20,
.height = 20,
.screen = undefined,
};
const child = parent.initChild(0, 0, .{ .limit = 21 }, .{ .limit = 21 });
try std.testing.expectEqual(20, child.width);
try std.testing.expectEqual(20, child.height);
}
test "Window size set too big with offset" {
var parent = Window{
.x_off = 0,
.y_off = 0,
.width = 20,
.height = 20,
.screen = undefined,
};
const child = parent.initChild(10, 10, .{ .limit = 21 }, .{ .limit = 21 });
try std.testing.expectEqual(10, child.width);
try std.testing.expectEqual(10, child.height);
}
test "Window size nested offsets" {
var parent = Window{
.x_off = 1,
.y_off = 1,
.width = 20,
.height = 20,
.screen = undefined,
};
const child = parent.initChild(10, 10, .{ .limit = 21 }, .{ .limit = 21 });
try std.testing.expectEqual(11, child.x_off);
try std.testing.expectEqual(11, child.y_off);
}