ref(event): remove .resize and replace with recursize method calls
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 40s

This also means that currently the dynamic resizing through the app's
detached thread is not working, as it cannot send size updates. The
examples have been overhauled to still implement intermediate mode
applications accordingly.
This commit is contained in:
2025-03-04 14:52:19 +01:00
parent 43cdc46853
commit ec22e68e8c
17 changed files with 334 additions and 224 deletions

View File

@@ -108,10 +108,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| {
if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit();
@@ -140,6 +138,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -118,10 +118,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
.click => |button| {
log.info("Clicked with mouse using Button: {s}", .{button});
@@ -137,6 +135,15 @@ pub fn main() !void {
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -131,10 +131,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
.accept => |input| {
defer allocator.free(input);
@@ -151,6 +149,15 @@ pub fn main() !void {
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -157,10 +157,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
else => {},
@@ -172,6 +170,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -70,7 +70,6 @@ const ErrorNotification = struct {
const this: *@This() = @ptrCast(@alignCast(ctx));
switch (event) {
.key => |key| if (!key.isAscii()) return error.UnsupportedKey,
.size => |_| {},
.err => |err| this.msg = err.msg,
else => {},
}
@@ -134,10 +133,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
else => {},
@@ -149,6 +146,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -85,10 +85,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
@@ -102,6 +100,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -77,10 +77,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
@@ -94,6 +92,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -89,10 +89,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
@@ -106,6 +104,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -76,10 +76,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
@@ -93,6 +91,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -74,10 +74,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
else => {},
@@ -89,6 +87,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -127,10 +127,8 @@ pub fn main() !void {
const event = app.nextEvent();
log.debug("received event: {s}", .{@tagName(event)});
// pre event handling
switch (event) {
.init => continue,
.quit => break,
.size => |size| try renderer.resize(size),
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
else => {},
@@ -142,6 +140,16 @@ pub fn main() !void {
.msg = "Container Event handling failed",
},
});
// post event handling
switch (event) {
.quit => break,
else => {},
}
try renderer.resize();
container.reposition(.{});
container.resize(renderer.size);
try renderer.render(@TypeOf(container), &container);
try renderer.flush();
}

View File

@@ -97,9 +97,6 @@ pub fn App(comptime E: type) type {
try terminal.enterAltScreen();
try terminal.hideCursor();
try terminal.enableMouseSupport();
// send initial size afterwards
this.postEvent(.{ .size = terminal.getTerminalSize() });
}
pub fn interrupt(this: *@This()) !void {
@@ -144,7 +141,8 @@ pub fn App(comptime E: type) type {
fn winsizeCallback(ptr: *anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(ptr));
this.postEvent(.{ .size = terminal.getTerminalSize() });
_ = this;
// this.postEvent(.{ .size = terminal.getTerminalSize() });
}
var winch_handler: ?SignalHandler = null;
@@ -329,10 +327,12 @@ pub fn App(comptime E: type) type {
const width_char = iter.next() orelse break;
const height_char = iter.next() orelse break;
this.postEvent(.{ .size = .{
.x = std.fmt.parseUnsigned(u16, width_char, 10) catch break,
.y = std.fmt.parseUnsigned(u16, height_char, 10) catch break,
} });
_ = width_char;
_ = height_char;
// this.postEvent(.{ .size = .{
// .x = std.fmt.parseUnsigned(u16, width_char, 10) catch break,
// .y = std.fmt.parseUnsigned(u16, height_char, 10) catch break,
// } });
}
},
'u' => {

View File

@@ -601,22 +601,21 @@ pub fn Container(comptime Event: type) type {
try this.elements.append(element);
}
pub fn position(this: *@This(), pos: Point) !void {
log.debug("pos: .{{ .x = {d}, .y = {d} }}", .{ pos.x, pos.y });
this.origin = pos;
pub fn reposition(this: *@This(), origin: Point) void {
log.debug("origin: .{{ .x = {d}, .y = {d} }}", .{ origin.x, origin.y });
this.origin = origin;
this.element.reposition(origin);
}
pub fn handle(this: *@This(), event: Event) !void {
switch (event) {
.size => |size| resize: {
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 Error.TooSmall;
if (this.properties.fixed_size.y > 0 and size.y < this.properties.fixed_size.y) return Error.TooSmall;
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;
try this.element.handle(event);
this.element.resize(size);
if (this.elements.items.len == 0) break :resize;
if (this.elements.items.len == 0) return;
const layout = this.properties.layout;
var fixed_size_elements: u16 = 0;
@@ -634,8 +633,8 @@ pub fn Container(comptime Event: type) type {
}
// check if the available screen is large enough
switch (layout.direction) {
.horizontal => if (fixed_size.x > size.x) return Error.TooSmall,
.vertical => if (fixed_size.y > size.y) return Error.TooSmall,
.horizontal => if (fixed_size.x > size.x) return,
.vertical => if (fixed_size.y > size.y) return,
}
const sides = this.properties.border.sides;
const padding = layout.padding;
@@ -763,11 +762,13 @@ pub fn Container(comptime Event: type) type {
if (sides.top) element_origin.y += 1;
if (sides.left) element_origin.x += 1;
// TODO tell the element its origin
try element.position(element_origin);
try element.handle(.{ .size = element_size });
element.reposition(element_origin);
element.resize(element_size);
}
},
}
pub fn handle(this: *@This(), event: Event) !void {
switch (event) {
.mouse => |mouse| if (mouse.in(this.origin, this.size)) {
try this.element.handle(event);
for (this.elements.items) |*element| try element.handle(event);

View File

@@ -13,10 +13,24 @@ pub fn Element(Event: type) type {
vtable: *const VTable = &.{},
pub const VTable = struct {
resize: ?*const fn (ctx: *anyopaque, size: Point) void = null,
reposition: ?*const fn (ctx: *anyopaque, origin: Point) void = null,
handle: ?*const fn (ctx: *anyopaque, event: Event) anyerror!void = null,
content: ?*const fn (ctx: *anyopaque, cells: []Cell, origin: Point, size: Point) anyerror!void = null,
};
/// Resize the corresponding `Element` with the given *size*.
pub fn resize(this: @This(), size: Point) void {
if (this.vtable.resize) |resize_fn|
resize_fn(this.ptr, size);
}
/// Reposition the corresponding `Element` with the given *origin*.
pub fn reposition(this: @This(), origin: Point) void {
if (this.vtable.reposition) |reposition_fn|
reposition_fn(this.ptr, origin);
}
/// Handle the received event. The event is one of the user provided
/// events or a system event, with the exception of the `.size`
/// `Event` as every `Container` already handles that event.
@@ -73,6 +87,8 @@ pub fn Scrollable(Event: type) type {
return .{
.ptr = this,
.vtable = &.{
.resize = resize,
.reposition = reposition,
.handle = handle,
.content = content,
},
@@ -86,16 +102,23 @@ pub fn Scrollable(Event: type) type {
};
}
fn resize(ctx: *anyopaque, size: Point) void {
const this: *@This() = @ptrCast(@alignCast(ctx));
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);
}
fn reposition(ctx: *anyopaque, origin: Point) void {
const this: *@This() = @ptrCast(@alignCast(ctx));
this.container_origin = origin; // TODO the size should be a provided origin
}
fn handle(ctx: *anyopaque, event: Event) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
switch (event) {
.size => |size| {
this.size = size;
// TODO scrollbar space - depending on configuration and only if necessary?
this.container_size = size.max(this.min_size);
this.container_origin = size; // TODO the size should be a provided origin
try this.container.handle(.{ .size = this.container_size });
},
// TODO other means to scroll except with the mouse? (i.e. Ctrl-u/d, k/j, etc.?)
.mouse => |mouse| switch (mouse.button) {
Mouse.Button.wheel_up => if (this.container_size.y > this.size.y) {
@@ -224,7 +247,7 @@ test "scrollable vertical" {
var renderer: testing.Renderer = .init(allocator, size);
defer renderer.deinit();
try container.handle(.{ .size = 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);
@@ -298,7 +321,7 @@ test "scrollable horizontal" {
var renderer: testing.Renderer = .init(allocator, size);
defer renderer.deinit();
try container.handle(.{ .size = 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);

View File

@@ -21,8 +21,6 @@ pub const SystemEvent = union(enum) {
/// associated error message
msg: []const u8,
},
/// Size event emitted by the terminal to derive the `Size` of the current terminal the application is rendered in
size: Point,
/// Input key event received from the user
key: Key,
/// Mouse input event

View File

@@ -2,17 +2,17 @@ pub const Point = packed struct {
x: u16 = 0,
y: u16 = 0,
pub fn add(this: @This(), other: @This()) @This() {
pub fn add(a: @This(), b: @This()) @This() {
return .{
.x = this.x + other.x,
.y = this.y + other.y,
.x = a.x + b.x,
.y = a.y + b.y,
};
}
pub fn max(this: @This(), other: @This()) @This() {
pub fn max(a: @This(), b: @This()) @This() {
return .{
.x = @max(this.x, other.x),
.y = @max(this.y, other.y),
.x = @max(a.x, b.x),
.y = @max(a.y, b.y),
};
}

View File

@@ -29,9 +29,12 @@ pub const Buffered = struct {
}
}
pub fn resize(this: *@This(), size: Point) !void {
pub fn resize(this: *@This()) !void {
const size = terminal.getTerminalSize();
if (std.meta.eql(this.size, size)) return;
this.size = size;
const n = @as(usize, size.x) * @as(usize, size.y);
const n = @as(usize, this.size.x) * @as(usize, this.size.y);
if (!this.created) {
this.screen = this.allocator.alloc(Cell, n) catch @panic("render.zig: Out of memory.");