//! Dynamic dispatch for layout implementations. //! Each layout should at least implement these functions: //! - handle(this: *@This(), event: Event) anyerror!*std.ArrayList(Event) {} //! - render(this: *@This(), renderer: *Renderer) anyerror!void {} //! - deinit(this: *@This()) void {} //! //! Create a `Layout` using `createFrom(object: anytype)` and use them through //! the defined interface. The layout will take care of calling the correct //! implementation of the corresponding underlying type. //! //! Each `Layout` is responsible for clearing the allocated memory of the used //! widgets when deallocated. This means that `deinit()` will also deallocate //! every used widget too. //! //! When `Layout.render` is called the provided `Renderer` type is expected //! which handles how contents are rendered for a given layout. const std = @import("std"); const isTaggedUnion = @import("event.zig").isTaggedUnion; pub fn Layout(comptime Event: type, comptime Renderer: type) type { if (!isTaggedUnion(Event)) { @compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`."); } const Events = std.ArrayList(Event); const Type = struct { const LayoutType = @This(); const Element = union(enum) { layout: LayoutType, widget: @import("widget.zig").Widget(Event, Renderer), }; const Ptr = usize; pub const Interface = @import("interface").Interface(.{ .handle = fn (anytype, Event) anyerror!*Events, .render = fn (anytype, *Renderer) anyerror!void, .deinit = fn (anytype) void, }, .{}); const VTable = struct { handle: *const fn (this: *LayoutType, event: Event) anyerror!*Events, render: *const fn (this: *LayoutType, renderer: *Renderer) anyerror!void, deinit: *const fn (this: *LayoutType) void, }; object: Ptr = undefined, vtable: *const VTable = undefined, // Handle the provided `Event` for this `Layout`. pub fn handle(this: *LayoutType, event: Event) !*Events { return try this.vtable.handle(this, event); } // Render this `Layout` completely. This will render contained sub-elements too. pub fn render(this: *LayoutType, renderer: *Renderer) !void { return try this.vtable.render(this, renderer); } pub fn deinit(this: *LayoutType) void { this.vtable.deinit(this); this.* = undefined; } pub fn createFrom(object: anytype) LayoutType { return LayoutType{ .object = @intFromPtr(object), .vtable = &.{ .handle = struct { // Handle the provided `Event` for this `Layout`. fn handle(this: *LayoutType, event: Event) !*Events { const layout: @TypeOf(object) = @ptrFromInt(this.object); return try layout.handle(event); } }.handle, .render = struct { // Render the contents of this `Layout`. fn render(this: *LayoutType, renderer: *Renderer) !void { const layout: @TypeOf(object) = @ptrFromInt(this.object); try layout.render(renderer); } }.render, .deinit = struct { fn deinit(this: *LayoutType) void { const layout: @TypeOf(object) = @ptrFromInt(this.object); layout.deinit(); } }.deinit, }, }; } // import and export of `Layout` implementations pub const HStack = @import("layout/HStack.zig").Layout(Event, Element, Renderer); pub const VStack = @import("layout/VStack.zig").Layout(Event, Element, Renderer); pub const Padding = @import("layout/Padding.zig").Layout(Event, Element, Renderer); pub const Framing = @import("layout/Framing.zig").Layout(Event, Element, Renderer); }; // test widget implementation satisfies the interface comptime Type.Interface.satisfiedBy(Type); // TODO: there is a dependency loop (due to the necessary `Layout` type for the `Element`) comptime Type.Interface.satisfiedBy(Type.HStack); comptime Type.Interface.satisfiedBy(Type.VStack); comptime Type.Interface.satisfiedBy(Type.Padding); comptime Type.Interface.satisfiedBy(Type.Framing); return Type; }