ref(event): split Size into two Points (one for the size and one for the anchor / origin)
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 39s
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 39s
This commit is contained in:
16
build.zig
16
build.zig
@@ -5,6 +5,7 @@ pub fn build(b: *std.Build) void {
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const Examples = enum {
|
||||
all,
|
||||
demo,
|
||||
// elements:
|
||||
button,
|
||||
@@ -22,7 +23,7 @@ pub fn build(b: *std.Build) void {
|
||||
errors,
|
||||
};
|
||||
|
||||
const example = b.option(Examples, "example", "Example to build and/or run. (default: demo)") orelse .demo;
|
||||
const example = b.option(Examples, "example", "Example to build and/or run. (default: all)") orelse .all;
|
||||
|
||||
const options = b.addOptions();
|
||||
options.addOption(Examples, "example", example);
|
||||
@@ -153,6 +154,19 @@ pub fn build(b: *std.Build) void {
|
||||
.palette => palette,
|
||||
// error handling:
|
||||
.errors => errors,
|
||||
else => blk: {
|
||||
b.installArtifact(button);
|
||||
b.installArtifact(input);
|
||||
b.installArtifact(scrollable);
|
||||
b.installArtifact(vertical);
|
||||
b.installArtifact(horizontal);
|
||||
b.installArtifact(grid);
|
||||
b.installArtifact(mixed);
|
||||
b.installArtifact(text);
|
||||
b.installArtifact(palette);
|
||||
b.installArtifact(errors);
|
||||
break :blk demo;
|
||||
},
|
||||
};
|
||||
b.installArtifact(exe);
|
||||
|
||||
|
||||
@@ -13,13 +13,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const y = 2;
|
||||
const x = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (y * size.x) + x;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -73,7 +74,7 @@ pub fn main() !void {
|
||||
|
||||
var scrollable: App.Scrollable = .{
|
||||
.container = box,
|
||||
.min_size = .{ .cols = 60 },
|
||||
.min_size = .{ .x = 60 },
|
||||
};
|
||||
|
||||
var container = try App.Container.init(allocator, .{
|
||||
@@ -91,11 +92,11 @@ pub fn main() !void {
|
||||
.color = .light_blue,
|
||||
.sides = .all,
|
||||
},
|
||||
.fixed_size = .{ .cols = 200 },
|
||||
.fixed_size = .{ .x = 200 },
|
||||
}, .{}));
|
||||
try container.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .blue },
|
||||
.fixed_size = .{ .cols = 30 },
|
||||
.fixed_size = .{ .x = 30 },
|
||||
}, .{}));
|
||||
defer container.deinit(); // also de-initializes the children
|
||||
|
||||
@@ -110,7 +111,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| {
|
||||
if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit();
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -52,7 +53,7 @@ const Clickable = struct {
|
||||
fn handle(ctx: *anyopaque, event: App.Event) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
.mouse => |mouse| {
|
||||
.mouse => |mouse| if (mouse.button == .left and mouse.kind == .release) {
|
||||
var value = @intFromEnum(this.color);
|
||||
value += 1;
|
||||
value %= 17;
|
||||
@@ -64,13 +65,14 @@ const Clickable = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = size.rows / 2 -| (text.len / 2);
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const row = size.y / 2 -| (text.len / 2);
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = this.color;
|
||||
@@ -119,7 +121,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.click => |button| {
|
||||
log.info("Clicked with mouse using Button: {s}", .{button});
|
||||
|
||||
@@ -14,13 +14,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -74,15 +75,16 @@ const InputField = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
if (this.input.items.len == 0) return;
|
||||
|
||||
const row = 1;
|
||||
const col = 1;
|
||||
const anchor = (row * size.cols) + col;
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (this.input.items, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .black;
|
||||
@@ -132,7 +134,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.accept => |input| {
|
||||
defer allocator.free(input);
|
||||
|
||||
@@ -13,13 +13,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -42,18 +43,22 @@ const HelloWorldText = packed struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
// NOTE error should only be returned here in case an in-recoverable exception has occurred
|
||||
const row = size.rows / 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const row = size.y / 2;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (0.., text) |idx, char| {
|
||||
cells[anchor + idx].style.fg = .black;
|
||||
cells[anchor + idx].cp = char;
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
cells[anchor + idx].style.bg = .black;
|
||||
cells[anchor + idx].cp = cp;
|
||||
|
||||
// NOTE do not write over the contents of this `Container`'s `Size`
|
||||
if (anchor + idx == cells.len - 1) break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -138,10 +143,10 @@ pub fn main() !void {
|
||||
defer container.deinit();
|
||||
|
||||
// place empty container containing the element of the scrollable Container.
|
||||
var scrollable_top: App.Scrollable = .init(top_box, .{ .rows = 50 });
|
||||
var scrollable_top: App.Scrollable = .init(top_box, .{ .y = 50 });
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable_top.element()));
|
||||
|
||||
var scrollable_bottom: App.Scrollable = .init(bottom_box, .{ .rows = 30 });
|
||||
var scrollable_bottom: App.Scrollable = .init(bottom_box, .{ .y = 30 });
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable_bottom.element()));
|
||||
|
||||
try app.start();
|
||||
@@ -155,7 +160,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
else => {},
|
||||
|
||||
@@ -12,13 +12,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -38,13 +39,14 @@ const InfoText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -64,24 +66,25 @@ const ErrorNotification = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .handle = handle, .content = content } };
|
||||
}
|
||||
|
||||
pub fn handle(ctx: *anyopaque, event: App.Event) !void {
|
||||
fn handle(ctx: *anyopaque, event: App.Event) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
.key => |key| if (!key.isAscii()) return error.UnsupportedKey,
|
||||
.resize => |_| {},
|
||||
.size => |_| {},
|
||||
.err => |err| this.msg = err.msg,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
if (this.msg) |msg| {
|
||||
const row = size.rows -| 2;
|
||||
const col = size.cols -| 2 -| msg.len;
|
||||
const anchor = (row * size.cols) + col;
|
||||
const row = size.y -| 2;
|
||||
const col = size.x -| 2 -| msg.len;
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (msg, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -134,7 +137,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
else => {},
|
||||
|
||||
@@ -15,13 +15,14 @@ const QuitText = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -87,7 +88,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
|
||||
@@ -15,13 +15,14 @@ const QuitText = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -79,7 +80,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
|
||||
@@ -15,13 +15,14 @@ const QuitText = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -91,7 +92,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
|
||||
@@ -15,13 +15,14 @@ const QuitText = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -78,7 +79,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
|
||||
@@ -12,13 +12,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -63,7 +64,7 @@ pub fn main() !void {
|
||||
if (comptime field.value == 0) continue; // zterm.Color.default == 0 -> skip
|
||||
try box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = @enumFromInt(field.value) } }, .{}));
|
||||
}
|
||||
var scrollable: App.Scrollable = .init(box, .{ .cols = 3 * std.meta.fields(zterm.Color).len }); // ensure enough columns to render all colors -> scrollable otherwise
|
||||
var scrollable: App.Scrollable = .init(box, .{ .x = 3 * std.meta.fields(zterm.Color).len }); // ensure enough columns to render all colors -> scrollable otherwise
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||
|
||||
try app.start();
|
||||
@@ -76,7 +77,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
else => {},
|
||||
|
||||
@@ -12,13 +12,14 @@ const QuitText = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
const row = 2;
|
||||
const col = size.cols / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.cols) + col;
|
||||
const col = size.x / 2 -| (text.len / 2);
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
for (text, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .white;
|
||||
@@ -38,10 +39,11 @@ const TextStyles = struct {
|
||||
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
||||
}
|
||||
|
||||
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
||||
@setEvalBranchQuota(50000);
|
||||
_ = ctx;
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
_ = origin;
|
||||
|
||||
var row: usize = 0;
|
||||
var col: usize = 0;
|
||||
@@ -56,9 +58,9 @@ const TextStyles = struct {
|
||||
|
||||
// witouth any emphasis
|
||||
for (text) |cp| {
|
||||
cells[(row * size.cols) + col].style.bg = @enumFromInt(bg_field.value);
|
||||
cells[(row * size.cols) + col].style.fg = @enumFromInt(fg_field.value);
|
||||
cells[(row * size.cols) + col].cp = cp;
|
||||
cells[(row * size.x) + col].style.bg = @enumFromInt(bg_field.value);
|
||||
cells[(row * size.x) + col].style.fg = @enumFromInt(fg_field.value);
|
||||
cells[(row * size.x) + col].cp = cp;
|
||||
col += 1;
|
||||
}
|
||||
|
||||
@@ -68,10 +70,10 @@ const TextStyles = struct {
|
||||
const emphasis: zterm.Style.Emphasis = @enumFromInt(emp_field.value);
|
||||
|
||||
for (text) |cp| {
|
||||
cells[(row * size.cols) + col].style.bg = @enumFromInt(bg_field.value);
|
||||
cells[(row * size.cols) + col].style.fg = @enumFromInt(fg_field.value);
|
||||
cells[(row * size.cols) + col].style.emphasis = &.{emphasis};
|
||||
cells[(row * size.cols) + col].cp = cp;
|
||||
cells[(row * size.x) + col].style.bg = @enumFromInt(bg_field.value);
|
||||
cells[(row * size.x) + col].style.fg = @enumFromInt(fg_field.value);
|
||||
cells[(row * size.x) + col].style.emphasis = &.{emphasis};
|
||||
cells[(row * size.x) + col].cp = cp;
|
||||
col += 1;
|
||||
}
|
||||
}
|
||||
@@ -113,8 +115,8 @@ pub fn main() !void {
|
||||
defer box.deinit();
|
||||
|
||||
var scrollable: App.Scrollable = .init(box, .{
|
||||
.rows = (std.meta.fields(zterm.Color).len - 1) * (std.meta.fields(zterm.Color).len - 2),
|
||||
.cols = std.meta.fields(zterm.Style.Emphasis).len * TextStyles.text.len,
|
||||
.x = std.meta.fields(zterm.Style.Emphasis).len * TextStyles.text.len,
|
||||
.y = (std.meta.fields(zterm.Color).len - 1) * (std.meta.fields(zterm.Color).len - 2),
|
||||
}); // ensure enough rows and/or columns to render all text styles -> scrollable otherwise
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||
|
||||
@@ -128,7 +130,7 @@ pub fn main() !void {
|
||||
switch (event) {
|
||||
.init => continue,
|
||||
.quit => break,
|
||||
.resize => |size| try renderer.resize(size),
|
||||
.size => |size| try renderer.resize(size),
|
||||
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
||||
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
else => {},
|
||||
|
||||
34
src/app.zig
34
src/app.zig
@@ -11,7 +11,7 @@ const isTaggedUnion = event.isTaggedUnion;
|
||||
|
||||
const Mouse = input.Mouse;
|
||||
const Key = input.Key;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
|
||||
const log = std.log.scoped(.app);
|
||||
|
||||
@@ -51,7 +51,6 @@ pub fn App(comptime E: type) type {
|
||||
quit_event: std.Thread.ResetEvent,
|
||||
termios: ?std.posix.termios = null,
|
||||
attached_handler: bool = false,
|
||||
prev_size: Size,
|
||||
|
||||
pub const SignalHandler = struct {
|
||||
context: *anyopaque,
|
||||
@@ -64,7 +63,6 @@ pub fn App(comptime E: type) type {
|
||||
.quit_event = .{},
|
||||
.termios = null,
|
||||
.attached_handler = false,
|
||||
.prev_size = .{},
|
||||
};
|
||||
|
||||
pub fn start(this: *@This()) !void {
|
||||
@@ -101,9 +99,7 @@ pub fn App(comptime E: type) type {
|
||||
try terminal.enableMouseSupport();
|
||||
|
||||
// send initial size afterwards
|
||||
const size = terminal.getTerminalSize();
|
||||
this.postEvent(.{ .resize = size });
|
||||
this.prev_size = size;
|
||||
this.postEvent(.{ .size = terminal.getTerminalSize() });
|
||||
}
|
||||
|
||||
pub fn interrupt(this: *@This()) !void {
|
||||
@@ -148,11 +144,7 @@ pub fn App(comptime E: type) type {
|
||||
|
||||
fn winsizeCallback(ptr: *anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ptr));
|
||||
const size = terminal.getTerminalSize();
|
||||
if (size.cols != this.prev_size.cols or size.rows != this.prev_size.rows) {
|
||||
this.postEvent(.{ .resize = size });
|
||||
this.prev_size = size;
|
||||
}
|
||||
this.postEvent(.{ .size = terminal.getTerminalSize() });
|
||||
}
|
||||
|
||||
var winch_handler: ?SignalHandler = null;
|
||||
@@ -302,8 +294,8 @@ pub fn App(comptime E: type) type {
|
||||
|
||||
const mouse: Mouse = .{
|
||||
.button = button,
|
||||
.col = px -| 1,
|
||||
.row = py -| 1,
|
||||
.x = px -| 1,
|
||||
.y = py -| 1,
|
||||
.kind = blk: {
|
||||
if (motion and button != Mouse.Button.none) {
|
||||
break :blk .drag;
|
||||
@@ -334,19 +326,13 @@ pub fn App(comptime E: type) type {
|
||||
if (std.mem.eql(u8, "48", ps)) {
|
||||
// in band window resize
|
||||
// CSI 48 ; height ; width ; height_pix ; width_pix t
|
||||
const height_char = iter.next() orelse break;
|
||||
const width_char = iter.next() orelse break;
|
||||
const height_char = iter.next() orelse break;
|
||||
|
||||
// TODO only post the event if the size has changed?
|
||||
// because there might be too many resize events (which force a re-draw of the entire screen)
|
||||
const size: Size = .{
|
||||
.rows = std.fmt.parseUnsigned(u16, height_char, 10) catch break,
|
||||
.cols = std.fmt.parseUnsigned(u16, width_char, 10) catch break,
|
||||
};
|
||||
if (size.cols != this.prev_size.cols or size.rows != this.prev_size.rows) {
|
||||
this.postEvent(.{ .resize = size });
|
||||
this.prev_size = size;
|
||||
}
|
||||
this.postEvent(.{ .size = .{
|
||||
.x = std.fmt.parseUnsigned(u16, width_char, 10) catch break,
|
||||
.y = std.fmt.parseUnsigned(u16, height_char, 10) catch break,
|
||||
} });
|
||||
}
|
||||
},
|
||||
'u' => {
|
||||
|
||||
@@ -4,7 +4,7 @@ const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||
|
||||
const Cell = @import("cell.zig");
|
||||
const Color = @import("color.zig").Color;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
const Style = @import("style.zig");
|
||||
const Error = @import("error.zig").Error;
|
||||
|
||||
@@ -38,8 +38,8 @@ pub const Border = packed struct {
|
||||
pub const vertical: @This() = .{ .top = true, .bottom = true };
|
||||
} = .{},
|
||||
|
||||
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
pub fn contents(this: @This(), cells: []Cell, size: Point) void {
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
|
||||
const frame = switch (this.corners) {
|
||||
.rounded => Border.rounded_border,
|
||||
@@ -49,14 +49,14 @@ pub const Border = packed struct {
|
||||
|
||||
// render top and bottom border
|
||||
if (this.sides.top or this.sides.bottom) {
|
||||
for (0..size.cols) |col| {
|
||||
const last_row = @as(usize, size.rows - 1) * @as(usize, size.cols);
|
||||
for (0..size.x) |col| {
|
||||
const last_row = @as(usize, size.y - 1) * @as(usize, size.x);
|
||||
if (this.sides.left and col == 0) {
|
||||
// top left corner
|
||||
if (this.sides.top) cells[col].cp = frame[0];
|
||||
// bottom left corner
|
||||
if (this.sides.bottom) cells[last_row + col].cp = frame[4];
|
||||
} else if (this.sides.right and col == size.cols - 1) {
|
||||
} else if (this.sides.right and col == size.x - 1) {
|
||||
// top right corner
|
||||
if (this.sides.top) cells[col].cp = frame[2];
|
||||
// bottom left corner
|
||||
@@ -75,17 +75,17 @@ pub const Border = packed struct {
|
||||
if (this.sides.left or this.sides.right) {
|
||||
var start: usize = 0;
|
||||
if (this.sides.top) start = 1;
|
||||
var end = size.rows;
|
||||
var end = size.y;
|
||||
if (this.sides.bottom) end -= 1;
|
||||
for (start..end) |row| {
|
||||
const idx = (row * size.cols);
|
||||
const idx = (row * size.x);
|
||||
if (this.sides.left) {
|
||||
cells[idx].cp = frame[3]; // left
|
||||
cells[idx].style.fg = this.color;
|
||||
}
|
||||
if (this.sides.right) {
|
||||
cells[idx + size.cols - 1].cp = frame[3]; // right
|
||||
cells[idx + size.cols - 1].style.fg = this.color;
|
||||
cells[idx + size.x - 1].cp = frame[3]; // right
|
||||
cells[idx + size.x - 1].style.fg = this.color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,8 +104,8 @@ pub const Border = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/border.all.zon"));
|
||||
}
|
||||
|
||||
@@ -122,8 +122,8 @@ pub const Border = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/border.vertical.zon"));
|
||||
}
|
||||
|
||||
@@ -140,8 +140,8 @@ pub const Border = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/border.horizontal.zon"));
|
||||
}
|
||||
};
|
||||
@@ -153,13 +153,13 @@ pub const Rectangle = packed struct {
|
||||
/// children accordingly without removing the coloring of the `Rectangle`
|
||||
fill: Color = .default,
|
||||
|
||||
// NOTE caller owns `Cells` slice and ensures that `cells.len == size.cols * size.rows`
|
||||
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
// NOTE caller owns `Cells` slice and ensures that `cells.len == size.x * size.y`
|
||||
pub fn contents(this: @This(), cells: []Cell, size: Point) void {
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
|
||||
for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
cells[(row * size.cols) + col].style.bg = this.fill;
|
||||
for (0..size.y) |row| {
|
||||
for (0..size.x) |col| {
|
||||
cells[(row * size.x) + col].style.bg = this.fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,8 +178,8 @@ pub const Rectangle = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/rectangle_with_parent_fill_without_padding.zon"));
|
||||
}
|
||||
|
||||
@@ -200,8 +200,8 @@ pub const Rectangle = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/rectangle_with_parent_padding.zon"));
|
||||
}
|
||||
|
||||
@@ -228,8 +228,8 @@ pub const Rectangle = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/rectangle_with_padding.zon"));
|
||||
}
|
||||
|
||||
@@ -259,8 +259,8 @@ pub const Rectangle = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/rectangle_with_gap.zon"));
|
||||
}
|
||||
|
||||
@@ -291,8 +291,8 @@ pub const Rectangle = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/rectangle_with_separator.zon"));
|
||||
}
|
||||
};
|
||||
@@ -341,8 +341,8 @@ pub const Layout = packed struct {
|
||||
} = .line,
|
||||
} = .{},
|
||||
|
||||
pub fn contents(this: @This(), comptime C: type, cells: []Cell, size: Size, children: []const C) void {
|
||||
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
pub fn contents(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void {
|
||||
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
|
||||
if (this.separator.enabled and children.len > 1) {
|
||||
const line_cps: [2]u21 = switch (this.separator.line) {
|
||||
@@ -355,16 +355,16 @@ pub const Layout = packed struct {
|
||||
for (0..children.len - 1) |idx| {
|
||||
const child = children[idx];
|
||||
const anchor = switch (this.direction) {
|
||||
.horizontal => ((@as(usize, child.size.anchor.row) -| @as(usize, size.anchor.row)) * @as(usize, size.cols)) + @as(usize, child.size.anchor.col) + @as(usize, child.size.cols) + gap -| @as(usize, size.anchor.col),
|
||||
.vertical => ((@as(usize, child.size.anchor.row) + @as(usize, child.size.rows) + gap -| @as(usize, size.anchor.row)) * @as(usize, size.cols)) + @as(usize, child.size.anchor.col) -| @as(usize, size.anchor.col),
|
||||
.horizontal => ((@as(usize, child.origin.y) -| @as(usize, origin.y)) * @as(usize, size.x)) + @as(usize, child.origin.x) + @as(usize, child.size.x) + gap -| @as(usize, origin.x),
|
||||
.vertical => ((@as(usize, child.origin.y) + @as(usize, child.size.y) + gap -| @as(usize, origin.y)) * @as(usize, size.x)) + @as(usize, child.origin.x) -| @as(usize, origin.x),
|
||||
};
|
||||
|
||||
switch (this.direction) {
|
||||
.horizontal => for (0..child.size.rows) |row| {
|
||||
cells[anchor + row * size.cols].cp = line_cps[0];
|
||||
cells[anchor + row * size.cols].style.fg = this.separator.color;
|
||||
.horizontal => for (0..child.size.y) |row| {
|
||||
cells[anchor + row * size.x].cp = line_cps[0];
|
||||
cells[anchor + row * size.x].style.fg = this.separator.color;
|
||||
},
|
||||
.vertical => for (0..child.size.cols) |col| {
|
||||
.vertical => for (0..child.size.x) |col| {
|
||||
cells[anchor + col].cp = line_cps[1];
|
||||
cells[anchor + col].style.fg = this.separator.color;
|
||||
},
|
||||
@@ -389,8 +389,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_no_gaps.zon"));
|
||||
}
|
||||
|
||||
@@ -411,8 +411,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_no_gaps_with_padding.zon"));
|
||||
}
|
||||
|
||||
@@ -435,8 +435,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_2x_no_gaps.zon"));
|
||||
}
|
||||
|
||||
@@ -462,8 +462,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_2x_no_gaps_with_border.zon"));
|
||||
}
|
||||
|
||||
@@ -490,8 +490,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_2x_no_gaps_with_padding.zon"));
|
||||
}
|
||||
|
||||
@@ -518,8 +518,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_2x_with_gaps_with_border.zon"));
|
||||
}
|
||||
|
||||
@@ -547,8 +547,8 @@ pub const Layout = packed struct {
|
||||
defer container.deinit();
|
||||
|
||||
try testing.expectContainerScreen(.{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
.y = 20,
|
||||
.x = 30,
|
||||
}, &container, @import("test/container/separator_2x_with_gaps_with_border_with_padding.zon"));
|
||||
}
|
||||
};
|
||||
@@ -559,7 +559,8 @@ pub fn Container(comptime Event: type) type {
|
||||
const Element = @import("element.zig").Element(Event);
|
||||
return struct {
|
||||
allocator: std.mem.Allocator,
|
||||
size: Size,
|
||||
origin: Point,
|
||||
size: Point,
|
||||
properties: Properties,
|
||||
element: Element,
|
||||
elements: std.ArrayList(@This()),
|
||||
@@ -571,7 +572,7 @@ pub fn Container(comptime Event: type) type {
|
||||
border: Border = .{},
|
||||
rectangle: Rectangle = .{},
|
||||
layout: Layout = .{},
|
||||
fixed_size: Size = .{},
|
||||
fixed_size: Point = .{},
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
@@ -581,6 +582,7 @@ pub fn Container(comptime Event: type) type {
|
||||
) !@This() {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.origin = .{},
|
||||
.size = .{},
|
||||
.properties = properties,
|
||||
.element = element,
|
||||
@@ -599,18 +601,18 @@ pub fn Container(comptime Event: type) type {
|
||||
try this.elements.append(element);
|
||||
}
|
||||
|
||||
pub fn position(this: *@This(), pos: Point) !void {
|
||||
log.debug("pos: .{{ .x = {d}, .y = {d} }}", .{ pos.x, pos.y });
|
||||
this.origin = pos;
|
||||
}
|
||||
|
||||
pub fn handle(this: *@This(), event: Event) !void {
|
||||
switch (event) {
|
||||
.resize => |size| resize: {
|
||||
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
size.anchor.col,
|
||||
size.anchor.row,
|
||||
size.cols,
|
||||
size.rows,
|
||||
});
|
||||
.size => |size| resize: {
|
||||
log.debug("Event .size: {{ .x = {d}, .y = {d} }}", .{ size.x, size.y });
|
||||
this.size = size;
|
||||
if (this.properties.fixed_size.cols > 0 and size.cols < this.properties.fixed_size.cols) return Error.TooSmall;
|
||||
if (this.properties.fixed_size.rows > 0 and size.rows < this.properties.fixed_size.rows) return Error.TooSmall;
|
||||
if (this.properties.fixed_size.x > 0 and size.x < this.properties.fixed_size.x) return Error.TooSmall;
|
||||
if (this.properties.fixed_size.y > 0 and size.y < this.properties.fixed_size.y) return Error.TooSmall;
|
||||
|
||||
try this.element.handle(event);
|
||||
|
||||
@@ -618,13 +620,13 @@ pub fn Container(comptime Event: type) type {
|
||||
|
||||
const layout = this.properties.layout;
|
||||
var fixed_size_elements: u16 = 0;
|
||||
var fixed_size: Size = .{};
|
||||
var fixed_size: Point = .{};
|
||||
for (this.elements.items) |element| {
|
||||
switch (layout.direction) {
|
||||
.horizontal => if (element.properties.fixed_size.cols > 0) {
|
||||
.horizontal => if (element.properties.fixed_size.x > 0) {
|
||||
fixed_size_elements += 1;
|
||||
},
|
||||
.vertical => if (element.properties.fixed_size.rows > 0) {
|
||||
.vertical => if (element.properties.fixed_size.y > 0) {
|
||||
fixed_size_elements += 1;
|
||||
},
|
||||
}
|
||||
@@ -632,8 +634,8 @@ pub fn Container(comptime Event: type) type {
|
||||
}
|
||||
// check if the available screen is large enough
|
||||
switch (layout.direction) {
|
||||
.horizontal => if (fixed_size.cols > size.cols) return Error.TooSmall,
|
||||
.vertical => if (fixed_size.rows > size.rows) return Error.TooSmall,
|
||||
.horizontal => if (fixed_size.x > size.x) return Error.TooSmall,
|
||||
.vertical => if (fixed_size.y > size.y) return Error.TooSmall,
|
||||
}
|
||||
const sides = this.properties.border.sides;
|
||||
const padding = layout.padding;
|
||||
@@ -641,28 +643,28 @@ pub fn Container(comptime Event: type) type {
|
||||
if (layout.separator.enabled) gap += 1;
|
||||
|
||||
const len: u16 = @truncate(this.elements.items.len);
|
||||
const element_cols = blk: {
|
||||
var cols = size.cols - fixed_size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
const element_x = blk: {
|
||||
var x = size.x - fixed_size.x - gap * (len - 1);
|
||||
if (sides.left) x -= 1;
|
||||
if (sides.right) x -= 1;
|
||||
x -= padding.left + padding.right;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk @divTrunc(cols, len);
|
||||
break :blk @divTrunc(x, len);
|
||||
} else {
|
||||
break :blk @divTrunc(cols, len - fixed_size_elements);
|
||||
break :blk @divTrunc(x, len - fixed_size_elements);
|
||||
}
|
||||
};
|
||||
const element_rows = blk: {
|
||||
var rows = size.rows - fixed_size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
const element_y = blk: {
|
||||
var y = size.y - fixed_size.y - gap * (len - 1);
|
||||
if (sides.top) y -= 1;
|
||||
if (sides.bottom) y -= 1;
|
||||
y -= padding.top + padding.bottom;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk @divTrunc(rows, len);
|
||||
break :blk @divTrunc(y, len);
|
||||
} else {
|
||||
break :blk @divTrunc(rows, len - fixed_size_elements);
|
||||
break :blk @divTrunc(y, len - fixed_size_elements);
|
||||
}
|
||||
};
|
||||
var offset: u16 = switch (layout.direction) {
|
||||
@@ -671,99 +673,102 @@ pub fn Container(comptime Event: type) type {
|
||||
};
|
||||
var overflow = switch (layout.direction) {
|
||||
.horizontal => blk: {
|
||||
var cols = size.cols - fixed_size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
var x = size.x - fixed_size.x - gap * (len - 1);
|
||||
if (sides.left) x -= 1;
|
||||
if (sides.right) x -= 1;
|
||||
x -= padding.left + padding.right;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk cols - element_cols * len;
|
||||
break :blk x - element_x * len;
|
||||
} else {
|
||||
break :blk cols - element_cols * (len - fixed_size_elements);
|
||||
break :blk x - element_x * (len - fixed_size_elements);
|
||||
}
|
||||
},
|
||||
.vertical => blk: {
|
||||
var rows = size.rows - fixed_size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
var y = size.y - fixed_size.y - gap * (len - 1);
|
||||
if (sides.top) y -= 1;
|
||||
if (sides.bottom) y -= 1;
|
||||
y -= padding.top + padding.bottom;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk rows - element_rows * len;
|
||||
break :blk y - element_y * len;
|
||||
} else {
|
||||
break :blk rows - element_rows * (len - fixed_size_elements);
|
||||
break :blk y - element_y * (len - fixed_size_elements);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
for (this.elements.items) |*element| {
|
||||
var element_size: Size = undefined;
|
||||
var element_size: Point = undefined;
|
||||
var element_origin: Point = undefined;
|
||||
switch (layout.direction) {
|
||||
.horizontal => {
|
||||
// TODO this should not always be the max size property!
|
||||
var cols = blk: {
|
||||
if (element.properties.fixed_size.cols > 0) break :blk element.properties.fixed_size.cols;
|
||||
break :blk element_cols;
|
||||
var x = blk: {
|
||||
if (element.properties.fixed_size.x > 0) break :blk element.properties.fixed_size.x;
|
||||
break :blk element_x;
|
||||
};
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
cols += 1;
|
||||
x += 1;
|
||||
}
|
||||
element_origin = .{
|
||||
.x = this.origin.x + offset,
|
||||
.y = this.origin.y,
|
||||
};
|
||||
element_size = .{
|
||||
.anchor = .{
|
||||
.col = this.size.anchor.col + offset,
|
||||
.row = this.size.anchor.row,
|
||||
},
|
||||
.cols = cols,
|
||||
.rows = size.rows,
|
||||
.x = x,
|
||||
.y = size.y,
|
||||
};
|
||||
// border
|
||||
if (sides.top) element_size.rows -= 1;
|
||||
if (sides.bottom) element_size.rows -= 1;
|
||||
if (sides.top) element_size.y -= 1;
|
||||
if (sides.bottom) element_size.y -= 1;
|
||||
// padding
|
||||
element_size.anchor.row += padding.top;
|
||||
element_size.rows -= padding.top + padding.bottom;
|
||||
element_origin.y += padding.top;
|
||||
element_size.y -= padding.top + padding.bottom;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += cols;
|
||||
offset += x;
|
||||
},
|
||||
.vertical => {
|
||||
var rows = blk: {
|
||||
if (element.properties.fixed_size.rows > 0) break :blk element.properties.fixed_size.rows;
|
||||
break :blk element_rows;
|
||||
var y = blk: {
|
||||
if (element.properties.fixed_size.y > 0) break :blk element.properties.fixed_size.y;
|
||||
break :blk element_y;
|
||||
};
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
rows += 1;
|
||||
y += 1;
|
||||
}
|
||||
element_origin = .{
|
||||
.x = this.origin.x,
|
||||
.y = this.origin.y + offset,
|
||||
};
|
||||
element_size = .{
|
||||
.anchor = .{
|
||||
.col = this.size.anchor.col,
|
||||
.row = this.size.anchor.row + offset,
|
||||
},
|
||||
.cols = size.cols,
|
||||
.rows = rows,
|
||||
.x = size.x,
|
||||
.y = y,
|
||||
};
|
||||
// border
|
||||
if (sides.left) element_size.cols -= 1;
|
||||
if (sides.right) element_size.cols -= 1;
|
||||
if (sides.left) element_size.x -= 1;
|
||||
if (sides.right) element_size.x -= 1;
|
||||
// padding
|
||||
element_size.anchor.col += padding.left;
|
||||
element_size.cols -= padding.left + padding.right;
|
||||
element_origin.x += padding.left;
|
||||
element_size.x -= padding.left + padding.right;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += rows;
|
||||
offset += y;
|
||||
},
|
||||
}
|
||||
|
||||
// border resizing
|
||||
if (sides.top) element_size.anchor.row += 1;
|
||||
if (sides.left) element_size.anchor.col += 1;
|
||||
if (sides.top) element_origin.y += 1;
|
||||
if (sides.left) element_origin.x += 1;
|
||||
|
||||
try element.handle(.{ .resize = element_size });
|
||||
// TODO tell the element its origin
|
||||
try element.position(element_origin);
|
||||
try element.handle(.{ .size = element_size });
|
||||
}
|
||||
},
|
||||
.mouse => |mouse| if (mouse.in(this.size)) {
|
||||
.mouse => |mouse| if (mouse.in(this.origin, this.size)) {
|
||||
try this.element.handle(event);
|
||||
for (this.elements.items) |*element| try element.handle(event);
|
||||
},
|
||||
@@ -775,15 +780,15 @@ pub fn Container(comptime Event: type) type {
|
||||
}
|
||||
|
||||
pub fn contents(this: *const @This()) ![]const Cell {
|
||||
const cells = try this.allocator.alloc(Cell, @as(usize, this.size.cols) * @as(usize, this.size.rows));
|
||||
const cells = try this.allocator.alloc(Cell, @as(usize, this.size.x) * @as(usize, this.size.y));
|
||||
@memset(cells, .{});
|
||||
errdefer this.allocator.free(cells);
|
||||
|
||||
this.properties.layout.contents(@This(), cells, this.size, this.elements.items);
|
||||
this.properties.layout.contents(@This(), cells, this.origin, this.size, this.elements.items);
|
||||
this.properties.border.contents(cells, this.size);
|
||||
this.properties.rectangle.contents(cells, this.size);
|
||||
|
||||
try this.element.content(cells, this.size);
|
||||
try this.element.content(cells, this.origin, this.size);
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
139
src/element.zig
139
src/element.zig
@@ -1,13 +1,11 @@
|
||||
//! Interface for Element's which describe the contents of a `Container`.
|
||||
const std = @import("std");
|
||||
const s = @import("size.zig");
|
||||
const input = @import("input.zig");
|
||||
|
||||
const Container = @import("container.zig").Container;
|
||||
const Cell = @import("cell.zig");
|
||||
const Mouse = input.Mouse;
|
||||
const Position = s.Position;
|
||||
const Size = s.Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
|
||||
pub fn Element(Event: type) type {
|
||||
return struct {
|
||||
@@ -16,11 +14,11 @@ pub fn Element(Event: type) type {
|
||||
|
||||
pub const VTable = struct {
|
||||
handle: ?*const fn (ctx: *anyopaque, event: Event) anyerror!void = null,
|
||||
content: ?*const fn (ctx: *anyopaque, cells: []Cell, size: Size) anyerror!void = null,
|
||||
content: ?*const fn (ctx: *anyopaque, cells: []Cell, origin: Point, size: Point) anyerror!void = null,
|
||||
};
|
||||
|
||||
/// Handle the received event. The event is one of the user provided
|
||||
/// events or a system event, with the exception of the `.resize`
|
||||
/// events or a system event, with the exception of the `.size`
|
||||
/// `Event` as every `Container` already handles that event.
|
||||
///
|
||||
/// In case of user errors this function should return an error. This
|
||||
@@ -32,23 +30,23 @@ pub fn Element(Event: type) type {
|
||||
}
|
||||
|
||||
/// Write content into the `cells` of the `Container`. The associated
|
||||
/// `cells` slice has the size of (`size.cols * size.rows`). The
|
||||
/// `cells` slice has the size of (`size.x * size.y`). The
|
||||
/// renderer will know where to place the contents on the screen.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// - Caller owns `cells` slice and ensures that the size usually by assertion:
|
||||
/// ```zig
|
||||
/// std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
|
||||
/// std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
/// ```
|
||||
///
|
||||
/// - This function should only fail with an error if the error is
|
||||
/// non-recoverable (i.e. an allocation error, system error, etc.).
|
||||
/// Otherwise user specific errors should be caught using the `handle`
|
||||
/// function before the rendering of the `Container` happens.
|
||||
pub inline fn content(this: @This(), cells: []Cell, size: Size) !void {
|
||||
pub inline fn content(this: @This(), cells: []Cell, origin: Point, size: Point) !void {
|
||||
if (this.vtable.content) |content_fn|
|
||||
try content_fn(this.ptr, cells, size);
|
||||
try content_fn(this.ptr, cells, origin, size);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -57,16 +55,17 @@ pub fn Scrollable(Event: type) type {
|
||||
return struct {
|
||||
/// `Size` of the actual contents where the anchor and the size is
|
||||
/// representing the size and location on screen.
|
||||
size: Size = .{},
|
||||
size: Point = .{},
|
||||
/// Minimal `Size` of the scrollable `Container` to be used. If the
|
||||
/// actual scrollable container's size is larger it will be used instead
|
||||
/// (no scrolling will be necessary for that screen size).
|
||||
min_size: Size = .{},
|
||||
min_size: Point = .{},
|
||||
/// `Size` of the `Container` content that is scrollable and mapped to
|
||||
/// the *size* of the `Scrollable` `Element`.
|
||||
container_size: Size = .{},
|
||||
container_size: Point = .{},
|
||||
container_origin: Point = .{},
|
||||
/// Anchor of the viewport of the scrollable `Container`.
|
||||
anchor: Position = .{},
|
||||
anchor: Point = .{},
|
||||
/// The actual `Container`, that is scrollable.
|
||||
container: Container(Event),
|
||||
|
||||
@@ -80,7 +79,7 @@ pub fn Scrollable(Event: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(container: Container(Event), min_size: Size) @This() {
|
||||
pub fn init(container: Container(Event), min_size: Point) @This() {
|
||||
return .{
|
||||
.container = container,
|
||||
.min_size = min_size,
|
||||
@@ -90,33 +89,33 @@ pub fn Scrollable(Event: type) type {
|
||||
fn handle(ctx: *anyopaque, event: Event) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
.resize => |size| {
|
||||
.size => |size| {
|
||||
this.size = size;
|
||||
// TODO scrollbar space - depending on configuration and only if necessary?
|
||||
this.container_size = size.max(this.min_size);
|
||||
this.container_size.anchor = size.anchor;
|
||||
try this.container.handle(.{ .resize = this.container_size });
|
||||
this.container_origin = size; // TODO the size should be a provided origin
|
||||
try this.container.handle(.{ .size = this.container_size });
|
||||
},
|
||||
// TODO other means to scroll except with the mouse? (i.e. Ctrl-u/d, k/j, etc.?)
|
||||
.mouse => |mouse| switch (mouse.button) {
|
||||
Mouse.Button.wheel_up => if (this.container_size.rows > this.size.rows) {
|
||||
this.anchor.row -|= 1;
|
||||
Mouse.Button.wheel_up => if (this.container_size.y > this.size.y) {
|
||||
this.anchor.y -|= 1;
|
||||
},
|
||||
Mouse.Button.wheel_down => if (this.container_size.rows > this.size.rows) {
|
||||
const max_anchor_row = this.container_size.rows -| this.size.rows;
|
||||
this.anchor.row = @min(this.anchor.row + 1, max_anchor_row);
|
||||
Mouse.Button.wheel_down => if (this.container_size.y > this.size.y) {
|
||||
const max_origin_y = this.container_size.y -| this.size.y;
|
||||
this.anchor.y = @min(this.anchor.y + 1, max_origin_y);
|
||||
},
|
||||
Mouse.Button.wheel_left => if (this.container_size.cols > this.size.cols) {
|
||||
this.anchor.col -|= 1;
|
||||
Mouse.Button.wheel_left => if (this.container_size.x > this.size.x) {
|
||||
this.anchor.x -|= 1;
|
||||
},
|
||||
Mouse.Button.wheel_right => if (this.container_size.cols > this.size.cols) {
|
||||
const max_anchor_col = this.container_size.cols -| this.size.cols;
|
||||
this.anchor.col = @min(this.anchor.col + 1, max_anchor_col);
|
||||
Mouse.Button.wheel_right => if (this.container_size.x > this.size.x) {
|
||||
const max_anchor_x = this.container_size.x -| this.size.x;
|
||||
this.anchor.x = @min(this.anchor.x + 1, max_anchor_x);
|
||||
},
|
||||
else => try this.container.handle(.{
|
||||
.mouse = .{
|
||||
.col = mouse.col + this.anchor.col,
|
||||
.row = mouse.row + this.anchor.row,
|
||||
.x = mouse.x + this.anchor.x,
|
||||
.y = mouse.y + this.anchor.y,
|
||||
.button = mouse.button,
|
||||
.kind = mouse.kind,
|
||||
},
|
||||
@@ -126,48 +125,50 @@ pub fn Scrollable(Event: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_container(container: Container(Event), cells: []Cell, container_size: Size) !void {
|
||||
fn render_container(container: Container(Event), cells: []Cell, container_origin: Point, container_size: Point) !void {
|
||||
const size = container.size;
|
||||
const origin = container.origin;
|
||||
const contents = try container.contents();
|
||||
defer container.allocator.free(contents);
|
||||
|
||||
const anchor = (@as(usize, size.anchor.row -| container_size.anchor.row) * @as(usize, container_size.cols)) +
|
||||
@as(usize, size.anchor.col -| container_size.anchor.col);
|
||||
const anchor = (@as(usize, origin.y -| container_origin.y) * @as(usize, container_size.x)) + @as(usize, origin.x -| container_origin.x);
|
||||
|
||||
var idx: usize = 0;
|
||||
blk: for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
cells[anchor + (row * container_size.cols) + col] = contents[idx];
|
||||
blk: for (0..size.y) |row| {
|
||||
for (0..size.x) |col| {
|
||||
cells[anchor + (row * container_size.x) + col] = contents[idx];
|
||||
idx += 1;
|
||||
|
||||
if (contents.len == idx) break :blk;
|
||||
}
|
||||
}
|
||||
|
||||
for (container.elements.items) |child| try render_container(child, cells, size);
|
||||
for (container.elements.items) |child| try render_container(child, cells, origin, size);
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, cells: []Cell, size: Size) !void {
|
||||
fn content(ctx: *anyopaque, cells: []Cell, origin: Point, size: Point) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
std.debug.assert(cells.len == @as(usize, this.size.cols) * @as(usize, this.size.rows));
|
||||
_ = origin; // this should be used
|
||||
std.debug.assert(cells.len == @as(usize, this.size.x) * @as(usize, this.size.y));
|
||||
|
||||
const container_size = this.container.size;
|
||||
const container_cells = try this.container.allocator.alloc(Cell, @as(usize, container_size.cols) * @as(usize, container_size.rows));
|
||||
const container_origin = this.container.origin;
|
||||
const container_cells = try this.container.allocator.alloc(Cell, @as(usize, container_size.x) * @as(usize, container_size.y));
|
||||
{
|
||||
const container_cells_const = try this.container.contents();
|
||||
defer this.container.allocator.free(container_cells_const);
|
||||
std.debug.assert(container_cells_const.len == @as(usize, container_size.cols) * @as(usize, container_size.rows));
|
||||
std.debug.assert(container_cells_const.len == @as(usize, container_size.x) * @as(usize, container_size.y));
|
||||
@memcpy(container_cells, container_cells_const);
|
||||
}
|
||||
|
||||
// FIX this is not resolving the rendering recursively! This means that the content is only shown for the first children
|
||||
for (this.container.elements.items) |child| try render_container(child, container_cells, container_size);
|
||||
for (this.container.elements.items) |child| try render_container(child, container_cells, container_origin, container_size);
|
||||
|
||||
const anchor = (@as(usize, this.anchor.row) * @as(usize, container_size.cols)) + @as(usize, this.anchor.col);
|
||||
const anchor = (@as(usize, this.anchor.y) * @as(usize, container_size.x)) + @as(usize, this.anchor.x);
|
||||
// TODO render scrollbar according to configuration!
|
||||
for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
cells[(row * size.cols) + col] = container_cells[anchor + (row * container_size.cols) + col];
|
||||
for (0..size.y) |row| {
|
||||
for (0..size.x) |col| {
|
||||
cells[(row * size.x) + col] = container_cells[anchor + (row * container_size.x) + col];
|
||||
}
|
||||
}
|
||||
this.container.allocator.free(container_cells);
|
||||
@@ -183,9 +184,9 @@ test "scrollable vertical" {
|
||||
const testing = @import("testing.zig");
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
const size: Size = .{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
const size: Point = .{
|
||||
.x = 30,
|
||||
.y = 20,
|
||||
};
|
||||
|
||||
var box: Container(event.SystemEvent) = try .init(allocator, .{
|
||||
@@ -210,7 +211,7 @@ test "scrollable vertical" {
|
||||
}, .{}));
|
||||
defer box.deinit();
|
||||
|
||||
var scrollable: Scrollable(event.SystemEvent) = .init(box, .{ .rows = size.rows + 15 });
|
||||
var scrollable: Scrollable(event.SystemEvent) = .init(box, .{ .y = size.y + 15 });
|
||||
|
||||
var container: Container(event.SystemEvent) = try .init(allocator, .{
|
||||
.border = .{
|
||||
@@ -223,33 +224,33 @@ test "scrollable vertical" {
|
||||
var renderer: testing.Renderer = .init(allocator, size);
|
||||
defer renderer.deinit();
|
||||
|
||||
try container.handle(.{ .resize = size });
|
||||
try container.handle(.{ .size = size });
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(renderer.size, @import("test/element/scrollable.vertical.top.zon"), renderer.screen);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.top.zon"), renderer.screen);
|
||||
|
||||
// scroll down 15 times (exactly to the end)
|
||||
for (0..15) |_| try container.handle(.{
|
||||
.mouse = .{
|
||||
.button = .wheel_down,
|
||||
.kind = .press,
|
||||
.col = 5,
|
||||
.row = 5,
|
||||
.x = 5,
|
||||
.y = 5,
|
||||
},
|
||||
});
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen);
|
||||
|
||||
// further scrolling down will not change anything
|
||||
try container.handle(.{
|
||||
.mouse = .{
|
||||
.button = .wheel_down,
|
||||
.kind = .press,
|
||||
.col = 5,
|
||||
.row = 5,
|
||||
.x = 5,
|
||||
.y = 5,
|
||||
},
|
||||
});
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen);
|
||||
}
|
||||
|
||||
test "scrollable horizontal" {
|
||||
@@ -257,9 +258,9 @@ test "scrollable horizontal" {
|
||||
const testing = @import("testing.zig");
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
const size: Size = .{
|
||||
.rows = 20,
|
||||
.cols = 30,
|
||||
const size: Point = .{
|
||||
.x = 30,
|
||||
.y = 20,
|
||||
};
|
||||
|
||||
var box: Container(event.SystemEvent) = try .init(allocator, .{
|
||||
@@ -284,7 +285,7 @@ test "scrollable horizontal" {
|
||||
}, .{}));
|
||||
defer box.deinit();
|
||||
|
||||
var scrollable: Scrollable(event.SystemEvent) = .init(box, .{ .cols = size.cols + 15 });
|
||||
var scrollable: Scrollable(event.SystemEvent) = .init(box, .{ .x = size.x + 15 });
|
||||
|
||||
var container: Container(event.SystemEvent) = try .init(allocator, .{
|
||||
.border = .{
|
||||
@@ -297,31 +298,31 @@ test "scrollable horizontal" {
|
||||
var renderer: testing.Renderer = .init(allocator, size);
|
||||
defer renderer.deinit();
|
||||
|
||||
try container.handle(.{ .resize = size });
|
||||
try container.handle(.{ .size = size });
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(renderer.size, @import("test/element/scrollable.horizontal.left.zon"), renderer.screen);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.horizontal.left.zon"), renderer.screen);
|
||||
|
||||
// scroll right 15 times (exactly to the end)
|
||||
for (0..15) |_| try container.handle(.{
|
||||
.mouse = .{
|
||||
.button = .wheel_right,
|
||||
.kind = .press,
|
||||
.col = 5,
|
||||
.row = 5,
|
||||
.x = 5,
|
||||
.y = 5,
|
||||
},
|
||||
});
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(renderer.size, @import("test/element/scrollable.horizontal.right.zon"), renderer.screen);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.horizontal.right.zon"), renderer.screen);
|
||||
|
||||
// further scrolling right will not change anything
|
||||
try container.handle(.{
|
||||
.mouse = .{
|
||||
.button = .wheel_right,
|
||||
.kind = .press,
|
||||
.col = 5,
|
||||
.row = 5,
|
||||
.x = 5,
|
||||
.y = 5,
|
||||
},
|
||||
});
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(renderer.size, @import("test/element/scrollable.horizontal.right.zon"), renderer.screen);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.horizontal.right.zon"), renderer.screen);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const terminal = @import("terminal.zig");
|
||||
|
||||
const Key = input.Key;
|
||||
const Mouse = input.Mouse;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
|
||||
/// System events available to every `zterm.App`
|
||||
pub const SystemEvent = union(enum) {
|
||||
@@ -21,8 +21,8 @@ pub const SystemEvent = union(enum) {
|
||||
/// associated error message
|
||||
msg: []const u8,
|
||||
},
|
||||
/// Resize event emitted by the terminal to derive the `Size` of the current terminal the application is rendered in
|
||||
resize: Size,
|
||||
/// Size event emitted by the terminal to derive the `Size` of the current terminal the application is rendered in
|
||||
size: Point,
|
||||
/// Input key event received from the user
|
||||
key: Key,
|
||||
/// Mouse input event
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! Input module for `zterm`. Contains structs to represent key events and mouse events.
|
||||
const std = @import("std");
|
||||
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
|
||||
pub const Mouse = packed struct {
|
||||
col: u16,
|
||||
row: u16,
|
||||
x: u16,
|
||||
y: u16,
|
||||
button: Button,
|
||||
kind: Kind,
|
||||
|
||||
@@ -35,9 +35,9 @@ pub const Mouse = packed struct {
|
||||
return std.meta.eql(this, other);
|
||||
}
|
||||
|
||||
pub fn in(this: @This(), size: Size) bool {
|
||||
return this.col >= size.anchor.col and this.col <= size.cols + size.anchor.col and
|
||||
this.row >= size.anchor.row and this.row <= size.rows + size.anchor.row;
|
||||
pub fn in(this: @This(), origin: Point, size: Point) bool {
|
||||
return this.x >= origin.x and this.x <= size.x + origin.x and
|
||||
this.y >= origin.y and this.y <= size.y + origin.y;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
60
src/point.zig
Normal file
60
src/point.zig
Normal file
@@ -0,0 +1,60 @@
|
||||
pub const Point = packed struct {
|
||||
x: u16 = 0,
|
||||
y: u16 = 0,
|
||||
|
||||
pub fn add(this: @This(), other: @This()) @This() {
|
||||
return .{
|
||||
.x = this.x + other.x,
|
||||
.y = this.y + other.y,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn max(this: @This(), other: @This()) @This() {
|
||||
return .{
|
||||
.x = @max(this.x, other.x),
|
||||
.y = @max(this.y, other.y),
|
||||
};
|
||||
}
|
||||
|
||||
test "adding" {
|
||||
const testing = @import("std").testing;
|
||||
|
||||
const a: @This() = .{
|
||||
.x = 10,
|
||||
.y = 20,
|
||||
};
|
||||
|
||||
const b: @This() = .{
|
||||
.x = 20,
|
||||
.y = 10,
|
||||
};
|
||||
|
||||
try testing.expectEqual(@This(){
|
||||
.x = 30,
|
||||
.y = 30,
|
||||
}, a.add(b));
|
||||
}
|
||||
|
||||
test "maximum" {
|
||||
const testing = @import("std").testing;
|
||||
|
||||
const a: @This() = .{
|
||||
.x = 10,
|
||||
.y = 20,
|
||||
};
|
||||
|
||||
const b: @This() = .{
|
||||
.x = 20,
|
||||
.y = 10,
|
||||
};
|
||||
|
||||
try testing.expectEqual(@This(){
|
||||
.x = 20,
|
||||
.y = 20,
|
||||
}, a.max(b));
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
_ = Point;
|
||||
}
|
||||
@@ -2,14 +2,13 @@ const std = @import("std");
|
||||
const terminal = @import("terminal.zig");
|
||||
|
||||
const Cell = @import("cell.zig");
|
||||
const Position = @import("size.zig").Position;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
|
||||
/// Double-buffered intermediate rendering pipeline
|
||||
pub const Buffered = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
created: bool,
|
||||
size: Size,
|
||||
size: Point,
|
||||
screen: []Cell,
|
||||
virtual_screen: []Cell,
|
||||
|
||||
@@ -30,9 +29,9 @@ pub const Buffered = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(this: *@This(), size: Size) !void {
|
||||
pub fn resize(this: *@This(), size: Point) !void {
|
||||
this.size = size;
|
||||
const n = @as(usize, size.cols) * @as(usize, size.rows);
|
||||
const n = @as(usize, size.x) * @as(usize, size.y);
|
||||
|
||||
if (!this.created) {
|
||||
this.screen = this.allocator.alloc(Cell, n) catch @panic("render.zig: Out of memory.");
|
||||
@@ -60,18 +59,19 @@ pub const Buffered = struct {
|
||||
|
||||
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
||||
pub fn render(this: *@This(), comptime T: type, container: *T) !void {
|
||||
const size: Size = container.size;
|
||||
const size: Point = container.size;
|
||||
const origin: Point = container.origin;
|
||||
const cells: []const Cell = try container.contents();
|
||||
|
||||
if (cells.len == 0) return;
|
||||
|
||||
var idx: usize = 0;
|
||||
var vs = this.virtual_screen;
|
||||
const anchor: usize = (@as(usize, size.anchor.row) * @as(usize, this.size.cols)) + @as(usize, size.anchor.col);
|
||||
const anchor: usize = (@as(usize, origin.y) * @as(usize, this.size.x)) + @as(usize, origin.x);
|
||||
|
||||
blk: for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
vs[anchor + (row * this.size.cols) + col] = cells[idx];
|
||||
blk: for (0..size.y) |row| {
|
||||
for (0..size.x) |col| {
|
||||
vs[anchor + (row * this.size.x) + col] = cells[idx];
|
||||
idx += 1;
|
||||
|
||||
if (cells.len == idx) break :blk;
|
||||
@@ -89,15 +89,15 @@ pub const Buffered = struct {
|
||||
const writer = terminal.writer();
|
||||
const s = this.screen;
|
||||
const vs = this.virtual_screen;
|
||||
for (0..this.size.rows) |row| {
|
||||
for (0..this.size.cols) |col| {
|
||||
const idx = (row * this.size.cols) + col;
|
||||
for (0..this.size.y) |row| {
|
||||
for (0..this.size.x) |col| {
|
||||
const idx = (row * this.size.x) + col;
|
||||
const cs = s[idx];
|
||||
const cvs = vs[idx];
|
||||
if (cs.eql(cvs)) continue;
|
||||
|
||||
// render differences found in virtual screen
|
||||
try terminal.setCursorPosition(.{ .row = @truncate(row + 1), .col = @truncate(col + 1) });
|
||||
try terminal.setCursorPosition(.{ .y = @truncate(row + 1), .x = @truncate(col + 1) });
|
||||
try cvs.value(writer);
|
||||
// update screen to be the virtual screen for the next frame
|
||||
s[idx] = vs[idx];
|
||||
|
||||
66
src/size.zig
66
src/size.zig
@@ -1,66 +0,0 @@
|
||||
pub const Size = packed struct {
|
||||
anchor: Position = .{},
|
||||
cols: u16 = 0,
|
||||
rows: u16 = 0,
|
||||
|
||||
pub fn add(this: @This(), other: @This()) Size {
|
||||
return .{
|
||||
.cols = this.cols + other.cols,
|
||||
.rows = this.rows + other.rows,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn max(this: @This(), other: @This()) Size {
|
||||
return .{
|
||||
.cols = @max(this.cols, other.cols),
|
||||
.rows = @max(this.rows, other.rows),
|
||||
};
|
||||
}
|
||||
|
||||
test "adding" {
|
||||
const testing = @import("std").testing;
|
||||
|
||||
const a: @This() = .{
|
||||
.anchor = .{ .col = 1, .row = 2 },
|
||||
.cols = 10,
|
||||
.rows = 20,
|
||||
};
|
||||
|
||||
const b: @This() = .{
|
||||
.anchor = .{ .col = 5, .row = 20 },
|
||||
.cols = 20,
|
||||
.rows = 10,
|
||||
};
|
||||
|
||||
try testing.expectEqual(@This(){
|
||||
.cols = 30,
|
||||
.rows = 30,
|
||||
}, a.add(b));
|
||||
}
|
||||
|
||||
test "maximum" {
|
||||
const testing = @import("std").testing;
|
||||
|
||||
const a: @This() = .{
|
||||
.anchor = .{ .col = 1, .row = 2 },
|
||||
.cols = 10,
|
||||
.rows = 20,
|
||||
};
|
||||
|
||||
const b: @This() = .{
|
||||
.anchor = .{ .col = 5, .row = 20 },
|
||||
.cols = 20,
|
||||
.rows = 10,
|
||||
};
|
||||
|
||||
try testing.expectEqual(@This(){
|
||||
.cols = 20,
|
||||
.rows = 20,
|
||||
}, a.max(b));
|
||||
}
|
||||
};
|
||||
|
||||
pub const Position = packed struct {
|
||||
col: u16 = 0,
|
||||
row: u16 = 0,
|
||||
};
|
||||
@@ -4,8 +4,8 @@ const ctlseqs = @import("ctlseqs.zig");
|
||||
const input = @import("input.zig");
|
||||
|
||||
const Key = input.Key;
|
||||
const Position = @import("size.zig").Position;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
const Size = @import("point.zig").Point;
|
||||
const Cell = @import("cell.zig");
|
||||
|
||||
const log = std.log.scoped(.terminal);
|
||||
@@ -23,7 +23,7 @@ pub const ReportMode = enum {
|
||||
pub fn getTerminalSize() Size {
|
||||
var ws: std.posix.winsize = undefined;
|
||||
_ = std.posix.system.ioctl(std.posix.STDIN_FILENO, std.posix.T.IOCGWINSZ, @intFromPtr(&ws));
|
||||
return .{ .cols = ws.col, .rows = ws.row };
|
||||
return .{ .x = ws.col, .y = ws.row };
|
||||
}
|
||||
|
||||
pub fn saveScreen() !void {
|
||||
@@ -89,9 +89,9 @@ pub fn writer() Writer {
|
||||
return .{ .context = .{} };
|
||||
}
|
||||
|
||||
pub fn setCursorPosition(pos: Position) !void {
|
||||
pub fn setCursorPosition(pos: Point) !void {
|
||||
var buf: [64]u8 = undefined;
|
||||
const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.row, pos.col });
|
||||
const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.y, pos.x });
|
||||
_ = try std.posix.write(std.posix.STDIN_FILENO, value);
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ pub fn getCursorPosition() !Size.Position {
|
||||
}
|
||||
|
||||
return .{
|
||||
.row = try std.fmt.parseInt(u16, row[0..ridx], 10) - 1,
|
||||
.col = try std.fmt.parseInt(u16, col[0..cidx], 10) - 1,
|
||||
.x = try std.fmt.parseInt(u16, col[0..cidx], 10) - 1,
|
||||
.y = try std.fmt.parseInt(u16, row[0..ridx], 10) - 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ const Container = @import("container.zig").Container;
|
||||
|
||||
const Cell = @import("cell.zig");
|
||||
const DisplayWidth = @import("DisplayWidth");
|
||||
const Position = @import("size.zig").Position;
|
||||
const Size = @import("size.zig").Size;
|
||||
const Point = @import("point.zig").Point;
|
||||
|
||||
// TODO how would I describe the expected screens?
|
||||
// - including styling?
|
||||
@@ -15,11 +14,11 @@ const Size = @import("size.zig").Size;
|
||||
/// Single-buffer test rendering pipeline for testing purposes.
|
||||
pub const Renderer = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
size: Size,
|
||||
size: Point,
|
||||
screen: []Cell,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, size: Size) @This() {
|
||||
const screen = allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("testing.zig: Out of memory.");
|
||||
pub fn init(allocator: std.mem.Allocator, size: Point) @This() {
|
||||
const screen = allocator.alloc(Cell, @as(usize, size.x) * @as(usize, size.y)) catch @panic("testing.zig: Out of memory.");
|
||||
@memset(screen, .{});
|
||||
|
||||
return .{
|
||||
@@ -33,9 +32,9 @@ pub const Renderer = struct {
|
||||
this.allocator.free(this.screen);
|
||||
}
|
||||
|
||||
pub fn resize(this: *@This(), size: Size) !void {
|
||||
pub fn resize(this: *@This(), size: Point) !void {
|
||||
this.size = size;
|
||||
const n = @as(usize, size.cols) * @as(usize, size.rows);
|
||||
const n = @as(usize, size.cols) * @as(usize, size.y);
|
||||
|
||||
this.allocator.free(this.screen);
|
||||
this.screen = this.allocator.alloc(Cell, n) catch @panic("testing.zig: Out of memory.");
|
||||
@@ -47,21 +46,22 @@ pub const Renderer = struct {
|
||||
}
|
||||
|
||||
pub fn render(this: *@This(), comptime T: type, container: *const T) !void {
|
||||
const size: Size = container.size;
|
||||
const size: Point = container.size;
|
||||
const origin: Point = container.origin;
|
||||
const cells: []const Cell = try container.contents();
|
||||
|
||||
if (cells.len == 0) return;
|
||||
|
||||
var idx: usize = 0;
|
||||
const anchor = (@as(usize, size.anchor.row) * @as(usize, this.size.cols)) + @as(usize, size.anchor.col);
|
||||
const anchor = (@as(usize, origin.y) * @as(usize, this.size.x)) + @as(usize, origin.x);
|
||||
|
||||
blk: for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
blk: for (0..size.y) |row| {
|
||||
for (0..size.x) |col| {
|
||||
const cell = cells[idx];
|
||||
idx += 1;
|
||||
|
||||
this.screen[anchor + (row * this.size.cols) + col].style = cell.style;
|
||||
this.screen[anchor + (row * this.size.cols) + col].cp = cell.cp;
|
||||
this.screen[anchor + (row * this.size.x) + col].style = cell.style;
|
||||
this.screen[anchor + (row * this.size.x) + col].cp = cell.cp;
|
||||
|
||||
if (cells.len == idx) break :blk;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ pub const Renderer = struct {
|
||||
/// var renderer: testing.Renderer = .init(allocator, size);
|
||||
/// defer renderer.deinit();
|
||||
///
|
||||
/// try container.handle(.{ .resize = size });
|
||||
/// try container.handle(.{ .size = size });
|
||||
/// try renderer.render(Container(event.SystemEvent), &container);
|
||||
/// try renderer.save(file.writer());
|
||||
/// ```
|
||||
@@ -115,34 +115,34 @@ pub const Renderer = struct {
|
||||
/// .cols = 30,
|
||||
/// }, &container, @import("test/container/border.all.zon"));
|
||||
/// ```
|
||||
pub fn expectContainerScreen(size: Size, container: *Container(event.SystemEvent), expected: []const Cell) !void {
|
||||
pub fn expectContainerScreen(size: Point, container: *Container(event.SystemEvent), expected: []const Cell) !void {
|
||||
const allocator = std.testing.allocator;
|
||||
var renderer: Renderer = .init(allocator, size);
|
||||
defer renderer.deinit();
|
||||
|
||||
try container.handle(.{ .resize = size });
|
||||
try container.handle(.{ .size = size });
|
||||
try renderer.render(Container(event.SystemEvent), container);
|
||||
|
||||
try expectEqualCells(renderer.size, expected, renderer.screen);
|
||||
try expectEqualCells(.{}, renderer.size, expected, renderer.screen);
|
||||
}
|
||||
|
||||
/// This function is intended to be used only in tests. Test if the two
|
||||
/// provided cell arrays are identical. Usually the `Cell` slices are
|
||||
/// the contents of a given screen from the `zterm.testing.Renderer`. See
|
||||
/// `zterm.testing.expectContainerScreen` for an example usage.
|
||||
pub fn expectEqualCells(size: Size, expected: []const Cell, actual: []const Cell) !void {
|
||||
pub fn expectEqualCells(origin: Point, size: Point, expected: []const Cell, actual: []const Cell) !void {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
try std.testing.expectEqual(expected.len, actual.len);
|
||||
try std.testing.expectEqual(expected.len, @as(usize, size.rows) * @as(usize, size.cols));
|
||||
try std.testing.expectEqual(expected.len, @as(usize, size.y) * @as(usize, size.x));
|
||||
|
||||
var expected_cps = try std.ArrayList(Cell).initCapacity(allocator, size.cols -| size.anchor.col);
|
||||
var expected_cps = try std.ArrayList(Cell).initCapacity(allocator, size.x);
|
||||
defer expected_cps.deinit();
|
||||
|
||||
var actual_cps = try std.ArrayList(Cell).initCapacity(allocator, size.cols -| size.anchor.col);
|
||||
var actual_cps = try std.ArrayList(Cell).initCapacity(allocator, size.x);
|
||||
defer actual_cps.deinit();
|
||||
|
||||
var output = try std.ArrayList(u8).initCapacity(allocator, expected_cps.capacity * actual_cps.capacity + 5 * size.rows);
|
||||
var output = try std.ArrayList(u8).initCapacity(allocator, expected_cps.capacity * actual_cps.capacity + 5 * size.y);
|
||||
defer output.deinit();
|
||||
|
||||
var buffer = std.io.bufferedWriter(output.writer());
|
||||
@@ -155,23 +155,22 @@ pub fn expectEqualCells(size: Size, expected: []const Cell, actual: []const Cell
|
||||
defer dwd.deinit();
|
||||
const dw: DisplayWidth = .{ .data = &dwd };
|
||||
|
||||
const expected_centered = try dw.center(allocator, "Expected Screen", size.cols, " ");
|
||||
const expected_centered = try dw.center(allocator, "Expected Screen", size.x, " ");
|
||||
defer allocator.free(expected_centered);
|
||||
|
||||
const actual_centered = try dw.center(allocator, "Actual Screen", size.cols, " ");
|
||||
const actual_centered = try dw.center(allocator, "Actual Screen", size.x, " ");
|
||||
defer allocator.free(actual_centered);
|
||||
|
||||
try writer.print("Screens are not equivalent.\n{s} ┆ {s}\n", .{ expected_centered, actual_centered });
|
||||
|
||||
const anchor = (size.anchor.row * size.cols) + size.anchor.col;
|
||||
for (size.anchor.row..size.rows) |row| {
|
||||
for (origin.y..size.y) |row| {
|
||||
defer {
|
||||
expected_cps.clearRetainingCapacity();
|
||||
actual_cps.clearRetainingCapacity();
|
||||
}
|
||||
for (size.anchor.col..size.cols) |col| {
|
||||
const expected_cell = expected[anchor + (row * size.cols) + col];
|
||||
const actual_cell = actual[anchor + (row * size.cols) + col];
|
||||
for (origin.x..size.x) |col| {
|
||||
const expected_cell = expected[(row * size.x) + col];
|
||||
const actual_cell = actual[(row * size.x) + col];
|
||||
|
||||
if (!expected_cell.eql(actual_cell)) differ = true;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// private imports
|
||||
const container = @import("container.zig");
|
||||
const color = @import("color.zig");
|
||||
const size = @import("size.zig");
|
||||
const size = @import("point.zig");
|
||||
|
||||
// public exports
|
||||
pub const input = @import("input.zig");
|
||||
@@ -24,8 +24,7 @@ pub const Cell = @import("cell.zig");
|
||||
pub const Color = color.Color;
|
||||
pub const Key = input.Key;
|
||||
pub const Mouse = input.Mouse;
|
||||
pub const Position = size.Position;
|
||||
pub const Size = size.Size;
|
||||
pub const Point = @import("point.zig").Point;
|
||||
pub const Style = @import("style.zig");
|
||||
|
||||
test {
|
||||
@@ -33,9 +32,9 @@ test {
|
||||
_ = @import("container.zig");
|
||||
_ = @import("queue.zig");
|
||||
_ = @import("error.zig");
|
||||
_ = @import("point.zig");
|
||||
|
||||
_ = color;
|
||||
_ = size;
|
||||
|
||||
_ = Cell;
|
||||
_ = Key;
|
||||
|
||||
Reference in New Issue
Block a user