//! Dynamic dispatch for layout implementations. Each `Layout` has to implement //! the `Layout.Interface`. //! //! Create a `Layout` using `createFrom(object: anytype)` and use them through //! the defined `Layout.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 //! `Element`s (union of `Layout` or `Widget`) when deallocated. This means //! that `deinit()` will also deallocate every used `Element` 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), }; 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: *anyopaque = 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); } pub fn createFrom(object: anytype) LayoutType { return LayoutType{ .object = @ptrCast(@alignCast(object)), .vtable = &.{ .handle = struct { // Handle the provided `Event` for this `Layout`. fn handle(this: *LayoutType, event: Event) !*Events { const layout: @TypeOf(object) = @ptrCast(@alignCast(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) = @ptrCast(@alignCast(this.object)); try layout.render(renderer); } }.render, .deinit = struct { fn deinit(this: *LayoutType) void { const layout: @TypeOf(object) = @ptrCast(@alignCast(this.object)); layout.deinit(); } }.deinit, }, }; } // import and export of `Layout` implementations pub const HContainer = @import("layout/HContainer.zig").Layout(Event, Element, Renderer); pub const HStack = @import("layout/HStack.zig").Layout(Event, Element, Renderer); pub const VContainer = @import("layout/VContainer.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 Margin = @import("layout/Margin.zig").Layout(Event, Element, Renderer); pub const Framing = @import("layout/Framing.zig").Layout(Event, Element, Renderer); pub const Tab = @import("layout/Tab.zig").Layout(Event, Element, Renderer); }; // test layout implementation satisfies the interface comptime Type.Interface.satisfiedBy(Type); comptime Type.Interface.satisfiedBy(Type.HContainer); comptime Type.Interface.satisfiedBy(Type.HStack); comptime Type.Interface.satisfiedBy(Type.VContainer); comptime Type.Interface.satisfiedBy(Type.VStack); comptime Type.Interface.satisfiedBy(Type.Padding); comptime Type.Interface.satisfiedBy(Type.Margin); comptime Type.Interface.satisfiedBy(Type.Framing); comptime Type.Interface.satisfiedBy(Type.Tab); return Type; }