107 lines
4.5 KiB
Zig
107 lines
4.5 KiB
Zig
//! 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;
|
|
}
|