pub fn Content(App: type) type { return struct { page: App.Model.Pages, pub fn init(allocator: Allocator) @This() { _ = allocator; return .{ .page = .blog, }; } pub fn deinit(this: *@This()) void { this.container.deinit(); } pub fn element(this: *@This()) App.Element { return .{ .ptr = this, .vtable = &.{ .minSize = minSize, .handle = handle, .content = content, }, }; } fn minSize(ctx: *anyopaque, size: zterm.Point) zterm.Point { const this: *const @This() = @ptrCast(@alignCast(ctx)); const text = switch (this.page) { .about => @embedFile("about"), .blog => @embedFile("blog"), }; var index: usize = 0; var new_size: zterm.Point = .{ .x = size.x }; for (0..text.len) |_| rows: { for (0..size.x) |_| { if (index == text.len) break :rows; const cp = text[index]; index += 1; switch (cp) { '\n' => break, else => {}, } } new_size.y += 1; } return new_size; } fn handle(ctx: *anyopaque, model: *App.Model, event: App.Event) !void { const this: *@This() = @ptrCast(@alignCast(ctx)); switch (event) { .about => { model.page = .about; model.document = .init(@embedFile("about")); }, .blog => { model.page = .blog; model.document = .init(@embedFile("blog")); }, else => {}, } this.page = model.page; } fn content(ctx: *anyopaque, model: *const App.Model, cells: []zterm.Cell, size: zterm.Point) !void { _ = ctx; assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); const text = model.document.content; var index: usize = 0; for (0..size.y) |row| { for (0..size.x) |col| { const cell = row * size.x + col; if (index == text.len) return; const cp = text[index]; index += 1; cells[cell].cp = switch (cp) { '\n' => break, else => cp, }; } } } }; } pub fn Title(App: type) type { return struct { pub fn init() @This() { return .{}; } pub fn element(this: *@This()) App.Element { return .{ .ptr = this, .vtable = &.{ .content = content, }, }; } fn content(ctx: *anyopaque, model: *const App.Model, cells: []zterm.Cell, size: zterm.Point) !void { const this: *const @This() = @ptrCast(@alignCast(ctx)); _ = this; assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); if (model.document.title) |text| { for (0.., text) |idx, cp| { cells[idx].style.fg = .green; cells[idx].style.emphasis = &.{.bold}; cells[idx].cp = cp; // NOTE do not write over the contents of this `Container`'s `Size` if (idx == cells.len - 1) break; } } } }; } pub fn InfoBanner(App: type) type { return struct { left_text: []const u8, right_text: []const u8, pub fn init() @This() { return .{ .left_text = "Build with zig", .right_text = "Yves Biener (@yves-biener)", }; } pub fn element(this: *@This()) App.Element { return .{ .ptr = this, .vtable = &.{ .content = content, }, }; } fn content(ctx: *anyopaque, _: *const App.Model, cells: []zterm.Cell, size: zterm.Point) !void { const this: *const @This() = @ptrCast(@alignCast(ctx)); assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); for (0.., this.left_text) |idx, cp| { // NOTE do not write over the contents of this `Container`'s `Size` if (idx == cells.len) break; cells[idx].style.fg = .default; cells[idx].style.emphasis = &.{.dim}; cells[idx].cp = cp; } var start_idx = size.x -| this.right_text.len; if (start_idx <= this.left_text.len) start_idx = this.left_text.len + 1; for (start_idx.., this.right_text) |idx, cp| { // NOTE do not write over the contents of this `Container`'s `Size` if (idx == cells.len) break; cells[idx].style.fg = .default; cells[idx].style.emphasis = &.{.dim}; cells[idx].cp = cp; } } }; } const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const zterm = @import("zterm");