From ec22e68e8c1f2bc5099e7e5f2ddfb856a0560389 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Tue, 4 Mar 2025 14:52:19 +0100 Subject: [PATCH] ref(event): remove `.resize` and replace with recursize method calls 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. --- examples/demo.zig | 14 +- examples/elements/button.zig | 13 +- examples/elements/input.zig | 13 +- examples/elements/scrollable.zig | 14 +- examples/errors.zig | 15 +- examples/layouts/grid.zig | 14 +- examples/layouts/horizontal.zig | 14 +- examples/layouts/mixed.zig | 14 +- examples/layouts/vertical.zig | 14 +- examples/styles/palette.zig | 14 +- examples/styles/text.zig | 14 +- src/app.zig | 16 +- src/container.zig | 327 ++++++++++++++++--------------- src/element.zig | 41 +++- src/event.zig | 2 - src/point.zig | 12 +- src/render.zig | 7 +- 17 files changed, 334 insertions(+), 224 deletions(-) diff --git a/examples/demo.zig b/examples/demo.zig index 5e82f57..6c78795 100644 --- a/examples/demo.zig +++ b/examples/demo.zig @@ -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(); } diff --git a/examples/elements/button.zig b/examples/elements/button.zig index e78b37e..b40f5b2 100644 --- a/examples/elements/button.zig +++ b/examples/elements/button.zig @@ -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(); } diff --git a/examples/elements/input.zig b/examples/elements/input.zig index 7c19eaa..a5b0db0 100644 --- a/examples/elements/input.zig +++ b/examples/elements/input.zig @@ -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(); } diff --git a/examples/elements/scrollable.zig b/examples/elements/scrollable.zig index 76f0515..c5c2a44 100644 --- a/examples/elements/scrollable.zig +++ b/examples/elements/scrollable.zig @@ -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(); } diff --git a/examples/errors.zig b/examples/errors.zig index b7e9345..15b2ec1 100644 --- a/examples/errors.zig +++ b/examples/errors.zig @@ -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(); } diff --git a/examples/layouts/grid.zig b/examples/layouts/grid.zig index bff53e4..583bf34f 100644 --- a/examples/layouts/grid.zig +++ b/examples/layouts/grid.zig @@ -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(); } diff --git a/examples/layouts/horizontal.zig b/examples/layouts/horizontal.zig index 2c1bf88..e27f852 100644 --- a/examples/layouts/horizontal.zig +++ b/examples/layouts/horizontal.zig @@ -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(); } diff --git a/examples/layouts/mixed.zig b/examples/layouts/mixed.zig index 3ead435..bc71ae1 100644 --- a/examples/layouts/mixed.zig +++ b/examples/layouts/mixed.zig @@ -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(); } diff --git a/examples/layouts/vertical.zig b/examples/layouts/vertical.zig index feb2aff..9ceee31 100644 --- a/examples/layouts/vertical.zig +++ b/examples/layouts/vertical.zig @@ -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(); } diff --git a/examples/styles/palette.zig b/examples/styles/palette.zig index 39b50ba..686ccb7 100644 --- a/examples/styles/palette.zig +++ b/examples/styles/palette.zig @@ -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(); } diff --git a/examples/styles/text.zig b/examples/styles/text.zig index 2c516d1..02a0af3 100644 --- a/examples/styles/text.zig +++ b/examples/styles/text.zig @@ -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(); } diff --git a/src/app.zig b/src/app.zig index 5776f82..ad7c648 100644 --- a/src/app.zig +++ b/src/app.zig @@ -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' => { diff --git a/src/container.zig b/src/container.zig index ba6b81b..4695eaa 100644 --- a/src/container.zig +++ b/src/container.zig @@ -601,173 +601,174 @@ 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 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; + + 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, + } + const sides = this.properties.border.sides; + const padding = layout.padding; + var gap = layout.gap; + if (layout.separator.enabled) gap += 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); + } + }, + }; + + for (this.elements.items) |*element| { + var element_size: Point = undefined; + var element_origin: Point = undefined; + 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; + }, + .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; + }, + } + + // border resizing + if (sides.top) element_origin.y += 1; + if (sides.left) element_origin.x += 1; + + element.reposition(element_origin); + element.resize(element_size); + } } pub fn handle(this: *@This(), event: Event) !void { switch (event) { - .size => |size| resize: { - 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; - - try this.element.handle(event); - - if (this.elements.items.len == 0) break :resize; - - 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 Error.TooSmall, - .vertical => if (fixed_size.y > size.y) return Error.TooSmall, - } - const sides = this.properties.border.sides; - const padding = layout.padding; - var gap = layout.gap; - if (layout.separator.enabled) gap += 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); - } - }, - }; - - for (this.elements.items) |*element| { - var element_size: Point = undefined; - var element_origin: Point = undefined; - 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; - }, - .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; - }, - } - - // border resizing - 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 }); - } - }, .mouse => |mouse| if (mouse.in(this.origin, this.size)) { try this.element.handle(event); for (this.elements.items) |*element| try element.handle(event); diff --git a/src/element.zig b/src/element.zig index 48b872e..6d7f435 100644 --- a/src/element.zig +++ b/src/element.zig @@ -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); diff --git a/src/event.zig b/src/event.zig index 285ef7a..9ace94a 100644 --- a/src/event.zig +++ b/src/event.zig @@ -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 diff --git a/src/point.zig b/src/point.zig index add1369..5374192 100644 --- a/src/point.zig +++ b/src/point.zig @@ -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), }; } diff --git a/src/render.zig b/src/render.zig index 2524b44..6c60b34 100644 --- a/src/render.zig +++ b/src/render.zig @@ -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.");