//! 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 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(); const Ptr = usize; 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, }; object: Ptr = 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.* = undefined; } pub fn createFrom(object: anytype) WidgetType { return WidgetType{ .object = @intFromPtr(object), .vtable = &.{ .handle = struct { // Handle the provided `Event` for this `Widget`. fn handle(this: *WidgetType, event: Event) ?Event { const widget: @TypeOf(object) = @ptrFromInt(this.object); return widget.handle(event); } }.handle, .render = struct { // Return the entire content of this `Widget`. fn render(this: *WidgetType, renderer: *Renderer) !void { const widget: @TypeOf(object) = @ptrFromInt(this.object); try widget.render(renderer); } }.render, .deinit = struct { fn deinit(this: *WidgetType) void { const widget: @TypeOf(object) = @ptrFromInt(this.object); widget.deinit(); } }.deinit, }, }; } // import and export of `Widget` implementations 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.RawText); comptime Type.Interface.satisfiedBy(Type.Spacer); return Type; }