ref(event): split resize event into viewport and size event
Viewport event reflects the absolut position and size of a given container (and propagates them to its children). While the size event propagates the content size to its children (and sets their corresponding member values accordingly). Both events are currently only emitted by `Container`s meaning that they don't need to be part of the event loop and that they might be removed later.
This commit is contained in:
11
README.md
11
README.md
@@ -108,6 +108,17 @@ cells of the content (and may be overwritten by child elements contents).
|
||||
The border of an element should be around independent of the scrolling of the
|
||||
contents, just like padding.
|
||||
|
||||
### Positioning and Rendering
|
||||
|
||||
I need to find a way to render the contents of each container independently on the screen. Currently the application has two (yet still married) sizes which it keeps track of:
|
||||
|
||||
- **viewport**: The `Size` of the content on screen. It contains the information about the location (*anchor*) where on the screen and the size of the content to show.
|
||||
- **size**: The actual size of the contents for a given container. This `Size` does contain information about the total content size (rows & cols which may not be equal to the screen space available (i.e. the *viewport*)) and the current *anchor* for the viewport of the contents.
|
||||
|
||||
Manipulating the *size* of a container has implications on the *viewport* of the child elements which actually causes problems when enlarging containers (children lose correct screen location information).
|
||||
|
||||
Should I split the *anchor* and the *size* information from another?
|
||||
|
||||
### Input
|
||||
|
||||
How is the user input handled in the containers? Should there be active
|
||||
|
||||
@@ -32,14 +32,14 @@ pub fn main() !void {
|
||||
},
|
||||
.layout = .{
|
||||
.padding = .all(5),
|
||||
.direction = .vertical,
|
||||
.direction = .horizontal,
|
||||
},
|
||||
});
|
||||
var box = try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .blue },
|
||||
.layout = .{
|
||||
.gap = 1,
|
||||
.direction = .horizontal,
|
||||
.direction = .vertical,
|
||||
.padding = .vertical(1),
|
||||
},
|
||||
});
|
||||
@@ -92,9 +92,7 @@ pub fn main() !void {
|
||||
});
|
||||
}
|
||||
},
|
||||
.err => |err| {
|
||||
log.err("Received {any} with message: {s}", .{ @errorName(err.err), err.msg });
|
||||
},
|
||||
.err => |err| log.err("Received {any} with message: {s}", .{ @errorName(err.err), err.msg }),
|
||||
else => {},
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ pub fn Container(comptime Event: type) type {
|
||||
viewport: Size,
|
||||
/// Size of the contents columns and rows used for the contents of this `Container`
|
||||
/// The anchor of this `Size` corresponds to the contents inside of itself.
|
||||
size: Size = .{},
|
||||
size: Size,
|
||||
properties: Properties,
|
||||
elements: std.ArrayList(@This()),
|
||||
|
||||
@@ -258,6 +258,7 @@ pub fn Container(comptime Event: type) type {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.viewport = .{},
|
||||
.size = .{},
|
||||
.properties = properties,
|
||||
.elements = std.ArrayList(@This()).init(allocator),
|
||||
};
|
||||
@@ -277,137 +278,241 @@ pub fn Container(comptime Event: type) type {
|
||||
pub fn handle(this: *@This(), event: Event) !void {
|
||||
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;
|
||||
|
||||
const size: Size = .{
|
||||
.cols = s.cols,
|
||||
.rows = s.rows,
|
||||
};
|
||||
this.size = size;
|
||||
|
||||
if (this.elements.items.len == 0) break :resize;
|
||||
|
||||
const separator = this.properties.border.separator;
|
||||
const sides = this.properties.border.sides;
|
||||
const padding = this.properties.layout.padding;
|
||||
var gap = this.properties.layout.gap;
|
||||
if (separator.enabled) gap += 1; // the gap will be used for the rendering of the separator line
|
||||
|
||||
const len: u16 = @truncate(this.elements.items.len);
|
||||
const element_cols = blk: {
|
||||
var cols = size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
break :blk @divTrunc(cols, len);
|
||||
};
|
||||
const element_rows = blk: {
|
||||
var rows = size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
break :blk @divTrunc(rows, len);
|
||||
};
|
||||
var offset: u16 = switch (this.properties.layout.direction) {
|
||||
.horizontal => padding.left,
|
||||
.vertical => padding.top,
|
||||
};
|
||||
var overflow = switch (this.properties.layout.direction) {
|
||||
.horizontal => blk: {
|
||||
var cols = size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
break :blk cols - element_cols * len;
|
||||
},
|
||||
.vertical => blk: {
|
||||
var rows = size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
break :blk rows - element_rows * len;
|
||||
},
|
||||
};
|
||||
// TODO: make sure that items cannot underflow in size!
|
||||
// - make their size and position still according (even if outside of the visible space!)
|
||||
// - don't render them then accordingly -> avoid index out of bounce accesses!
|
||||
for (this.elements.items) |*element| {
|
||||
var element_size: Size = undefined;
|
||||
switch (this.properties.layout.direction) {
|
||||
.horizontal => {
|
||||
var cols = element_cols;
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
cols += 1;
|
||||
}
|
||||
element_size = .{
|
||||
.anchor = .{
|
||||
.col = this.viewport.anchor.col + offset,
|
||||
.row = this.viewport.anchor.row,
|
||||
},
|
||||
.cols = cols,
|
||||
.rows = size.rows,
|
||||
};
|
||||
// border
|
||||
if (sides.top) element_size.rows -= 1;
|
||||
if (sides.bottom) element_size.rows -= 1;
|
||||
// padding
|
||||
element_size.anchor.row += padding.top;
|
||||
element_size.rows -= padding.top + padding.bottom;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += cols;
|
||||
},
|
||||
.vertical => {
|
||||
var rows = element_rows;
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
rows += 1;
|
||||
}
|
||||
element_size = .{
|
||||
.anchor = .{
|
||||
.col = this.viewport.anchor.col,
|
||||
.row = this.viewport.anchor.row + offset,
|
||||
},
|
||||
.cols = size.cols,
|
||||
.rows = rows,
|
||||
};
|
||||
// border
|
||||
if (sides.left) element_size.cols -= 1;
|
||||
if (sides.right) element_size.cols -= 1;
|
||||
// padding
|
||||
element_size.anchor.col += padding.left;
|
||||
element_size.cols -= padding.left + padding.right;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += rows;
|
||||
},
|
||||
}
|
||||
|
||||
// border resizing
|
||||
if (sides.top) {
|
||||
element_size.anchor.row += 1;
|
||||
}
|
||||
if (sides.left) {
|
||||
element_size.anchor.col += 1;
|
||||
}
|
||||
|
||||
// padding resizing
|
||||
|
||||
try element.handle(.{ .resize = element_size });
|
||||
}
|
||||
.resize => |size| {
|
||||
// initial resize event (handled by the root container)
|
||||
try this.content_layout(size);
|
||||
try this.viewport_layout(size);
|
||||
},
|
||||
.size => |size| try this.content_layout(size),
|
||||
.viewport => |size| try this.viewport_layout(size),
|
||||
else => for (this.elements.items) |*element| try element.handle(event),
|
||||
}
|
||||
}
|
||||
|
||||
fn viewport_layout(this: *@This(), size: Size) anyerror!void {
|
||||
// NOTE: do not manipulate the values here!
|
||||
log.debug("Event .viewport: .{{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
size.anchor.col,
|
||||
size.anchor.row,
|
||||
size.cols,
|
||||
size.rows,
|
||||
});
|
||||
this.viewport = size;
|
||||
|
||||
// without child elements no size information needs to be propagated further
|
||||
if (this.elements.items.len == 0) return;
|
||||
|
||||
const separator = this.properties.border.separator;
|
||||
const sides = this.properties.border.sides;
|
||||
const padding = this.properties.layout.padding;
|
||||
|
||||
var gap = this.properties.layout.gap;
|
||||
if (separator.enabled) gap += 1; // the gap will be used for the rendering of the separator line
|
||||
|
||||
const len: u16 = @truncate(this.elements.items.len);
|
||||
const element_cols = blk: {
|
||||
var cols = size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
break :blk @divTrunc(cols, len);
|
||||
};
|
||||
const element_rows = blk: {
|
||||
var rows = size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
break :blk @divTrunc(rows, len);
|
||||
};
|
||||
var offset: u16 = switch (this.properties.layout.direction) {
|
||||
.horizontal => padding.left,
|
||||
.vertical => padding.top,
|
||||
};
|
||||
var overflow = switch (this.properties.layout.direction) {
|
||||
.horizontal => blk: {
|
||||
var cols = size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
break :blk cols - element_cols * len;
|
||||
},
|
||||
.vertical => blk: {
|
||||
var rows = size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
break :blk rows - element_rows * len;
|
||||
},
|
||||
};
|
||||
// TODO: make sure that items cannot underflow in size!
|
||||
// - make their size and position still according (even if outside of the visible space!)
|
||||
// - don't render them then accordingly -> avoid index out of bounce accesses!
|
||||
for (this.elements.items) |*element| {
|
||||
var element_size: Size = undefined;
|
||||
switch (this.properties.layout.direction) {
|
||||
.horizontal => {
|
||||
var cols = element_cols;
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
cols += 1;
|
||||
}
|
||||
element_size = .{
|
||||
.anchor = .{
|
||||
.col = size.anchor.col + offset,
|
||||
.row = size.anchor.row,
|
||||
},
|
||||
.cols = cols,
|
||||
.rows = size.rows,
|
||||
};
|
||||
// border
|
||||
if (sides.top) element_size.rows -= 1;
|
||||
if (sides.bottom) element_size.rows -= 1;
|
||||
// padding
|
||||
element_size.anchor.row += padding.top;
|
||||
element_size.rows -= padding.top + padding.bottom;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += cols;
|
||||
},
|
||||
.vertical => {
|
||||
var rows = element_rows;
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
rows += 1;
|
||||
}
|
||||
element_size = .{
|
||||
.anchor = .{
|
||||
.col = size.anchor.col,
|
||||
.row = size.anchor.row + offset,
|
||||
},
|
||||
.cols = size.cols,
|
||||
.rows = rows,
|
||||
};
|
||||
// border
|
||||
if (sides.left) element_size.cols -= 1;
|
||||
if (sides.right) element_size.cols -= 1;
|
||||
// padding
|
||||
element_size.anchor.col += padding.left;
|
||||
element_size.cols -= padding.left + padding.right;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += rows;
|
||||
},
|
||||
}
|
||||
|
||||
// border resizing
|
||||
if (sides.top) element_size.anchor.row += 1;
|
||||
if (sides.left) element_size.anchor.col += 1;
|
||||
|
||||
try element.handle(.{ .viewport = element_size });
|
||||
}
|
||||
}
|
||||
|
||||
fn content_layout(this: *@This(), s: Size) anyerror!void {
|
||||
// TODO: overwrite size with configuration values (i.e. fixed, min size, max size, etc.)
|
||||
const size = s;
|
||||
log.debug("Event .size: .{{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
size.anchor.col,
|
||||
size.anchor.row,
|
||||
size.cols,
|
||||
size.rows,
|
||||
});
|
||||
this.size = size;
|
||||
|
||||
// without child elements no size information needs to be propagated further
|
||||
if (this.elements.items.len == 0) return;
|
||||
|
||||
const separator = this.properties.border.separator;
|
||||
const sides = this.properties.border.sides;
|
||||
const padding = this.properties.layout.padding;
|
||||
|
||||
var gap = this.properties.layout.gap;
|
||||
if (separator.enabled) gap += 1; // the gap will be used for the rendering of the separator line
|
||||
|
||||
const len: u16 = @truncate(this.elements.items.len);
|
||||
const element_cols = blk: {
|
||||
var cols = size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
break :blk @divTrunc(cols, len);
|
||||
};
|
||||
const element_rows = blk: {
|
||||
var rows = size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
break :blk @divTrunc(rows, len);
|
||||
};
|
||||
var offset: u16 = switch (this.properties.layout.direction) {
|
||||
.horizontal => padding.left,
|
||||
.vertical => padding.top,
|
||||
};
|
||||
var overflow = switch (this.properties.layout.direction) {
|
||||
.horizontal => blk: {
|
||||
var cols = size.cols - gap * (len - 1);
|
||||
if (sides.left) cols -= 1;
|
||||
if (sides.right) cols -= 1;
|
||||
cols -= padding.left + padding.right;
|
||||
break :blk cols - element_cols * len;
|
||||
},
|
||||
.vertical => blk: {
|
||||
var rows = size.rows - gap * (len - 1);
|
||||
if (sides.top) rows -= 1;
|
||||
if (sides.bottom) rows -= 1;
|
||||
rows -= padding.top + padding.bottom;
|
||||
break :blk rows - element_rows * len;
|
||||
},
|
||||
};
|
||||
// TODO: make sure that items cannot underflow in size!
|
||||
// - make their size and position still according (even if outside of the visible space!)
|
||||
// - don't render them then accordingly -> avoid index out of bounce accesses!
|
||||
for (this.elements.items) |*element| {
|
||||
var element_size: Size = undefined;
|
||||
switch (this.properties.layout.direction) {
|
||||
.horizontal => {
|
||||
var cols = element_cols;
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
cols += 1;
|
||||
}
|
||||
element_size = .{
|
||||
.cols = cols,
|
||||
.rows = size.rows,
|
||||
};
|
||||
// border
|
||||
if (sides.top) element_size.rows -= 1;
|
||||
if (sides.bottom) element_size.rows -= 1;
|
||||
// padding
|
||||
element_size.rows -= padding.top + padding.bottom;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += cols;
|
||||
},
|
||||
.vertical => {
|
||||
var rows = element_rows;
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
rows += 1;
|
||||
}
|
||||
element_size = .{
|
||||
.cols = size.cols,
|
||||
.rows = rows,
|
||||
};
|
||||
// border
|
||||
if (sides.left) element_size.cols -= 1;
|
||||
if (sides.right) element_size.cols -= 1;
|
||||
// padding
|
||||
element_size.cols -= padding.left + padding.right;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += rows;
|
||||
},
|
||||
}
|
||||
try element.handle(.{ .size = element_size });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contents(this: *const @This()) ![]const Cell {
|
||||
const content_cells = try this.allocator.alloc(Cell, @as(usize, this.size.cols) * @as(usize, this.size.rows));
|
||||
defer this.allocator.free(content_cells);
|
||||
@@ -419,13 +524,6 @@ pub fn Container(comptime Event: type) type {
|
||||
this.properties.border.contents(content_cells, this.size, this.properties.layout, @truncate(this.elements.items.len));
|
||||
this.properties.rectangle.contents(content_cells, this.size);
|
||||
|
||||
log.debug("Content::contents .scroll.size: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||
this.size.anchor.col,
|
||||
this.size.anchor.row,
|
||||
this.size.cols,
|
||||
this.size.rows,
|
||||
});
|
||||
|
||||
const cols = blk: {
|
||||
var cols: u16 = this.size.cols - this.size.anchor.col;
|
||||
if (cols > this.viewport.cols) {
|
||||
|
||||
@@ -21,6 +21,10 @@ pub const SystemEvent = union(enum) {
|
||||
},
|
||||
/// 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 `Container`'s to provide child elements their appropriate viewport information
|
||||
viewport: Size,
|
||||
/// `Size` event emitted by `Container`'s to provide child elements with their available content size
|
||||
size: Size,
|
||||
/// Input key event received from the user
|
||||
key: Key,
|
||||
/// Focus event for mouse interaction
|
||||
|
||||
Reference in New Issue
Block a user