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