add(element/scrollable): implement content provider
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m49s

However this is only working for the same size as the parent container
(i.e. same `Size`). The `.resize` event for the `Container` of the
scrollable element needs to be the necessary and/or required size for
the contents (regardless of the screen viewport).
This commit is contained in:
2025-02-17 19:58:25 +01:00
parent 7b690d387b
commit 7891af6c6f
5 changed files with 125 additions and 19 deletions

View File

@@ -1,8 +1,11 @@
//! Interface for Element's which describe the contents of a `Container`.
const std = @import("std");
const s = @import("size.zig");
const Container = @import("container.zig").Container;
const Cell = @import("cell.zig");
const Size = @import("size.zig").Size;
const Position = s.Position;
const Size = s.Size;
pub fn Element(Event: type) type {
return struct {
@@ -45,6 +48,7 @@ pub fn Element(Event: type) type {
///
/// TODO: Should elements need to be composible with each other, such that they may build complexer outputs?
/// - the goal would rather be to have re-usable parts of handlers and/or content functions which serve similar functionalities.
/// - composible through empty `Container`'s? -> make sense for the `Scrollable` `Element`
pub fn Template(Event: type) type {
return packed struct {
pub fn element(this: *@This()) Element(Event) {
@@ -58,13 +62,99 @@ pub fn Template(Event: type) type {
}
fn handle(ctx: *anyopaque, event: Event) !void {
_ = ctx;
_ = event;
const this: *@This() = @ptrCast(@alignCast(ctx));
_ = this;
switch (event) {
else => {},
}
}
fn content(ctx: *anyopaque, cells: []Cell, size: Size) !void {
_ = ctx;
const this: *@This() = @ptrCast(@alignCast(ctx));
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
_ = this;
}
};
}
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 = .{},
/// Anchor
anchor: Position = .{},
/// The actual container, that is *scrollable*
container: Container(Event),
/// Enable horizontal scrolling. This also renders a scrollbar (along the bottom of the viewport).
horizontal: bool = false,
/// Enable vertical scrolling. This also renders a scrollbar (along the right of the viewport).
vertical: bool = false,
pub fn element(this: *@This()) Element(Event) {
return .{
.ptr = this,
.vtable = &.{
.handle = handle,
.content = content,
},
};
}
fn handle(ctx: *anyopaque, event: Event) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
switch (event) {
// TODO: emit `.resize` event for the container to set the size for the scrollable `Container`
// - how would I determine the required or necessary `Size`?
.init => try this.container.handle(event),
.resize => |size| {
this.size = size;
// TODO: not just pass through the given size, but rather the size that is necessary for scrollable content
try this.container.handle(.{ .resize = size });
},
else => try this.container.handle(event),
}
}
fn content(ctx: *anyopaque, cells: []Cell, size: Size) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
std.debug.assert(cells.len == @as(usize, this.size.cols) * @as(usize, this.size.rows));
const container_cells = try this.container.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows));
@memset(cells, .{});
{
const container_cells_const = try this.container.contents();
defer this.container.allocator.free(container_cells_const);
const container_size = this.container.size;
std.debug.assert(container_cells_const.len == @as(usize, container_size.cols) * @as(usize, container_size.rows));
@memcpy(container_cells, container_cells_const);
}
for (this.container.elements.items) |*e| {
const e_size = e.size;
const element_cells = try e.contents();
defer e.allocator.free(element_cells);
const anchor = (@as(usize, e_size.anchor.row -| size.anchor.row) * @as(usize, size.cols)) + @as(usize, e_size.anchor.col -| size.anchor.col);
var idx: usize = 0;
blk: for (0..e_size.rows) |row| {
for (0..e_size.cols) |col| {
const cell = element_cells[idx];
idx += 1;
container_cells[anchor + (row * size.cols) + col].style = cell.style;
container_cells[anchor + (row * size.cols) + col].cp = cell.cp;
if (element_cells.len == idx) break :blk;
}
}
}
const anchor = (@as(usize, this.anchor.row) * @as(usize, size.cols)) + @as(usize, this.anchor.col);
for (container_cells, 0..) |cell, idx| {
cells[anchor + idx] = cell;
}
this.container.allocator.free(container_cells);
}
};
}