//! Dynamic dispatch for widget implementations. Each `Widget` has to implement //! the `Widget.Interface`. //! //! Create a `Widget` using `createFrom(object: anytype)` and use them through //! the defined `Widget.Interface`. The widget will take care of calling the //! correct implementation of the corresponding underlying type. //! //! Each `Widget` may cache its content and should if the contents will not //! change for a long time. //! //! When `Widget.render` is called the provided `Renderer` type is expected //! which handles how contents are rendered for a given widget. const std = @import("std"); const isTaggedUnion = @import("event.zig").isTaggedUnion; const log = @import("std").log.scoped(.widget); pub fn Widget(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 Type = struct { const WidgetType = @This(); pub const Interface = @import("interface").Interface(.{ .handle = fn (anytype, Event) ?Event, .render = fn (anytype, *Renderer) anyerror!void, .deinit = fn (anytype) void, }, .{}); const VTable = struct { handle: *const fn (this: *WidgetType, event: Event) ?Event, render: *const fn (this: *WidgetType, renderer: *Renderer) anyerror!void, deinit: *const fn (this: *WidgetType) void, }; allocator: std.mem.Allocator = undefined, object: *anyopaque = undefined, vtable: *const VTable = undefined, // Handle the provided `Event` for this `Widget`. pub fn handle(this: *WidgetType, event: Event) ?Event { switch (event) { .resize => |size| { log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{ size.anchor.col, size.anchor.row, size.cols, size.rows, }); }, else => {}, } return this.vtable.handle(this, event); } // Render the content of this `Widget` given the `Size` of the widget (.resize System`Event`). pub fn render(this: *WidgetType, renderer: *Renderer) !void { try this.vtable.render(this, renderer); } pub fn deinit(this: *WidgetType) void { this.vtable.deinit(this); this.allocator.destroy(this); } pub fn createFrom(allocator: std.mem.Allocator, object: anytype) *WidgetType { const widget = allocator.create(WidgetType) catch @panic("widget.zig: out of memory"); widget.allocator = allocator; widget.object = @ptrCast(object); widget.vtable = &.{ .handle = struct { // Handle the provided `Event` for this `Widget`. fn handle(this: *WidgetType, event: Event) ?Event { const widget_ptr: @TypeOf(object) = @ptrCast(@alignCast(this.object)); return widget_ptr.handle(event); } }.handle, .render = struct { // Return the entire content of this `Widget`. fn render(this: *WidgetType, renderer: *Renderer) !void { const widget_ptr: @TypeOf(object) = @ptrCast(@alignCast(this.object)); try widget_ptr.render(renderer); } }.render, .deinit = struct { fn deinit(this: *WidgetType) void { const widget_ptr: @TypeOf(object) = @ptrCast(@alignCast(this.object)); widget_ptr.deinit(); } }.deinit, }; return widget; } // import and export of `Widget` implementations pub const Text = @import("widget/Text.zig").Widget(Event, Renderer); pub const RawText = @import("widget/RawText.zig").Widget(Event, Renderer); pub const Spacer = @import("widget/Spacer.zig").Widget(Event, Renderer); }; // test widget implementation satisfies the interface comptime Type.Interface.satisfiedBy(Type); comptime Type.Interface.satisfiedBy(Type.Text); comptime Type.Interface.satisfiedBy(Type.RawText); comptime Type.Interface.satisfiedBy(Type.Spacer); return Type; }