ref(container): split size and position calculations
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 34s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 34s
This commit is contained in:
@@ -92,11 +92,11 @@ pub fn main() !void {
|
||||
.color = .light_blue,
|
||||
.sides = .all,
|
||||
},
|
||||
.fixed_size = .{ .x = 200 },
|
||||
.size = .{ .x = 200 },
|
||||
}, .{}));
|
||||
try container.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .blue },
|
||||
.fixed_size = .{ .x = 30 },
|
||||
.size = .{ .x = 30 },
|
||||
}, .{}));
|
||||
defer container.deinit(); // also de-initializes the children
|
||||
|
||||
@@ -146,8 +146,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -142,8 +142,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -156,8 +156,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -178,8 +178,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -154,8 +154,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -108,8 +108,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -100,8 +100,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -112,8 +112,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -99,8 +99,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -95,8 +95,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -148,8 +148,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
try renderer.resize();
|
||||
container.reposition(.{});
|
||||
container.resize(renderer.size);
|
||||
container.resize(.{}, renderer.size);
|
||||
try renderer.render(@TypeOf(container), &container);
|
||||
try renderer.flush();
|
||||
}
|
||||
|
||||
@@ -572,7 +572,10 @@ pub fn Container(comptime Event: type) type {
|
||||
border: Border = .{},
|
||||
rectangle: Rectangle = .{},
|
||||
layout: Layout = .{},
|
||||
fixed_size: Point = .{},
|
||||
/// size which should be used by the `Container`
|
||||
size: Point = .{},
|
||||
/// should size grow to the available space?
|
||||
grow: bool = true,
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
@@ -601,170 +604,204 @@ pub fn Container(comptime Event: type) type {
|
||||
try this.elements.append(element);
|
||||
}
|
||||
|
||||
pub fn reposition(this: *@This(), origin: Point) void {
|
||||
log.debug("origin: .{{ .x = {d}, .y = {d} }}", .{ origin.x, origin.y });
|
||||
fn reposition(this: *@This(), origin: Point) void {
|
||||
const layout = this.properties.layout;
|
||||
this.origin = origin;
|
||||
this.element.reposition(origin);
|
||||
|
||||
var offset: Point = Point.add(origin, .{
|
||||
.x = layout.padding.left,
|
||||
.y = layout.padding.top,
|
||||
});
|
||||
|
||||
const sides = this.properties.border.sides;
|
||||
if (sides.left) offset.x += 1;
|
||||
if (sides.top) offset.y += 1;
|
||||
|
||||
for (this.elements.items) |*child| {
|
||||
child.reposition(offset);
|
||||
|
||||
switch (layout.direction) {
|
||||
.horizontal => offset.x += child.size.x + layout.gap,
|
||||
.vertical => offset.y += child.size.y + layout.gap,
|
||||
}
|
||||
|
||||
if (layout.separator.enabled) switch (layout.direction) {
|
||||
.horizontal => offset.x += 1,
|
||||
.vertical => offset.y += 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(this: *@This(), size: Point) void {
|
||||
log.debug("Event .size: {{ .x = {d}, .y = {d} }}", .{ size.x, size.y });
|
||||
this.size = size;
|
||||
if (this.properties.fixed_size.x > 0 and size.x < this.properties.fixed_size.x) return;
|
||||
if (this.properties.fixed_size.y > 0 and size.y < this.properties.fixed_size.y) return;
|
||||
|
||||
this.element.resize(size);
|
||||
|
||||
if (this.elements.items.len == 0) return;
|
||||
|
||||
fn fit_resize(this: *@This()) Point {
|
||||
// NOTE this is supposed to be a simple and easy to understand alogrithm, there are currently no optimizations done
|
||||
const layout = this.properties.layout;
|
||||
var fixed_size_elements: u16 = 0;
|
||||
var fixed_size: Point = .{};
|
||||
for (this.elements.items) |element| {
|
||||
switch (layout.direction) {
|
||||
.horizontal => if (element.properties.fixed_size.x > 0) {
|
||||
fixed_size_elements += 1;
|
||||
},
|
||||
.vertical => if (element.properties.fixed_size.y > 0) {
|
||||
fixed_size_elements += 1;
|
||||
},
|
||||
}
|
||||
fixed_size = fixed_size.add(element.properties.fixed_size);
|
||||
}
|
||||
// check if the available screen is large enough
|
||||
switch (layout.direction) {
|
||||
.horizontal => if (fixed_size.x > size.x) return,
|
||||
.vertical => if (fixed_size.y > size.y) return,
|
||||
}
|
||||
var size: Point = switch (layout.direction) {
|
||||
.horizontal => .{ .x = layout.gap * @as(u16, @truncate(this.elements.items.len -| 1)) },
|
||||
.vertical => .{ .y = layout.gap * @as(u16, @truncate(this.elements.items.len -| 1)) },
|
||||
};
|
||||
size = Point.add(size, this.properties.size);
|
||||
|
||||
if (this.elements.items.len > 0) switch (layout.direction) {
|
||||
.horizontal => size.x += layout.padding.left + layout.padding.right,
|
||||
.vertical => size.y += layout.padding.top + layout.padding.bottom,
|
||||
};
|
||||
|
||||
const sides = this.properties.border.sides;
|
||||
const padding = layout.padding;
|
||||
var gap = layout.gap;
|
||||
if (layout.separator.enabled) gap += 1;
|
||||
if (sides.right) size.x -|= 1;
|
||||
if (sides.bottom) size.y -|= 1;
|
||||
|
||||
const len: u16 = @truncate(this.elements.items.len);
|
||||
const element_x = blk: {
|
||||
var x = size.x - fixed_size.x - gap * (len - 1);
|
||||
if (sides.left) x -= 1;
|
||||
if (sides.right) x -= 1;
|
||||
x -= padding.left + padding.right;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk @divTrunc(x, len);
|
||||
} else {
|
||||
break :blk @divTrunc(x, len - fixed_size_elements);
|
||||
}
|
||||
};
|
||||
const element_y = blk: {
|
||||
var y = size.y - fixed_size.y - gap * (len - 1);
|
||||
if (sides.top) y -= 1;
|
||||
if (sides.bottom) y -= 1;
|
||||
y -= padding.top + padding.bottom;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk @divTrunc(y, len);
|
||||
} else {
|
||||
break :blk @divTrunc(y, len - fixed_size_elements);
|
||||
}
|
||||
};
|
||||
var offset: u16 = switch (layout.direction) {
|
||||
.horizontal => padding.left,
|
||||
.vertical => padding.top,
|
||||
};
|
||||
var overflow = switch (layout.direction) {
|
||||
.horizontal => blk: {
|
||||
var x = size.x - fixed_size.x - gap * (len - 1);
|
||||
if (sides.left) x -= 1;
|
||||
if (sides.right) x -= 1;
|
||||
x -= padding.left + padding.right;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk x - element_x * len;
|
||||
} else {
|
||||
break :blk x - element_x * (len - fixed_size_elements);
|
||||
}
|
||||
},
|
||||
.vertical => blk: {
|
||||
var y = size.y - fixed_size.y - gap * (len - 1);
|
||||
if (sides.top) y -= 1;
|
||||
if (sides.bottom) y -= 1;
|
||||
y -= padding.top + padding.bottom;
|
||||
if (fixed_size_elements == len) break :blk 0;
|
||||
if (fixed_size_elements == 0) {
|
||||
break :blk y - element_y * len;
|
||||
} else {
|
||||
break :blk y - element_y * (len - fixed_size_elements);
|
||||
}
|
||||
},
|
||||
if (layout.separator.enabled) switch (layout.direction) {
|
||||
.horizontal => size.x += @as(u16, @truncate(this.elements.items.len -| 1)),
|
||||
.vertical => size.y += @as(u16, @truncate(this.elements.items.len -| 1)),
|
||||
};
|
||||
|
||||
for (this.elements.items) |*element| {
|
||||
var element_size: Point = undefined;
|
||||
var element_origin: Point = undefined;
|
||||
for (this.elements.items) |*child| {
|
||||
const child_size = child.fit_resize();
|
||||
switch (layout.direction) {
|
||||
.horizontal => {
|
||||
// TODO this should not always be the max size property!
|
||||
var x = blk: {
|
||||
if (element.properties.fixed_size.x > 0) break :blk element.properties.fixed_size.x;
|
||||
break :blk element_x;
|
||||
};
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
x += 1;
|
||||
}
|
||||
element_origin = .{
|
||||
.x = this.origin.x + offset,
|
||||
.y = this.origin.y,
|
||||
};
|
||||
element_size = .{
|
||||
.x = x,
|
||||
.y = size.y,
|
||||
};
|
||||
// border
|
||||
if (sides.top) element_size.y -= 1;
|
||||
if (sides.bottom) element_size.y -= 1;
|
||||
// padding
|
||||
element_origin.y += padding.top;
|
||||
element_size.y -= padding.top + padding.bottom;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += x;
|
||||
size.x += child_size.x;
|
||||
size.y = @max(size.y, child_size.y);
|
||||
},
|
||||
.vertical => {
|
||||
var y = blk: {
|
||||
if (element.properties.fixed_size.y > 0) break :blk element.properties.fixed_size.y;
|
||||
break :blk element_y;
|
||||
};
|
||||
if (overflow > 0) {
|
||||
overflow -|= 1;
|
||||
y += 1;
|
||||
}
|
||||
element_origin = .{
|
||||
.x = this.origin.x,
|
||||
.y = this.origin.y + offset,
|
||||
};
|
||||
element_size = .{
|
||||
.x = size.x,
|
||||
.y = y,
|
||||
};
|
||||
// border
|
||||
if (sides.left) element_size.x -= 1;
|
||||
if (sides.right) element_size.x -= 1;
|
||||
// padding
|
||||
element_origin.x += padding.left;
|
||||
element_size.x -= padding.left + padding.right;
|
||||
// gap
|
||||
offset += gap;
|
||||
offset += y;
|
||||
size.x = @max(size.x, child_size.x);
|
||||
size.y += child_size.y;
|
||||
},
|
||||
}
|
||||
|
||||
// border resizing
|
||||
if (sides.top) element_origin.y += 1;
|
||||
if (sides.left) element_origin.x += 1;
|
||||
|
||||
element.reposition(element_origin);
|
||||
element.resize(element_size);
|
||||
}
|
||||
|
||||
// assign currently calculated size
|
||||
this.size = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
const layout = this.properties.layout;
|
||||
var remainder = switch (layout.direction) {
|
||||
.horizontal => this.size.x -| (layout.padding.left + layout.padding.right),
|
||||
.vertical => this.size.y -| (layout.padding.top + layout.padding.bottom),
|
||||
};
|
||||
remainder -|= layout.gap * @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) {
|
||||
.horizontal => this.size.y -| (layout.padding.top + layout.padding.bottom),
|
||||
.vertical => this.size.x -| (layout.padding.left + layout.padding.right),
|
||||
};
|
||||
|
||||
const sides = this.properties.border.sides;
|
||||
switch (layout.direction) {
|
||||
.horizontal => {
|
||||
if (sides.bottom) {
|
||||
available -|= 1;
|
||||
remainder -|= 1;
|
||||
}
|
||||
if (sides.top) {
|
||||
available -|= 1;
|
||||
remainder -|= 1;
|
||||
}
|
||||
},
|
||||
.vertical => {
|
||||
if (sides.right) {
|
||||
available -|= 1;
|
||||
remainder -|= 1;
|
||||
}
|
||||
if (sides.left) {
|
||||
available -|= 1;
|
||||
remainder -|= 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
for (this.elements.items) |child| {
|
||||
remainder -|= switch (layout.direction) {
|
||||
.horizontal => child.size.x,
|
||||
.vertical => child.size.y,
|
||||
};
|
||||
}
|
||||
|
||||
var growable_children: usize = 0;
|
||||
var first_growable_child: *@This() = undefined;
|
||||
for (this.elements.items) |*child| {
|
||||
if (child.properties.grow) {
|
||||
if (growable_children == 0) first_growable_child = child;
|
||||
growable_children += 1;
|
||||
|
||||
switch (layout.direction) {
|
||||
.horizontal => child.size.y = available,
|
||||
.vertical => child.size.x = available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (growable_children > 0 and remainder > 0) {
|
||||
var smallest_size = switch (layout.direction) {
|
||||
.horizontal => first_growable_child.size.x,
|
||||
.vertical => first_growable_child.size.y,
|
||||
};
|
||||
var second_smallest_size: u16 = std.math.maxInt(u16);
|
||||
var size_to_correct = remainder;
|
||||
|
||||
for (this.elements.items) |child| {
|
||||
if (!child.properties.grow) continue;
|
||||
|
||||
const size = switch (layout.direction) {
|
||||
.horizontal => child.size.x,
|
||||
.vertical => child.size.y,
|
||||
};
|
||||
|
||||
if (size < smallest_size) {
|
||||
second_smallest_size = smallest_size;
|
||||
smallest_size = size;
|
||||
} else if (size > smallest_size) {
|
||||
second_smallest_size = @min(size, second_smallest_size);
|
||||
size_to_correct = second_smallest_size -| smallest_size;
|
||||
}
|
||||
}
|
||||
|
||||
size_to_correct = @min(size_to_correct, remainder / growable_children);
|
||||
var overflow: u16 = 0;
|
||||
if (size_to_correct == 0 and remainder > 0) {
|
||||
// there is some overflow
|
||||
overflow = remainder;
|
||||
}
|
||||
for (this.elements.items) |*child| {
|
||||
const child_size = switch (layout.direction) {
|
||||
.horizontal => child.size.x,
|
||||
.vertical => child.size.y,
|
||||
};
|
||||
if (child.properties.grow and child_size == smallest_size) {
|
||||
switch (layout.direction) {
|
||||
.horizontal => child.size.x += size_to_correct,
|
||||
.vertical => child.size.y += size_to_correct,
|
||||
}
|
||||
if (overflow > 0) {
|
||||
switch (layout.direction) {
|
||||
.horizontal => child.size.x += 1,
|
||||
.vertical => child.size.y += 1,
|
||||
}
|
||||
overflow -|= 1;
|
||||
remainder -|= 1;
|
||||
}
|
||||
remainder -|= size_to_correct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.element.resize(this.size);
|
||||
for (this.elements.items) |*child| child.grow_resize();
|
||||
}
|
||||
|
||||
pub fn resize(this: *@This(), origin: Point, size: Point) void {
|
||||
// NOTE assume that this function is only called for the root `Container`
|
||||
this.size = size; // total screen size;
|
||||
const fit_size = this.fit_resize();
|
||||
this.size = size; // total screen size
|
||||
_ = fit_size;
|
||||
this.grow_resize();
|
||||
this.reposition(origin);
|
||||
}
|
||||
|
||||
pub fn handle(this: *@This(), event: Event) !void {
|
||||
|
||||
@@ -107,8 +107,7 @@ pub fn Scrollable(Event: type) type {
|
||||
this.size = size;
|
||||
// TODO scrollbar space - depending on configuration and only if necessary?
|
||||
this.container_size = Point.max(size, this.min_size);
|
||||
this.container_origin = size; // TODO the size should be a provided origin
|
||||
this.container.resize(this.container_size);
|
||||
this.container.resize(.{}, this.container_size);
|
||||
}
|
||||
|
||||
fn reposition(ctx: *anyopaque, origin: Point) void {
|
||||
@@ -247,7 +246,7 @@ test "scrollable vertical" {
|
||||
var renderer: testing.Renderer = .init(allocator, size);
|
||||
defer renderer.deinit();
|
||||
|
||||
container.resize(size);
|
||||
container.resize(.{}, size);
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.top.zon"), renderer.screen);
|
||||
|
||||
@@ -321,7 +320,7 @@ test "scrollable horizontal" {
|
||||
var renderer: testing.Renderer = .init(allocator, size);
|
||||
defer renderer.deinit();
|
||||
|
||||
container.resize(size);
|
||||
container.resize(.{}, size);
|
||||
try renderer.render(Container(event.SystemEvent), &container);
|
||||
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.horizontal.left.zon"), renderer.screen);
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ pub const Renderer = struct {
|
||||
|
||||
pub fn resize(this: *@This(), size: Point) !void {
|
||||
this.size = size;
|
||||
const n = @as(usize, size.cols) * @as(usize, size.y);
|
||||
const n = @as(usize, size.x) * @as(usize, size.y);
|
||||
|
||||
this.allocator.free(this.screen);
|
||||
this.screen = this.allocator.alloc(Cell, n) catch @panic("testing.zig: Out of memory.");
|
||||
@@ -120,8 +120,8 @@ pub fn expectContainerScreen(size: Point, container: *Container(event.SystemEven
|
||||
var renderer: Renderer = .init(allocator, size);
|
||||
defer renderer.deinit();
|
||||
|
||||
container.reposition(.{});
|
||||
container.resize(size);
|
||||
try renderer.resize(size);
|
||||
container.resize(.{}, size);
|
||||
try renderer.render(Container(event.SystemEvent), container);
|
||||
|
||||
try expectEqualCells(.{}, renderer.size, expected, renderer.screen);
|
||||
|
||||
Reference in New Issue
Block a user