pub fn Content(App: type) type { return struct { allocator: Allocator, document: *const App.Model.Document, pub fn init(allocator: Allocator, document: *const App.Model.Document) @This() { return .{ .allocator = allocator, .document = document, }; } 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 = this.document.content; var index: usize = 0; var new_size: zterm.Point = .{ .x = size.x }; for (0..text.len) |_| rows: { for (0..size.x - 1) |_| { // assume that there is one cell reserved for the scrollbar 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) { .init => this.document = &model.document, .about => { model.page.deinit(this.allocator); model.page = .about; model.document.deinit(this.allocator); model.document = .init(try std.fs.cwd().readFileAlloc("./doc/about.md", this.allocator, .unlimited)); this.document = &model.document; }, .blog => |path| { model.page.deinit(this.allocator); model.page = .{ .blog = path }; model.document.deinit(this.allocator); model.document = .init(try std.fs.cwd().readFileAlloc(if (path) |p| p else "./doc/blog.md", this.allocator, .unlimited)); this.document = &model.document; }, else => {}, } } 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 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 = "Build with zig", right_text: []const u8 = "Yves Biener (@yves-biener)", 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)); 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; } switch (model.page) { .about => {}, .blog => |path| if (path) |p| page: { const start_idx = (size.x -| p.len) / 2; if (start_idx <= this.left_text.len) break :page; for (start_idx.., p) |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].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");