const std = @import("std"); const terminal = @import("../terminal.zig"); const isTaggedUnion = @import("../event.zig").isTaggedUnion; const Error = @import("../event.zig").Error; const Cell = terminal.Cell; const log = std.log.scoped(.widget_text); 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)`."); } return struct { allocator: std.mem.Allocator, alignment: Alignment, contents: []const Cell, size: terminal.Size, require_render: bool, const Alignment = enum { default, center, top, bottom, left, right, }; pub fn init(allocator: std.mem.Allocator, alignment: Alignment, contents: []const Cell) *@This() { var this = allocator.create(@This()) catch @panic("Text.zig: Failed to create"); this.allocator = allocator; this.require_render = true; this.alignment = alignment; this.contents = contents; return this; } pub fn deinit(this: *@This()) void { this.allocator.destroy(this); } pub fn handle(this: *@This(), event: Event) ?Event { switch (event) { // store the received size .resize => |size| { this.size = size; this.require_render = true; }, else => {}, } return null; } pub fn render(this: *@This(), renderer: *Renderer) !void { if (!this.require_render) { return; } try renderer.clear(this.size); // update size for aligned contents, default will not change size const size: terminal.Size = blk: { switch (this.alignment) { .default => break :blk this.size, .center => { var length_usize: usize = 0; for (this.contents) |content| { length_usize += content.content.len; } const length: u16 = @truncate(length_usize); const cols = @min(length, this.size.cols); const rows = cols / length; break :blk .{ .anchor = .{ .col = this.size.anchor.col + @divTrunc(this.size.cols, 2) - @divTrunc(cols, 2), .row = this.size.anchor.row + @divTrunc(this.size.rows, 2), }, .rows = rows, .cols = cols, }; }, .top => { var length_usize: usize = 0; for (this.contents) |content| { length_usize += content.content.len; } const length: u16 = @truncate(length_usize); const cols = @min(length, this.size.cols); const rows = cols / length; break :blk .{ .anchor = .{ .col = this.size.anchor.col + @divTrunc(this.size.cols, 2) - @divTrunc(cols, 2), .row = this.size.anchor.row, }, .rows = rows, .cols = cols, }; }, .bottom => { var length_usize: usize = 0; for (this.contents) |content| { length_usize += content.content.len; } const length: u16 = @truncate(length_usize); const cols = @min(length, this.size.cols); const rows = cols / length; break :blk .{ .anchor = .{ .col = this.size.anchor.col + @divTrunc(this.size.cols, 2) - @divTrunc(cols, 2), .row = this.size.anchor.row + this.size.rows - rows, }, .rows = rows, .cols = cols, }; }, .left => { var length_usize: usize = 0; for (this.contents) |content| { length_usize += content.content.len; } const length: u16 = @truncate(length_usize); const cols = @min(length, this.size.cols); const rows = cols / length; break :blk .{ .anchor = .{ .col = this.size.anchor.col, .row = this.size.anchor.row + @divTrunc(this.size.rows, 2) - @divTrunc(rows, 2), }, .rows = rows, .cols = cols, }; }, .right => { var length_usize: usize = 0; for (this.contents) |content| { length_usize += content.content.len; } const length: u16 = @truncate(length_usize); const cols = @min(length, this.size.cols); const rows = cols / length; break :blk .{ .anchor = .{ .col = this.size.anchor.col + this.size.cols - cols, .row = this.size.anchor.row + @divTrunc(this.size.rows, 2) - @divTrunc(rows, 2), }, .rows = rows, .cols = cols, }; }, } }; try renderer.render(size, this.contents); this.require_render = false; } }; }