add(element/scrollable): implement content provider
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m49s
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:
@@ -38,7 +38,9 @@ pub fn App(comptime E: type) type {
|
|||||||
return struct {
|
return struct {
|
||||||
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
|
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
|
||||||
pub const Container = @import("container.zig").Container(Event);
|
pub const Container = @import("container.zig").Container(Event);
|
||||||
pub const Element = @import("element.zig").Element(Event);
|
const element = @import("element.zig");
|
||||||
|
pub const Element = element.Element(Event);
|
||||||
|
pub const Scrollable = element.Scrollable(Event);
|
||||||
|
|
||||||
queue: Queue(Event, 256),
|
queue: Queue(Event, 256),
|
||||||
thread: ?std.Thread,
|
thread: ?std.Thread,
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ const log = std.log.scoped(.container);
|
|||||||
|
|
||||||
/// Border configuration struct
|
/// Border configuration struct
|
||||||
pub const Border = packed struct {
|
pub const Border = packed struct {
|
||||||
|
// corners:
|
||||||
pub const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
|
pub const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
|
||||||
pub const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
|
pub const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
|
||||||
|
// separator.line:
|
||||||
pub const line: [2]u21 = .{ '│', '─' };
|
pub const line: [2]u21 = .{ '│', '─' };
|
||||||
pub const dotted: [2]u21 = .{ '┆', '┄' };
|
pub const dotted: [2]u21 = .{ '┆', '┄' };
|
||||||
pub const double: [2]u21 = .{ '║', '═' };
|
pub const double: [2]u21 = .{ '║', '═' };
|
||||||
|
|
||||||
/// Color to use for the border
|
/// Color to use for the border
|
||||||
color: Color = .default,
|
color: Color = .default,
|
||||||
/// Configure the corner type to be used for the border
|
/// Configure the corner type to be used for the border
|
||||||
@@ -277,6 +280,7 @@ pub fn Container(comptime Event: type) type {
|
|||||||
size.rows,
|
size.rows,
|
||||||
});
|
});
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
try this.element.handle(event);
|
||||||
|
|
||||||
if (this.elements.items.len == 0) break :resize;
|
if (this.elements.items.len == 0) break :resize;
|
||||||
|
|
||||||
@@ -397,6 +401,7 @@ pub fn Container(comptime Event: type) type {
|
|||||||
|
|
||||||
this.properties.border.contents(cells, this.size, this.properties.layout, @truncate(this.elements.items.len));
|
this.properties.border.contents(cells, this.size, this.properties.layout, @truncate(this.elements.items.len));
|
||||||
this.properties.rectangle.contents(cells, this.size);
|
this.properties.rectangle.contents(cells, this.size);
|
||||||
|
// NOTE: Layout has no contents to provide, hence no content method is called (or even exists in the `Layout` struct)
|
||||||
|
|
||||||
try this.element.content(cells, this.size);
|
try this.element.content(cells, this.size);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
//! Interface for Element's which describe the contents of a `Container`.
|
//! Interface for Element's which describe the contents of a `Container`.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const s = @import("size.zig");
|
||||||
|
|
||||||
|
const Container = @import("container.zig").Container;
|
||||||
const Cell = @import("cell.zig");
|
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 {
|
pub fn Element(Event: type) type {
|
||||||
return struct {
|
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?
|
/// 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.
|
/// - 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 {
|
pub fn Template(Event: type) type {
|
||||||
return packed struct {
|
return packed struct {
|
||||||
pub fn element(this: *@This()) Element(Event) {
|
pub fn element(this: *@This()) Element(Event) {
|
||||||
@@ -58,13 +62,99 @@ pub fn Template(Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle(ctx: *anyopaque, event: Event) !void {
|
fn handle(ctx: *anyopaque, event: Event) !void {
|
||||||
_ = ctx;
|
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||||
_ = event;
|
_ = this;
|
||||||
|
switch (event) {
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content(ctx: *anyopaque, cells: []Cell, size: Size) !void {
|
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));
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/main.zig
36
src/main.zig
@@ -77,19 +77,6 @@ pub fn main() !void {
|
|||||||
var element_wrapper: HelloWorldText = .{};
|
var element_wrapper: HelloWorldText = .{};
|
||||||
const element = element_wrapper.element();
|
const element = element_wrapper.element();
|
||||||
|
|
||||||
var container = try App.Container.init(allocator, .{
|
|
||||||
.border = .{
|
|
||||||
.separator = .{
|
|
||||||
.enabled = true,
|
|
||||||
.line = .double,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.layout = .{
|
|
||||||
.gap = 2,
|
|
||||||
.padding = .all(5),
|
|
||||||
.direction = .vertical,
|
|
||||||
},
|
|
||||||
}, .{});
|
|
||||||
var box = try App.Container.init(allocator, .{
|
var box = try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .blue },
|
.rectangle = .{ .fill = .blue },
|
||||||
.layout = .{
|
.layout = .{
|
||||||
@@ -107,7 +94,28 @@ pub fn main() !void {
|
|||||||
try box.append(try App.Container.init(allocator, .{
|
try box.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .light_green },
|
.rectangle = .{ .fill = .light_green },
|
||||||
}, .{}));
|
}, .{}));
|
||||||
try container.append(box);
|
defer box.deinit();
|
||||||
|
|
||||||
|
var scrollable: App.Scrollable = .{
|
||||||
|
.container = box,
|
||||||
|
.vertical = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var container = try App.Container.init(allocator, .{
|
||||||
|
.border = .{
|
||||||
|
.separator = .{
|
||||||
|
.enabled = true,
|
||||||
|
.line = .double,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.layout = .{
|
||||||
|
.gap = 2,
|
||||||
|
.padding = .all(5),
|
||||||
|
.direction = .vertical,
|
||||||
|
},
|
||||||
|
}, .{});
|
||||||
|
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||||
|
|
||||||
try container.append(try App.Container.init(allocator, .{
|
try container.append(try App.Container.init(allocator, .{
|
||||||
.border = .{
|
.border = .{
|
||||||
.color = .light_blue,
|
.color = .light_blue,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub const Layout = container.Layout;
|
|||||||
pub const Cell = @import("cell.zig");
|
pub const Cell = @import("cell.zig");
|
||||||
pub const Color = color.Color;
|
pub const Color = color.Color;
|
||||||
pub const Key = @import("key.zig");
|
pub const Key = @import("key.zig");
|
||||||
|
pub const Position = size.Position;
|
||||||
pub const Size = size.Size;
|
pub const Size = size.Size;
|
||||||
pub const Style = @import("style.zig");
|
pub const Style = @import("style.zig");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user