diff --git a/examples/demo.zig b/examples/demo.zig index 194b65a..74c57d2 100644 --- a/examples/demo.zig +++ b/examples/demo.zig @@ -66,7 +66,6 @@ pub fn main() !void { try box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .lightgreen }, }, .{})); - defer box.deinit(); var scrollable: App.Scrollable = .init(box, .disabled); diff --git a/examples/elements/input.zig b/examples/elements/input.zig index a0263a0..b03e70d 100644 --- a/examples/elements/input.zig +++ b/examples/elements/input.zig @@ -73,8 +73,6 @@ pub fn main() !void { defer renderer.deinit(); var input_field: App.Input(.accept) = .init(allocator, &app.queue, .init(.black, .blue)); - defer input_field.deinit(); - var mouse_draw: MouseDraw = .{}; var second_mouse_draw: MouseDraw = .{}; var quit_text: QuitText = .{}; diff --git a/examples/elements/scrollable.zig b/examples/elements/scrollable.zig index b48b4d7..8821c84 100644 --- a/examples/elements/scrollable.zig +++ b/examples/elements/scrollable.zig @@ -106,7 +106,6 @@ pub fn main() !void { .dim = .{ .y = 2 }, }, }, .{})); - defer top_box.deinit(); var bottom_box = try App.Container.init(allocator, .{ .border = .{ @@ -134,7 +133,6 @@ pub fn main() !void { try bottom_box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .grey }, }, .{})); - defer bottom_box.deinit(); var container = try App.Container.init(allocator, .{ .layout = .{ diff --git a/examples/styles/palette.zig b/examples/styles/palette.zig index 300e5f2..35178e2 100644 --- a/examples/styles/palette.zig +++ b/examples/styles/palette.zig @@ -53,7 +53,6 @@ pub fn main() !void { var box = try App.Container.init(allocator, .{ .layout = .{ .direction = .horizontal }, }, .{}); - defer box.deinit(); inline for (std.meta.fields(zterm.Color)) |field| { if (field.value == 0) continue; // zterm.Color.default == 0 -> skip diff --git a/examples/styles/text.zig b/examples/styles/text.zig index a6c8b88..135d6e1 100644 --- a/examples/styles/text.zig +++ b/examples/styles/text.zig @@ -207,12 +207,9 @@ pub fn main() !void { }, }, table_head.element())); - var box = try App.Container.init(allocator, .{ + var scrollable: App.Scrollable = .init(try .init(allocator, .{ .layout = .{ .direction = .vertical }, - }, text_styles.element()); - defer box.deinit(); - - var scrollable: App.Scrollable = .init(box, .disabled); + }, text_styles.element()), .enabled(.white, true)); try container.append(try App.Container.init(allocator, .{}, scrollable.element())); try app.start(); diff --git a/src/container.zig b/src/container.zig index f5e39d2..11f37d4 100644 --- a/src/container.zig +++ b/src/container.zig @@ -648,6 +648,7 @@ pub fn Container(Model: type, Event: type) type { pub fn deinit(this: *@This()) void { for (this.elements.items) |*element| element.deinit(); this.elements.deinit(this.allocator); + this.element.deinit(); } pub fn append(this: *@This(), element: @This()) !void { diff --git a/src/element.zig b/src/element.zig index cbd8d68..ca4d723 100644 --- a/src/element.zig +++ b/src/element.zig @@ -15,6 +15,7 @@ pub fn Element(Model: type, Event: type) type { vtable: *const VTable = &.{}, pub const VTable = struct { + deinit: ?*const fn (ctx: *anyopaque) void = null, minSize: ?*const fn (ctx: *anyopaque, model: *const Model, size: Point) Point = null, resize: ?*const fn (ctx: *anyopaque, model: *const Model, size: Point) void = null, reposition: ?*const fn (ctx: *anyopaque, model: *const Model, origin: Point) void = null, @@ -22,6 +23,20 @@ pub fn Element(Model: type, Event: type) type { content: ?*const fn (ctx: *anyopaque, model: *const Model, cells: []Cell, size: Point) anyerror!void = null, }; + /// Deinitialize the `Element`. Each `Element` that requires an + /// `Allocator` shall keep one in their instance as no `Allocator` + /// will be passed through to the `deinit` call. The `deinit` function + /// is called from the root `Container` for every `Element` in the + /// `Container` tree to deinitialize the entire tree. + /// + /// This function is only necessary to provide if the `Element` + /// implementation does allocate memory that should be cleaned up at + /// the end. + pub inline fn deinit(this: @This()) void { + if (this.vtable.deinit) |deinit_fn| + deinit_fn(this.ptr); + } + /// Request the minimal size required for this `Element` the provided /// available *size* can be used as a fallback. This function ensures /// that the minimal size returned has at least the dimensions of the @@ -149,10 +164,16 @@ pub fn Alignment(Model: type, Event: type) type { }; } + fn deinit(ctx: *anyopaque) void { + const this: *@This() = @ptrCast(@alignCast(ctx)); + this.container.deinit(); + } + pub fn element(this: *@This()) Element(Model, Event) { return .{ .ptr = this, .vtable = &.{ + .deinit = deinit, .resize = resize, .reposition = reposition, .handle = handle, @@ -258,10 +279,16 @@ pub fn Scrollable(Model: type, Event: type) type { }; } + fn deinit(ctx: *anyopaque) void { + const this: *@This() = @ptrCast(@alignCast(ctx)); + this.container.deinit(); + } + pub fn element(this: *@This()) Element(Model, Event) { return .{ .ptr = this, .vtable = &.{ + .deinit = deinit, .resize = resize, .reposition = reposition, .handle = handle, @@ -506,7 +533,7 @@ pub fn Input(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event)) t } }; return struct { - allocator: std.mem.Allocator, + allocator: Allocator, /// Offset from the end describing the current position of the cursor. cursor_offset: usize = 0, /// Configuration for the InputField. @@ -529,7 +556,7 @@ pub fn Input(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event)) t } }; - pub fn init(allocator: std.mem.Allocator, queue: *Queue, configuration: Configuration) @This() { + pub fn init(allocator: Allocator, queue: *Queue, configuration: Configuration) @This() { return .{ .allocator = allocator, .configuration = configuration, @@ -538,7 +565,8 @@ pub fn Input(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event)) t }; } - pub fn deinit(this: *@This()) void { + fn deinit(ctx: *anyopaque) void { + const this: *@This() = @ptrCast(@alignCast(ctx)); this.input.deinit(this.allocator); } @@ -546,6 +574,7 @@ pub fn Input(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event)) t return .{ .ptr = this, .vtable = &.{ + .deinit = deinit, .handle = handle, .content = content, }, @@ -1087,6 +1116,7 @@ pub fn Progress(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event) const std = @import("std"); const assert = std.debug.assert; +const Allocator = std.mem.Allocator; const meta = std.meta; const build_options = @import("build_options"); const input = @import("input.zig"); @@ -1134,7 +1164,6 @@ test "scrollable vertical" { try box.append(try .init(allocator, .{ .rectangle = .{ .fill = .grey }, }, .{})); - defer box.deinit(); var scrollable: Scrollable(Model, event.SystemEvent) = .init(box, .disabled); @@ -1251,7 +1280,6 @@ test "scrollable vertical with scrollbar" { try box.append(try .init(allocator, .{ .rectangle = .{ .fill = .grey }, }, .{})); - defer box.deinit(); var scrollable: Scrollable(Model, event.SystemEvent) = .init(box, .enabled(.white, true)); @@ -1331,7 +1359,6 @@ test "scrollable horizontal" { try box.append(try .init(allocator, .{ .rectangle = .{ .fill = .grey }, }, .{})); - defer box.deinit(); var scrollable: Scrollable(Model, event.SystemEvent) = .init(box, .disabled); @@ -1411,7 +1438,6 @@ test "scrollable horizontal with scrollbar" { try box.append(try .init(allocator, .{ .rectangle = .{ .fill = .grey }, }, .{})); - defer box.deinit(); var scrollable: Scrollable(Model, event.SystemEvent) = .init(box, .enabled(.white, true)); @@ -1465,16 +1491,13 @@ test "alignment center" { var container: Container(Model, event.SystemEvent) = try .init(allocator, .{}, .{}); defer container.deinit(); - var aligned_container: Container(Model, event.SystemEvent) = try .init(allocator, .{ + var alignment: Alignment(Model, event.SystemEvent) = .init(try .init(allocator, .{ .rectangle = .{ .fill = .green }, .size = .{ .dim = .{ .x = 12, .y = 5 }, .grow = .fixed, }, - }, .{}); - defer aligned_container.deinit(); - - var alignment: Alignment(Model, event.SystemEvent) = .init(aligned_container, .center); + }, .{}), .center); try container.append(try .init(allocator, .{}, alignment.element())); try testing.expectContainerScreen(.{ @@ -1492,16 +1515,13 @@ test "alignment left" { var container: Container(Model, event.SystemEvent) = try .init(allocator, .{}, .{}); defer container.deinit(); - var aligned_container: Container(Model, event.SystemEvent) = try .init(allocator, .{ + var alignment: Alignment(Model, event.SystemEvent) = .init(try .init(allocator, .{ .rectangle = .{ .fill = .green }, .size = .{ .dim = .{ .x = 12, .y = 5 }, .grow = .fixed, }, - }, .{}); - defer aligned_container.deinit(); - - var alignment: Alignment(Model, event.SystemEvent) = .init(aligned_container, .{ + }, .{}), .{ .h = .start, .v = .center, }); @@ -1522,16 +1542,13 @@ test "alignment right" { var container: Container(Model, event.SystemEvent) = try .init(allocator, .{}, .{}); defer container.deinit(); - var aligned_container: Container(Model, event.SystemEvent) = try .init(allocator, .{ + var alignment: Alignment(Model, event.SystemEvent) = .init(try .init(allocator, .{ .rectangle = .{ .fill = .green }, .size = .{ .dim = .{ .x = 12, .y = 5 }, .grow = .fixed, }, - }, .{}); - defer aligned_container.deinit(); - - var alignment: Alignment(Model, event.SystemEvent) = .init(aligned_container, .{ + }, .{}), .{ .h = .end, .v = .center, }); @@ -1552,16 +1569,13 @@ test "alignment top" { var container: Container(Model, event.SystemEvent) = try .init(allocator, .{}, .{}); defer container.deinit(); - var aligned_container: Container(Model, event.SystemEvent) = try .init(allocator, .{ + var alignment: Alignment(Model, event.SystemEvent) = .init(try .init(allocator, .{ .rectangle = .{ .fill = .green }, .size = .{ .dim = .{ .x = 12, .y = 5 }, .grow = .fixed, }, - }, .{}); - defer aligned_container.deinit(); - - var alignment: Alignment(Model, event.SystemEvent) = .init(aligned_container, .{ + }, .{}), .{ .h = .center, .v = .start, }); @@ -1582,16 +1596,13 @@ test "alignment bottom" { var container: Container(Model, event.SystemEvent) = try .init(allocator, .{}, .{}); defer container.deinit(); - var aligned_container: Container(Model, event.SystemEvent) = try .init(allocator, .{ + var alignment: Alignment(Model, event.SystemEvent) = .init(try .init(allocator, .{ .rectangle = .{ .fill = .green }, .size = .{ .dim = .{ .x = 12, .y = 5 }, .grow = .fixed, }, - }, .{}); - defer aligned_container.deinit(); - - var alignment: Alignment(Model, event.SystemEvent) = .init(aligned_container, .{ + }, .{}), .{ .h = .center, .v = .end, }); @@ -1625,8 +1636,6 @@ test "input element" { var queue: Queue = .{}; var input_element: Input(Model, Event, Queue)(.accept) = .init(allocator, &queue, .init(.black, .default)); - defer input_element.deinit(); - const input_container: Container(Model, Event) = try .init(allocator, .{ .rectangle = .{ .fill = .green }, .size = .{