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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user