9 Commits

Author SHA1 Message Date
0b7d032b11 tag: 0.2.0
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m15s
2025-03-27 21:43:02 +01:00
7e20dd73d9 mod: add missing inline function attribute
Correct example to use the actual `zterm.Error` type accordingly.
2025-03-27 21:41:18 +01:00
182dec6065 release: 0.1.0
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 21s
2025-03-13 19:50:49 +01:00
54af974c2b mod(container): make reposition public and split from resize
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m3s
2025-03-13 19:48:31 +01:00
dddc09b4ce mod(container/element): remove origin: Point argument from Element.content function
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m12s
Renamed `Container.contents` to `Container.content` to be in line with
the corresponding `Element` function name. This has also been done for
the properties structs used by the `Container`.
2025-03-11 07:52:35 +01:00
adda53c5a9 add(test): container size with fixed and growable container children
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 46s
2025-03-10 22:07:34 +01:00
5c1d61eefd rem(element): unnecessary debug loging
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m20s
2025-03-10 21:44:18 +01:00
54c7e19939 fix(container): growth options to dynamically size Containers
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 29s
2025-03-06 22:31:00 +01:00
5457e91b37 fix(container/grow_size): respect vertical / horizontal provided dimensions 2025-03-06 22:07:39 +01:00
18 changed files with 163 additions and 100 deletions

View File

@@ -24,11 +24,11 @@
// This is a [Semantic Version](https://semver.org/). // This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication. // In a future version of Zig it will be used for package deduplication.
.version = "0.0.0", .version = "0.2.0",
// Tracks the earliest Zig version that the package considers to be a // Tracks the earliest Zig version that the package considers to be a
// supported use case. // supported use case.
.minimum_zig_version = "0.14.0-dev.3445+6c3cbb0c8", .minimum_zig_version = "0.15.0-dev.56+d0911786c",
// This field is optional. // This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`. // Each dependency must either provide a `url` and `hash`, or a `path`.
@@ -42,11 +42,9 @@
}, },
}, },
.paths = .{ .paths = .{
"LICENSE",
"build.zig", "build.zig",
"build.zig.zon", "build.zig.zon",
"src", "src",
// For example...
//"LICENSE",
//"README.md",
}, },
} }

View File

@@ -13,10 +13,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
pub fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const y = 2; const y = 2;
const x = size.x / 2 -| (text.len / 2); const x = size.x / 2 -| (text.len / 2);
@@ -151,7 +150,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -14,10 +14,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -65,10 +64,9 @@ const Clickable = struct {
} }
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
const this: *@This() = @ptrCast(@alignCast(ctx)); const this: *@This() = @ptrCast(@alignCast(ctx));
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = size.y / 2 -| (text.len / 2); const row = size.y / 2 -| (text.len / 2);
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -142,7 +140,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -14,10 +14,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -75,10 +74,9 @@ const InputField = struct {
} }
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
const this: *@This() = @ptrCast(@alignCast(ctx)); const this: *@This() = @ptrCast(@alignCast(ctx));
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); 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;
@@ -156,7 +154,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -13,10 +13,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -43,10 +42,9 @@ const HelloWorldText = packed struct {
}; };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = size.y / 2; const row = size.y / 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -193,7 +191,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -12,10 +12,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -39,10 +38,9 @@ const InfoText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -69,16 +67,15 @@ const ErrorNotification = 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) {
.key => |key| if (!key.isAscii()) return error.UnsupportedKey, .key => |key| if (!key.isAscii()) return zterm.Error.TooSmall,
.err => |err| this.msg = err.msg, .err => |err| this.msg = err.msg,
else => {}, else => {},
} }
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
const this: *@This() = @ptrCast(@alignCast(ctx)); const this: *@This() = @ptrCast(@alignCast(ctx));
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); 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.y -| 2; const row = size.y -| 2;
@@ -154,7 +151,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -15,10 +15,9 @@ const QuitText = struct {
}; };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -108,7 +107,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -15,10 +15,9 @@ const QuitText = struct {
}; };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -100,7 +99,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -15,10 +15,9 @@ const QuitText = struct {
}; };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -71,7 +70,12 @@ pub fn main() !void {
.rectangle = .{ .fill = .yellow }, .rectangle = .{ .fill = .yellow },
}, .{})); }, .{}));
} else { } else {
try column.append(try App.Container.init(allocator, .{}, .{})); try column.append(try App.Container.init(allocator, .{
.size = .{
.dim = .{ .y = 4 },
.grow = .horizontal,
},
}, .{}));
} }
try column.append(try App.Container.init(allocator, .{ try column.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .blue }, .rectangle = .{ .fill = .blue },
@@ -111,7 +115,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -15,10 +15,9 @@ const QuitText = struct {
}; };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -99,7 +98,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -12,10 +12,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -95,7 +94,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -12,10 +12,9 @@ const QuitText = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
_ = origin;
const row = 2; const row = 2;
const col = size.x / 2 -| (text.len / 2); const col = size.x / 2 -| (text.len / 2);
@@ -39,11 +38,10 @@ const TextStyles = struct {
return .{ .ptr = this, .vtable = &.{ .content = content } }; return .{ .ptr = this, .vtable = &.{ .content = content } };
} }
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void { fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Point) !void {
@setEvalBranchQuota(50000); @setEvalBranchQuota(50000);
_ = ctx; _ = ctx;
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); 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;
@@ -151,7 +149,8 @@ pub fn main() !void {
} }
try renderer.resize(); try renderer.resize();
container.resize(.{}, renderer.size); container.resize(renderer.size);
container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }

View File

@@ -38,7 +38,7 @@ 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: Point) void { pub fn content(this: @This(), cells: []Cell, size: Point) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
const frame = switch (this.corners) { const frame = switch (this.corners) {
@@ -154,7 +154,7 @@ pub const Rectangle = packed struct {
fill: Color = .default, fill: Color = .default,
// NOTE caller owns `Cells` slice and ensures that `cells.len == size.x * size.y` // NOTE caller owns `Cells` slice and ensures that `cells.len == size.x * size.y`
pub fn contents(this: @This(), cells: []Cell, size: Point) void { pub fn content(this: @This(), cells: []Cell, size: Point) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
for (0..size.y) |row| { for (0..size.y) |row| {
@@ -341,7 +341,7 @@ pub const Layout = packed struct {
} = .line, } = .line,
} = .{}, } = .{},
pub fn contents(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void { pub fn content(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)); 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) {
@@ -616,7 +616,7 @@ pub fn Container(comptime Event: type) type {
try this.elements.append(element); try this.elements.append(element);
} }
fn reposition(this: *@This(), origin: Point) void { pub fn reposition(this: *@This(), origin: Point) void {
const layout = this.properties.layout; const layout = this.properties.layout;
this.origin = origin; this.origin = origin;
this.element.reposition(origin); this.element.reposition(origin);
@@ -690,10 +690,10 @@ pub fn Container(comptime Event: type) type {
.fixed => this.properties.size.dim, .fixed => this.properties.size.dim,
.horizontal => .{ .horizontal => .{
.x = @max(size.x, this.properties.size.dim.x), .x = @max(size.x, this.properties.size.dim.x),
.y = size.y, .y = this.properties.size.dim.y,
}, },
.vertical => .{ .vertical => .{
.x = size.x, .x = this.properties.size.dim.x,
.y = @max(size.y, this.properties.size.dim.y), .y = @max(size.y, this.properties.size.dim.y),
}, },
}; };
@@ -701,7 +701,7 @@ pub fn Container(comptime Event: type) type {
return this.size; return this.size;
} }
// growable implicitly requires the root `Container` to have a set a size property to the size of the available terminal screen /// growable implicitly requires the root `Container` to have a set a size property to the size of the available terminal screen
fn grow_resize(this: *@This(), max_size: Point) void { fn grow_resize(this: *@This(), max_size: Point) void {
log.debug("grow_size: {any}", .{this.size}); log.debug("grow_size: {any}", .{this.size});
const layout = this.properties.layout; const layout = this.properties.layout;
@@ -750,18 +750,30 @@ pub fn Container(comptime Event: type) type {
var growable_children: usize = 0; var growable_children: usize = 0;
var first_growable_child: *@This() = undefined; var first_growable_child: *@This() = undefined;
for (this.elements.items) |*child| { for (this.elements.items) |*child| {
if (child.properties.size.grow != .fixed) { // layout direction side growth
switch (child.properties.size.grow) {
.fixed => continue,
.both => {
if (growable_children == 0) first_growable_child = child; if (growable_children == 0) first_growable_child = child;
growable_children += 1; growable_children += 1;
switch (layout.direction) {
.horizontal => if (child.properties.size.grow != .vertical) {
child.size.y = available;
}, },
.vertical => if (child.properties.size.grow != .horizontal) { .horizontal => if (layout.direction == .horizontal) {
child.size.x = available; if (growable_children == 0) first_growable_child = child;
growable_children += 1;
},
.vertical => if (layout.direction == .vertical) {
if (growable_children == 0) first_growable_child = child;
growable_children += 1;
}, },
} }
// non layout direction side growth
switch (layout.direction) {
.horizontal => if (child.properties.size.grow == .vertical or child.properties.size.grow == .both) {
child.size.y = available;
},
.vertical => if (child.properties.size.grow == .horizontal or child.properties.size.grow == .both) {
child.size.x = available;
},
} }
} }
@@ -833,7 +845,7 @@ pub fn Container(comptime Event: type) type {
for (this.elements.items) |*child| child.grow_resize(child.size); for (this.elements.items) |*child| child.grow_resize(child.size);
} }
pub fn resize(this: *@This(), origin: Point, size: Point) void { pub fn resize(this: *@This(), size: Point) void {
// NOTE assume that this function is only called for the root `Container` // NOTE assume that this function is only called for the root `Container`
const fit_size = this.fit_resize(); const fit_size = this.fit_resize();
// if (fit_size.y > size.y or fit_size.x > size.x) @panic("error: cannot render in available space"); // if (fit_size.y > size.y or fit_size.x > size.x) @panic("error: cannot render in available space");
@@ -850,7 +862,6 @@ pub fn Container(comptime Event: type) type {
}, },
} }
this.grow_resize(this.size); this.grow_resize(this.size);
this.reposition(origin);
} }
pub fn handle(this: *@This(), event: Event) !void { pub fn handle(this: *@This(), event: Event) !void {
@@ -866,17 +877,17 @@ pub fn Container(comptime Event: type) type {
} }
} }
pub fn contents(this: *const @This()) ![]const Cell { pub fn content(this: *const @This()) ![]const Cell {
if (this.size.x == 0 or this.size.y == 0) return Error.TooSmall; if (this.size.x == 0 or this.size.y == 0) return Error.TooSmall;
const cells = try this.allocator.alloc(Cell, @as(usize, this.size.x) * @as(usize, this.size.y)); 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.origin, this.size, this.elements.items); this.properties.layout.content(@This(), cells, this.origin, this.size, this.elements.items);
this.properties.border.contents(cells, this.size); this.properties.border.content(cells, this.size);
this.properties.rectangle.contents(cells, this.size); this.properties.rectangle.content(cells, this.size);
try this.element.content(cells, this.origin, this.size); try this.element.content(cells, this.size);
// DEBUG render corresponding top left corner of this `Container` *red* // DEBUG render corresponding top left corner of this `Container` *red*
// cells[0].style.fg = .red; // cells[0].style.fg = .red;
@@ -892,3 +903,51 @@ test {
_ = Layout; _ = Layout;
_ = Rectangle; _ = Rectangle;
} }
test "Container Fixed and Grow Size Vertical" {
const event = @import("event.zig");
const testing = @import("testing.zig");
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{
.layout = .{ .direction = .vertical },
}, .{});
try container.append(try .init(std.testing.allocator, .{
.size = .{
.dim = .{ .y = 5 },
.grow = .horizontal,
},
.rectangle = .{ .fill = .grey },
}, .{}));
try container.append(try .init(std.testing.allocator, .{
.rectangle = .{ .fill = .red },
}, .{}));
defer container.deinit();
try testing.expectContainerScreen(.{
.y = 20,
.x = 30,
}, &container, @import("test/container/fixed_grow_vertical.zon"));
}
test "Container Fixed and Grow Size Horizontal" {
const event = @import("event.zig");
const testing = @import("testing.zig");
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{}, .{});
try container.append(try .init(std.testing.allocator, .{
.size = .{
.dim = .{ .x = 5 },
.grow = .vertical,
},
.rectangle = .{ .fill = .grey },
}, .{}));
try container.append(try .init(std.testing.allocator, .{
.rectangle = .{ .fill = .red },
}, .{}));
defer container.deinit();
try testing.expectContainerScreen(.{
.y = 20,
.x = 30,
}, &container, @import("test/container/fixed_grow_horizontal.zon"));
}

View File

@@ -16,17 +16,17 @@ pub fn Element(Event: type) type {
resize: ?*const fn (ctx: *anyopaque, size: Point) void = null, resize: ?*const fn (ctx: *anyopaque, size: Point) void = null,
reposition: ?*const fn (ctx: *anyopaque, origin: Point) void = null, reposition: ?*const fn (ctx: *anyopaque, origin: Point) void = null,
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, origin: Point, size: Point) anyerror!void = null, content: ?*const fn (ctx: *anyopaque, cells: []Cell, size: Point) anyerror!void = null,
}; };
/// Resize the corresponding `Element` with the given *size*. /// Resize the corresponding `Element` with the given *size*.
pub fn resize(this: @This(), size: Point) void { pub inline fn resize(this: @This(), size: Point) void {
if (this.vtable.resize) |resize_fn| if (this.vtable.resize) |resize_fn|
resize_fn(this.ptr, size); resize_fn(this.ptr, size);
} }
/// Reposition the corresponding `Element` with the given *origin*. /// Reposition the corresponding `Element` with the given *origin*.
pub fn reposition(this: @This(), origin: Point) void { pub inline fn reposition(this: @This(), origin: Point) void {
if (this.vtable.reposition) |reposition_fn| if (this.vtable.reposition) |reposition_fn|
reposition_fn(this.ptr, origin); reposition_fn(this.ptr, origin);
} }
@@ -58,9 +58,9 @@ pub fn Element(Event: type) type {
/// 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, origin: Point, size: Point) !void { pub inline fn content(this: @This(), cells: []Cell, size: Point) !void {
if (this.vtable.content) |content_fn| if (this.vtable.content) |content_fn|
try content_fn(this.ptr, cells, origin, size); try content_fn(this.ptr, cells, size);
} }
}; };
} }
@@ -83,6 +83,7 @@ pub fn Scrollable(Event: type) type {
.ptr = this, .ptr = this,
.vtable = &.{ .vtable = &.{
.resize = resize, .resize = resize,
.reposition = reposition,
.handle = handle, .handle = handle,
.content = content, .content = content,
}, },
@@ -94,12 +95,15 @@ pub fn Scrollable(Event: type) type {
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?
std.log.debug("----", .{}); this.container.resize(this.size);
this.container.resize(.{}, this.size);
std.log.debug("----", .{});
this.container_size = this.container.size; this.container_size = this.container.size;
} }
fn reposition(ctx: *anyopaque, _: Point) void {
const this: *@This() = @ptrCast(@alignCast(ctx));
this.container.reposition(.{});
}
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) {
@@ -132,13 +136,13 @@ pub fn Scrollable(Event: type) type {
} }
} }
fn render_container(container: Container(Event), cells: []Cell, container_origin: Point, container_size: Point) !void { fn render_container(container: Container(Event), cells: []Cell, container_size: Point) !void {
const size = container.size; const size = container.size;
const origin = container.origin; const origin = container.origin;
const contents = try container.contents(); const contents = try container.content();
defer container.allocator.free(contents); defer container.allocator.free(contents);
const anchor = (@as(usize, origin.y -| container_origin.y) * @as(usize, container_size.x)) + @as(usize, origin.x -| container_origin.x); const anchor = (@as(usize, origin.y) * @as(usize, container_size.x)) + @as(usize, origin.x);
var idx: usize = 0; var idx: usize = 0;
blk: for (0..size.y) |row| { blk: for (0..size.y) |row| {
@@ -150,25 +154,23 @@ pub fn Scrollable(Event: type) type {
} }
} }
for (container.elements.items) |child| try render_container(child, cells, origin, size); for (container.elements.items) |child| try render_container(child, cells, size);
} }
fn content(ctx: *anyopaque, cells: []Cell, origin: Point, size: Point) !void { fn content(ctx: *anyopaque, cells: []Cell, size: Point) !void {
const this: *@This() = @ptrCast(@alignCast(ctx)); const this: *@This() = @ptrCast(@alignCast(ctx));
_ = origin;
std.debug.assert(cells.len == @as(usize, this.size.x) * @as(usize, this.size.y)); 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_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 = 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.content();
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.x) * @as(usize, container_size.y)); 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);
} }
for (this.container.elements.items) |child| try render_container(child, container_cells, container_origin, container_size); for (this.container.elements.items) |child| try render_container(child, container_cells, container_size);
const anchor = (@as(usize, this.anchor.y) * @as(usize, container_size.x)) + @as(usize, this.anchor.x); 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!
@@ -233,7 +235,8 @@ test "scrollable vertical" {
var renderer: testing.Renderer = .init(allocator, size); var renderer: testing.Renderer = .init(allocator, size);
defer renderer.deinit(); defer renderer.deinit();
container.resize(.{}, size); container.resize(size);
container.reposition(.{});
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);
@@ -310,7 +313,8 @@ test "scrollable horizontal" {
var renderer: testing.Renderer = .init(allocator, size); var renderer: testing.Renderer = .init(allocator, size);
defer renderer.deinit(); defer renderer.deinit();
container.resize(.{}, size); container.resize(size);
container.reposition(.{});
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);

View File

@@ -64,7 +64,7 @@ pub const Buffered = struct {
pub fn render(this: *@This(), comptime T: type, container: *T) !void { pub fn render(this: *@This(), comptime T: type, container: *T) !void {
const size: Point = container.size; const size: Point = container.size;
const origin: Point = container.origin; const origin: Point = container.origin;
const cells: []const Cell = try container.contents(); const cells: []const Cell = try container.content();
if (cells.len == 0) return; if (cells.len == 0) return;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -48,7 +48,7 @@ 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: Point = container.size; const size: Point = container.size;
const origin: Point = container.origin; const origin: Point = container.origin;
const cells: []const Cell = try container.contents(); const cells: []const Cell = try container.content();
if (cells.len == 0) return; if (cells.len == 0) return;
@@ -121,7 +121,8 @@ pub fn expectContainerScreen(size: Point, container: *Container(event.SystemEven
defer renderer.deinit(); defer renderer.deinit();
try renderer.resize(size); try renderer.resize(size);
container.resize(.{}, size); container.resize(size);
container.reposition(.{});
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);