add(sizing): grow configuration
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m14s
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m14s
Currently the 'grid' and 'mixed' examples are not working yet.
This commit is contained in:
@@ -61,7 +61,9 @@ pub fn main() !void {
|
|||||||
.padding = .vertical(2),
|
.padding = .vertical(2),
|
||||||
.direction = .vertical,
|
.direction = .vertical,
|
||||||
},
|
},
|
||||||
.size = .{ .y = 90 },
|
.size = .{
|
||||||
|
.dim = .{ .y = 90 },
|
||||||
|
},
|
||||||
}, .{});
|
}, .{});
|
||||||
try box.append(try App.Container.init(allocator, .{
|
try box.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .light_green },
|
.rectangle = .{ .fill = .light_green },
|
||||||
@@ -91,11 +93,15 @@ pub fn main() !void {
|
|||||||
.color = .light_blue,
|
.color = .light_blue,
|
||||||
.sides = .all,
|
.sides = .all,
|
||||||
},
|
},
|
||||||
.size = .{ .x = 100 },
|
.size = .{
|
||||||
|
.dim = .{ .x = 100 },
|
||||||
|
},
|
||||||
}, .{}));
|
}, .{}));
|
||||||
try container.append(try App.Container.init(allocator, .{
|
try container.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .blue },
|
.rectangle = .{ .fill = .blue },
|
||||||
.size = .{ .x = 30 },
|
.size = .{
|
||||||
|
.dim = .{ .x = 30 },
|
||||||
|
},
|
||||||
}, .{}));
|
}, .{}));
|
||||||
defer container.deinit(); // also de-initializes the children
|
defer container.deinit(); // also de-initializes the children
|
||||||
|
|
||||||
|
|||||||
@@ -98,15 +98,21 @@ pub fn main() !void {
|
|||||||
}, .{});
|
}, .{});
|
||||||
try top_box.append(try App.Container.init(allocator, .{
|
try top_box.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .light_green },
|
.rectangle = .{ .fill = .light_green },
|
||||||
.size = .{ .y = 30 },
|
.size = .{
|
||||||
|
.dim = .{ .y = 30 },
|
||||||
|
},
|
||||||
}, .{}));
|
}, .{}));
|
||||||
try top_box.append(try App.Container.init(allocator, .{
|
try top_box.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .light_green },
|
.rectangle = .{ .fill = .light_green },
|
||||||
.size = .{ .y = 5 },
|
.size = .{
|
||||||
|
.dim = .{ .y = 5 },
|
||||||
|
},
|
||||||
}, element));
|
}, element));
|
||||||
try top_box.append(try App.Container.init(allocator, .{
|
try top_box.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .light_green },
|
.rectangle = .{ .fill = .light_green },
|
||||||
.size = .{ .y = 2 },
|
.size = .{
|
||||||
|
.dim = .{ .y = 2 },
|
||||||
|
},
|
||||||
}, .{}));
|
}, .{}));
|
||||||
defer top_box.deinit();
|
defer top_box.deinit();
|
||||||
|
|
||||||
@@ -123,6 +129,9 @@ pub fn main() !void {
|
|||||||
.direction = .vertical,
|
.direction = .vertical,
|
||||||
.padding = .vertical(1),
|
.padding = .vertical(1),
|
||||||
},
|
},
|
||||||
|
.size = .{
|
||||||
|
.dim = .{ .y = 30 },
|
||||||
|
},
|
||||||
}, .{});
|
}, .{});
|
||||||
try bottom_box.append(try App.Container.init(allocator, .{
|
try bottom_box.append(try App.Container.init(allocator, .{
|
||||||
.rectangle = .{ .fill = .grey },
|
.rectangle = .{ .fill = .grey },
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ pub fn main() !void {
|
|||||||
if (comptime field.value == 0) continue; // zterm.Color.default == 0 -> skip
|
if (comptime field.value == 0) continue; // zterm.Color.default == 0 -> skip
|
||||||
try box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = @enumFromInt(field.value) } }, .{}));
|
try box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = @enumFromInt(field.value) } }, .{}));
|
||||||
}
|
}
|
||||||
var scrollable: App.Scrollable = .init(box, .{ .x = 3 * std.meta.fields(zterm.Color).len }); // ensure enough columns to render all colors -> scrollable otherwise
|
var scrollable: App.Scrollable = .{ .container = box };
|
||||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||||
|
|
||||||
try app.start();
|
try app.start();
|
||||||
|
|||||||
@@ -114,10 +114,7 @@ pub fn main() !void {
|
|||||||
}, text_styles.element());
|
}, text_styles.element());
|
||||||
defer box.deinit();
|
defer box.deinit();
|
||||||
|
|
||||||
var scrollable: App.Scrollable = .init(box, .{
|
var scrollable: App.Scrollable = .{ .container = box };
|
||||||
.x = std.meta.fields(zterm.Style.Emphasis).len * TextStyles.text.len,
|
|
||||||
.y = (std.meta.fields(zterm.Color).len - 1) * (std.meta.fields(zterm.Color).len - 2),
|
|
||||||
}); // ensure enough rows and/or columns to render all text styles -> scrollable otherwise
|
|
||||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||||
|
|
||||||
try app.start();
|
try app.start();
|
||||||
|
|||||||
@@ -352,12 +352,15 @@ pub const Layout = packed struct {
|
|||||||
};
|
};
|
||||||
const gap: u16 = (this.gap + 1) / 2;
|
const gap: u16 = (this.gap + 1) / 2;
|
||||||
|
|
||||||
|
log.debug("origin: {any}, size: {any}", .{ origin, size });
|
||||||
for (0..children.len - 1) |idx| {
|
for (0..children.len - 1) |idx| {
|
||||||
const child = children[idx];
|
const child = children[idx];
|
||||||
const anchor = switch (this.direction) {
|
const anchor = switch (this.direction) {
|
||||||
.horizontal => ((@as(usize, child.origin.y) -| @as(usize, origin.y)) * @as(usize, size.x)) + @as(usize, child.origin.x) + @as(usize, child.size.x) + gap -| @as(usize, origin.x),
|
.horizontal => (@as(usize, child.origin.y) * @as(usize, size.x)) + @as(usize, child.origin.x) + @as(usize, child.size.x) + gap,
|
||||||
.vertical => ((@as(usize, child.origin.y) + @as(usize, child.size.y) + gap -| @as(usize, origin.y)) * @as(usize, size.x)) + @as(usize, child.origin.x) -| @as(usize, origin.x),
|
.vertical => ((@as(usize, child.origin.y) + @as(usize, child.size.y) + gap) * @as(usize, size.x)) + @as(usize, child.origin.x),
|
||||||
};
|
};
|
||||||
|
log.debug("child.origin: {any}, child.size: {any}", .{ child.origin, child.size });
|
||||||
|
log.debug("anchor: {d}", .{anchor});
|
||||||
|
|
||||||
switch (this.direction) {
|
switch (this.direction) {
|
||||||
.horizontal => for (0..child.size.y) |row| {
|
.horizontal => for (0..child.size.y) |row| {
|
||||||
@@ -553,6 +556,17 @@ pub const Layout = packed struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Sizing options which should be used by the `Container`
|
||||||
|
pub const Size = packed struct {
|
||||||
|
dim: Point = .{},
|
||||||
|
grow: enum(u2) {
|
||||||
|
both,
|
||||||
|
fixed,
|
||||||
|
vertical,
|
||||||
|
horizontal,
|
||||||
|
} = .both,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn Container(comptime Event: type) type {
|
pub fn Container(comptime Event: type) type {
|
||||||
if (!isTaggedUnion(Event)) @compileError("Provided user event `Event` for `Container(comptime Event: type)`");
|
if (!isTaggedUnion(Event)) @compileError("Provided user event `Event` for `Container(comptime Event: type)`");
|
||||||
|
|
||||||
@@ -572,10 +586,7 @@ pub fn Container(comptime Event: type) type {
|
|||||||
border: Border = .{},
|
border: Border = .{},
|
||||||
rectangle: Rectangle = .{},
|
rectangle: Rectangle = .{},
|
||||||
layout: Layout = .{},
|
layout: Layout = .{},
|
||||||
/// size which should be used by the `Container`
|
size: Size = .{},
|
||||||
size: Point = .{},
|
|
||||||
/// should size grow to the available space?
|
|
||||||
grow: bool = true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
@@ -609,7 +620,7 @@ pub fn Container(comptime Event: type) type {
|
|||||||
this.origin = origin;
|
this.origin = origin;
|
||||||
this.element.reposition(origin);
|
this.element.reposition(origin);
|
||||||
|
|
||||||
var offset: Point = Point.add(origin, .{
|
var offset = origin.add(.{
|
||||||
.x = layout.padding.left,
|
.x = layout.padding.left,
|
||||||
.y = layout.padding.top,
|
.y = layout.padding.top,
|
||||||
});
|
});
|
||||||
@@ -633,7 +644,8 @@ pub fn Container(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fit_resize(this: *@This()) Point {
|
/// Resize all fit sized `Containers` to the necessary size required by its child elements.
|
||||||
|
fn fit_resize(this: *@This()) Point {
|
||||||
// NOTE this is supposed to be a simple and easy to understand algorithm, there are currently no optimizations done
|
// NOTE this is supposed to be a simple and easy to understand algorithm, there are currently no optimizations done
|
||||||
const layout = this.properties.layout;
|
const layout = this.properties.layout;
|
||||||
var size: Point = switch (layout.direction) {
|
var size: Point = switch (layout.direction) {
|
||||||
@@ -647,8 +659,10 @@ pub fn Container(comptime Event: type) type {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sides = this.properties.border.sides;
|
const sides = this.properties.border.sides;
|
||||||
if (sides.right) size.x -|= 1;
|
if (sides.left) size.x += 1;
|
||||||
if (sides.bottom) size.y -|= 1;
|
if (sides.right) size.x += 1;
|
||||||
|
if (sides.top) size.y += 1;
|
||||||
|
if (sides.bottom) size.y += 1;
|
||||||
|
|
||||||
if (layout.separator.enabled) switch (layout.direction) {
|
if (layout.separator.enabled) switch (layout.direction) {
|
||||||
.horizontal => size.x += @as(u16, @truncate(this.elements.items.len -| 1)),
|
.horizontal => size.x += @as(u16, @truncate(this.elements.items.len -| 1)),
|
||||||
@@ -670,44 +684,55 @@ pub fn Container(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// assign currently calculated size
|
// assign currently calculated size
|
||||||
this.size = Point.max(size, this.properties.size);
|
this.size = switch (this.properties.size.grow) {
|
||||||
|
.both => Point.max(size, this.properties.size.dim),
|
||||||
|
.fixed => this.properties.size.dim,
|
||||||
|
.horizontal => .{
|
||||||
|
.x = @max(size.x, this.properties.size.dim.x),
|
||||||
|
.y = size.y,
|
||||||
|
},
|
||||||
|
.vertical => .{
|
||||||
|
.x = size.x,
|
||||||
|
.y = @max(size.y, this.properties.size.dim.y),
|
||||||
|
},
|
||||||
|
};
|
||||||
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()) void {
|
fn grow_resize(this: *@This(), max_size: Point) void {
|
||||||
const layout = this.properties.layout;
|
const layout = this.properties.layout;
|
||||||
var remainder = switch (layout.direction) {
|
var remainder = switch (layout.direction) {
|
||||||
.horizontal => this.size.x -| (layout.padding.left + layout.padding.right),
|
.horizontal => max_size.x -| (layout.padding.left + layout.padding.right),
|
||||||
.vertical => this.size.y -| (layout.padding.top + layout.padding.bottom),
|
.vertical => max_size.y -| (layout.padding.top + layout.padding.bottom),
|
||||||
};
|
};
|
||||||
remainder -|= layout.gap * @as(u16, @truncate(this.elements.items.len -| 1));
|
remainder -|= layout.gap * @as(u16, @truncate(this.elements.items.len -| 1));
|
||||||
|
|
||||||
if (layout.separator.enabled) remainder -|= @as(u16, @truncate(this.elements.items.len -| 1));
|
if (layout.separator.enabled) remainder -|= @as(u16, @truncate(this.elements.items.len -| 1));
|
||||||
|
|
||||||
var available = switch (layout.direction) {
|
var available = switch (layout.direction) {
|
||||||
.horizontal => this.size.y -| (layout.padding.top + layout.padding.bottom),
|
.horizontal => max_size.y -| (layout.padding.top + layout.padding.bottom),
|
||||||
.vertical => this.size.x -| (layout.padding.left + layout.padding.right),
|
.vertical => max_size.x -| (layout.padding.left + layout.padding.right),
|
||||||
};
|
};
|
||||||
|
|
||||||
const sides = this.properties.border.sides;
|
const sides = this.properties.border.sides;
|
||||||
switch (layout.direction) {
|
switch (layout.direction) {
|
||||||
.horizontal => {
|
.horizontal => {
|
||||||
if (sides.bottom) {
|
if (sides.top) {
|
||||||
available -|= 1;
|
available -|= 1;
|
||||||
remainder -|= 1;
|
remainder -|= 1;
|
||||||
}
|
}
|
||||||
if (sides.top) {
|
if (sides.bottom) {
|
||||||
available -|= 1;
|
available -|= 1;
|
||||||
remainder -|= 1;
|
remainder -|= 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.vertical => {
|
.vertical => {
|
||||||
if (sides.right) {
|
if (sides.left) {
|
||||||
available -|= 1;
|
available -|= 1;
|
||||||
remainder -|= 1;
|
remainder -|= 1;
|
||||||
}
|
}
|
||||||
if (sides.left) {
|
if (sides.right) {
|
||||||
available -|= 1;
|
available -|= 1;
|
||||||
remainder -|= 1;
|
remainder -|= 1;
|
||||||
}
|
}
|
||||||
@@ -724,18 +749,23 @@ 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.grow) {
|
if (child.properties.size.grow != .fixed) {
|
||||||
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) {
|
switch (layout.direction) {
|
||||||
.horizontal => child.size.y = available,
|
.horizontal => if (child.properties.size.grow != .vertical) {
|
||||||
.vertical => child.size.x = available,
|
child.size.y = available;
|
||||||
|
},
|
||||||
|
.vertical => if (child.properties.size.grow != .horizontal) {
|
||||||
|
child.size.x = available;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (growable_children > 0 and remainder > 0) {
|
var count: usize = this.elements.items.len;
|
||||||
|
while (growable_children > 0 and remainder > 0 and count > 0) : (count -|= 1) {
|
||||||
var smallest_size = switch (layout.direction) {
|
var smallest_size = switch (layout.direction) {
|
||||||
.horizontal => first_growable_child.size.x,
|
.horizontal => first_growable_child.size.x,
|
||||||
.vertical => first_growable_child.size.y,
|
.vertical => first_growable_child.size.y,
|
||||||
@@ -744,7 +774,7 @@ pub fn Container(comptime Event: type) type {
|
|||||||
var size_to_correct = remainder;
|
var size_to_correct = remainder;
|
||||||
|
|
||||||
for (this.elements.items) |child| {
|
for (this.elements.items) |child| {
|
||||||
if (!child.properties.grow) continue;
|
if (child.properties.size.grow == .fixed) continue;
|
||||||
|
|
||||||
const size = switch (layout.direction) {
|
const size = switch (layout.direction) {
|
||||||
.horizontal => child.size.x,
|
.horizontal => child.size.x,
|
||||||
@@ -771,18 +801,28 @@ pub fn Container(comptime Event: type) type {
|
|||||||
.horizontal => child.size.x,
|
.horizontal => child.size.x,
|
||||||
.vertical => child.size.y,
|
.vertical => child.size.y,
|
||||||
};
|
};
|
||||||
if (child.properties.grow and child_size == smallest_size) {
|
if (child.properties.size.grow != .fixed and child_size == smallest_size) {
|
||||||
switch (layout.direction) {
|
switch (layout.direction) {
|
||||||
.horizontal => child.size.x += size_to_correct,
|
.horizontal => if (child.properties.size.grow != .vertical) {
|
||||||
.vertical => child.size.y += size_to_correct,
|
child.size.x += size_to_correct;
|
||||||
|
},
|
||||||
|
.vertical => if (child.properties.size.grow != .horizontal) {
|
||||||
|
child.size.y += size_to_correct;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (overflow > 0) {
|
if (overflow > 0) {
|
||||||
switch (layout.direction) {
|
switch (layout.direction) {
|
||||||
.horizontal => child.size.x += 1,
|
.horizontal => if (child.properties.size.grow != .vertical) {
|
||||||
.vertical => child.size.y += 1,
|
child.size.x += 1;
|
||||||
|
overflow -|= 1;
|
||||||
|
remainder -|= 1;
|
||||||
|
},
|
||||||
|
.vertical => if (child.properties.size.grow != .horizontal) {
|
||||||
|
child.size.y += 1;
|
||||||
|
overflow -|= 1;
|
||||||
|
remainder -|= 1;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
overflow -|= 1;
|
|
||||||
remainder -|= 1;
|
|
||||||
}
|
}
|
||||||
remainder -|= size_to_correct;
|
remainder -|= size_to_correct;
|
||||||
}
|
}
|
||||||
@@ -790,16 +830,28 @@ pub fn Container(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.element.resize(this.size);
|
this.element.resize(this.size);
|
||||||
for (this.elements.items) |*child| child.grow_resize();
|
for (this.elements.items) |*child| child.grow_resize(this.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(this: *@This(), origin: Point, size: Point) void {
|
pub fn resize(this: *@This(), origin: Point, 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`
|
||||||
this.size = size; // total screen size;
|
|
||||||
const fit_size = this.fit_resize();
|
const fit_size = this.fit_resize();
|
||||||
this.size = size; // total screen size
|
log.debug("fit_size: {any}; size: {any}", .{ fit_size, size });
|
||||||
_ = fit_size;
|
// if (fit_size.y > size.y or fit_size.x > size.x) @panic("error: cannot render in available space");
|
||||||
this.grow_resize();
|
switch (this.properties.size.grow) {
|
||||||
|
.both => this.size = Point.max(size, fit_size),
|
||||||
|
.fixed => {},
|
||||||
|
.horizontal => this.size = .{
|
||||||
|
.x = @max(size.x, fit_size.x),
|
||||||
|
.y = size.y,
|
||||||
|
},
|
||||||
|
.vertical => this.size = .{
|
||||||
|
.x = size.x,
|
||||||
|
.y = @max(size.y, fit_size.y),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.debug("this.size: {any}", .{this.size});
|
||||||
|
this.grow_resize(this.size);
|
||||||
this.reposition(origin);
|
this.reposition(origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,6 +874,7 @@ pub fn Container(comptime Event: type) type {
|
|||||||
@memset(cells, .{});
|
@memset(cells, .{});
|
||||||
errdefer this.allocator.free(cells);
|
errdefer this.allocator.free(cells);
|
||||||
|
|
||||||
|
log.debug("contents: this.size: {any}", .{this.size});
|
||||||
this.properties.layout.contents(@This(), cells, this.origin, this.size, this.elements.items);
|
this.properties.layout.contents(@This(), cells, this.origin, this.size, this.elements.items);
|
||||||
this.properties.border.contents(cells, this.size);
|
this.properties.border.contents(cells, this.size);
|
||||||
this.properties.rectangle.contents(cells, this.size);
|
this.properties.rectangle.contents(cells, this.size);
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ 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?
|
||||||
this.container_size = Point.max(size, this.container.fit_resize());
|
this.container.resize(.{}, this.size);
|
||||||
this.container.resize(.{}, this.container_size);
|
this.container_size = this.container.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(ctx: *anyopaque, event: Event) !void {
|
fn handle(ctx: *anyopaque, event: Event) !void {
|
||||||
@@ -206,7 +206,9 @@ test "scrollable vertical" {
|
|||||||
.direction = .vertical,
|
.direction = .vertical,
|
||||||
.padding = .all(1),
|
.padding = .all(1),
|
||||||
},
|
},
|
||||||
.size = .{ .y = size.y + 15 },
|
.size = .{
|
||||||
|
.dim = .{ .y = size.y + 15 },
|
||||||
|
},
|
||||||
}, .{});
|
}, .{});
|
||||||
try box.append(try .init(allocator, .{
|
try box.append(try .init(allocator, .{
|
||||||
.rectangle = .{ .fill = .grey },
|
.rectangle = .{ .fill = .grey },
|
||||||
@@ -281,7 +283,9 @@ test "scrollable horizontal" {
|
|||||||
.direction = .horizontal,
|
.direction = .horizontal,
|
||||||
.padding = .all(1),
|
.padding = .all(1),
|
||||||
},
|
},
|
||||||
.size = .{ .x = size.x + 15 },
|
.size = .{
|
||||||
|
.dim = .{ .x = size.x + 15 },
|
||||||
|
},
|
||||||
}, .{});
|
}, .{});
|
||||||
try box.append(try .init(allocator, .{
|
try box.append(try .init(allocator, .{
|
||||||
.rectangle = .{ .fill = .grey },
|
.rectangle = .{ .fill = .grey },
|
||||||
|
|||||||
Reference in New Issue
Block a user