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