WIP: use viewport to allow sizes of scroll to extend further than renderable screen

This commit is contained in:
2025-02-12 22:33:03 +01:00
parent 98031dbd1a
commit bbe6f4741e
9 changed files with 119 additions and 51 deletions

View File

@@ -4,13 +4,13 @@ const isTaggedUnion = @import("event.zig").isTaggedUnion;
const Cell = @import("cell.zig");
const Color = @import("color.zig").Color;
const Size = @import("size.zig");
const Size = @import("size.zig").Size;
const Style = @import("style.zig");
const log = std.log.scoped(.container);
/// Border configuration struct
pub const Border = struct {
pub const Border = packed struct {
pub const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
pub const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
/// Color to use for the border
@@ -40,10 +40,10 @@ pub const Border = struct {
}
} = .{},
/// Configure separator borders between child element to added to the layout
separator: struct {
separator: packed struct {
enabled: bool = false,
color: Color = .white,
line: enum {
line: enum(u1) {
line,
dotted,
// TODO: add more variations which could be used for the separator
@@ -52,7 +52,7 @@ pub const Border = struct {
// NOTE: caller owns `cells` slice and ensures that `cells.len == size.cols * size.rows`
pub fn contents(this: @This(), cells: []Cell, size: Size, layout: Layout, len: u16) void {
std.debug.assert(cells.len == size.cols * size.rows);
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
const frame = switch (this.corners) {
.rounded => Border.rounded_border,
@@ -62,7 +62,7 @@ pub const Border = struct {
// render top and bottom border
for (0..size.cols) |col| {
const last_row = (size.rows - 1) * size.cols;
const last_row = @as(usize, size.rows - 1) * @as(usize, size.cols);
if (this.sides.left and col == 0) {
// top left corner
if (this.sides.top) cells[col].cp = frame[0];
@@ -170,7 +170,7 @@ pub const Border = struct {
};
/// Rectangle configuration struct
pub const Rectangle = struct {
pub const Rectangle = packed struct {
/// `Color` to use to fill the `Rectangle` with
/// NOTE: used as background color when rendering! such that it renders the
/// children accordingly without removing the coloring of the `Rectangle`
@@ -178,7 +178,7 @@ pub const Rectangle = struct {
// 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 == size.cols * size.rows);
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
for (0..size.rows) |row| {
for (0..size.cols) |col| {
@@ -200,8 +200,21 @@ pub const Scroll = packed struct {
// TODO: rendering enhancements:
// - render corresponding scroll-bars?
// TODO: does the `size` then actually need to be part of the `Scroll`, as the user should not tamper with the value anyway..
// TODO: should the container size be 'virtual' i.e. the complete
// - content size (which might be larger than the available screen)
// but how would a scrollable small portion of the screen work?
// -> this means that the size can remain and have its meaning, but the
// content needs another abstraction for the scroll
size: Size = .{},
// anchor => position in view of scrollable contents
// cols / rows => view port dimensions
// render => window in window
};
// TODO: can `Layout` become `packed`? -> for that the sizing cannot be a tagged enum!
/// Layout configuration struct
pub const Layout = struct {
/// control the direction in which child elements are laid out
@@ -240,8 +253,8 @@ pub const Layout = struct {
// NOTE: `sizing` cannot be *packed* because of the tagged unions? is this necessary -> I would need to measure the size differences
/// Sizing to be used for the width and height of this element to use
sizing: struct {
width: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
height: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
width: union(enum(u2)) { fit, grow, fixed: u16, percent: u16 } = .fit,
height: union(enum(u2)) { fit, grow, fixed: u16, percent: u16 } = .fit,
} = .{},
};
@@ -251,7 +264,8 @@ pub fn Container(comptime Event: type) type {
}
return struct {
allocator: std.mem.Allocator,
size: Size,
/// Size of actual columns and rows used to render this `Container`
viewport: Size,
properties: Properties,
elements: std.ArrayList(@This()),
@@ -268,7 +282,7 @@ pub fn Container(comptime Event: type) type {
pub fn init(allocator: std.mem.Allocator, properties: Properties) !@This() {
return .{
.allocator = allocator,
.size = .{ .cols = 0, .rows = 0 },
.viewport = .{},
.properties = properties,
.elements = std.ArrayList(@This()).init(allocator),
};
@@ -289,8 +303,16 @@ pub fn Container(comptime Event: type) type {
switch (event) {
.init => log.debug(".init event", .{}),
.resize => |s| resize: {
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
s.anchor.col,
s.anchor.row,
s.cols,
s.rows,
});
this.viewport = s;
// sizing
var size = s;
size.anchor = .{}; // reset relative anchor of size!
const sizing = this.properties.layout.sizing;
switch (sizing.width) {
.fit => {
@@ -301,7 +323,7 @@ pub fn Container(comptime Event: type) type {
// NOTE: this is pretty much the current implementation
},
.fixed => |fix| {
std.debug.assert(fix <= size.cols);
// NOTE: fixed may now even define a larger column / row span for a container (but not everything might be rendered)
size.cols = fix;
},
.percent => |percent| {
@@ -312,13 +334,16 @@ pub fn Container(comptime Event: type) type {
switch (sizing.height) {
.fit => {
// use as much space as necessary (but nothing more than necessary)
// TODO: I need to work out the representation between the
// - `Size` and `Size.Position` with the actual screen size
// - the virtual screen may be larger than the actual screen (can I do this?)
},
.grow => {
// grow use as much space as available by the parent (i.e. the entire width)
// NOTE: this is pretty much the current implementation
},
.fixed => |fix| {
std.debug.assert(fix <= size.rows);
// NOTE: fixed may now even define a larger column / row span for a container (but not everything might be rendered)
size.rows = fix;
},
.percent => |percent| {
@@ -326,14 +351,7 @@ pub fn Container(comptime Event: type) type {
size.rows = @divTrunc(size.rows * percent, 100);
},
}
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
size.anchor.col,
size.anchor.row,
size.cols,
size.rows,
});
this.size = size;
this.properties.scroll.size = size;
if (this.elements.items.len == 0) break :resize;
@@ -446,10 +464,51 @@ pub fn Container(comptime Event: type) type {
}
pub fn contents(this: *const @This()) ![]const Cell {
const cells = try this.allocator.alloc(Cell, this.size.cols * this.size.rows);
@memset(cells, .{}); // reset all cells
this.properties.border.contents(cells, this.size, this.properties.layout, @truncate(this.elements.items.len));
this.properties.rectangle.contents(cells, this.size);
const content = try this.allocator.alloc(Cell, @as(usize, this.properties.scroll.size.cols) * @as(usize, this.properties.scroll.size.rows));
defer this.allocator.free(content);
@memset(content, .{});
const cells = try this.allocator.alloc(Cell, @as(usize, this.viewport.cols) * @as(usize, this.viewport.rows));
@memset(cells, .{});
this.properties.border.contents(content, this.properties.scroll.size, this.properties.layout, @truncate(this.elements.items.len));
this.properties.rectangle.contents(content, this.properties.scroll.size);
const cols = blk: {
var cols: u16 = this.properties.scroll.size.cols;
if (this.properties.scroll.size.cols > this.viewport.cols) {
cols = this.viewport.cols;
}
break :blk cols;
};
const rows = blk: {
var rows: u16 = this.properties.scroll.size.rows;
if (this.properties.scroll.size.rows > this.viewport.rows) {
rows = this.viewport.rows;
}
break :blk rows;
};
const anchor = (this.properties.scroll.size.anchor.row * this.properties.scroll.size.cols) + this.properties.scroll.size.anchor.col;
log.debug("viewport = .{{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
this.viewport.anchor.col,
this.viewport.anchor.row,
this.viewport.cols,
this.viewport.rows,
});
log.debug("size = .{{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
this.properties.scroll.size.anchor.col,
this.properties.scroll.size.anchor.row,
this.properties.scroll.size.cols,
this.properties.scroll.size.rows,
});
for (0..rows) |row| {
for (0..cols) |col| {
// TODO: try to do this with @memcpy instead to improve performance
const cell = content[anchor + (row * this.properties.scroll.size.cols) + col];
cells[row * this.viewport.cols + col].cp = cell.cp;
cells[row * this.viewport.cols + col].style = cell.style;
}
}
return cells;
}
};