feat: rework header; add footer; padding for content
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m37s
Release Zig Application / Release zig project (release) Has been cancelled

This commit is contained in:
2025-10-30 16:28:49 +01:00
parent c07cfd5f3d
commit 5acca12abf
3 changed files with 204 additions and 30 deletions

View File

@@ -53,6 +53,89 @@ pub fn Content(App: type) type {
}; };
} }
pub fn Title(App: type) type {
return struct {
text: []const u8,
pub fn init() @This() {
return .{
.text = "<Title>",
};
}
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.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 std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;

View File

@@ -19,41 +19,65 @@ pub fn main() !void {
}, .{}); }, .{});
defer container.deinit(); defer container.deinit();
var header: App.Container = try .init(allocator, .{ // header with navigation buttons and content's title
.size = .{
.dim = .{ .y = 1 },
.grow = .horizontal,
},
}, .{});
var navigation: App.Container = try .init(allocator, .{
.layout = .{
.separator = .{ .enabled = true },
},
}, .{});
// about navigation button
{ {
var button: App.Button(.about) = .init(&app.queue, .init(.default, "about")); var header: App.Container = try .init(allocator, .{
try navigation.append(try .init(allocator, .{
.size = .{ .size = .{
.dim = .{ .x = 5 + 2 }, .dim = .{ .y = 1 },
.grow = .vertical, .grow = .horizontal,
}, },
}, button.element())); .layout = .{
.separator = .{ .enabled = true },
},
}, .{});
// title
{
var title: Title = .init();
try header.append(try .init(allocator, .{}, title.element()));
}
// about navigation button
{
var button: NavigationButton(.about) = .init(&app.model, &app.queue);
try header.append(try .init(allocator, .{
.size = .{
.dim = .{ .x = 5 + 2 },
.grow = .vertical,
},
}, button.element()));
}
// blog navigation button
{
var button: NavigationButton(.blog) = .init(&app.model, &app.queue);
try header.append(try .init(allocator, .{
.size = .{
.dim = .{ .x = 4 + 2 },
.grow = .vertical,
},
}, button.element()));
}
try container.append(header);
} }
// blog navigation button // main actual tui_website page content
{ {
var button: App.Button(.blog) = .init(&app.queue, .init(.default, "blog")); // intermediate container for *padding*
try navigation.append(try .init(allocator, .{ var content: Content = .init(allocator);
.size = .{ var content_container: App.Container = try .init(allocator, .{
.dim = .{ .x = 4 + 2 }, .layout = .{ .padding = .horizontal(2) },
.grow = .vertical, }, .{});
}, try content_container.append(try .init(allocator, .{}, content.element()));
}, button.element())); try container.append(content_container);
}
// footer
{
var info_banner: InfoBanner = .init();
const footer: App.Container = try .init(allocator, .{
.size = .{
.dim = .{ .y = 1 },
.grow = .horizontal,
},
}, info_banner.element());
try container.append(footer);
} }
try header.append(navigation);
try container.append(header);
var content: Content = .init(allocator);
try container.append(try .init(allocator, .{}, content.element()));
try app.start(); try app.start();
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err}); defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err});
@@ -107,8 +131,12 @@ const App = zterm.App(
}, },
); );
const contents = @import("content.zig");
const Model = @import("model.zig"); const Model = @import("model.zig");
const Content = @import("content.zig").Content(App); const Content = contents.Content(App);
const Title = contents.Title(App);
const InfoBanner = contents.InfoBanner(App);
const NavigationButton = @import("navigation.zig").NavigationButton(App);
test { test {
std.testing.refAllDeclsRecursive(@This()); std.testing.refAllDeclsRecursive(@This());

63
src/navigation.zig Normal file
View File

@@ -0,0 +1,63 @@
pub fn NavigationButton(App: type) fn (std.meta.FieldEnum(App.Event)) type {
const navigation_struct = struct {
fn navigation_fn(page: std.meta.FieldEnum(App.Event)) type {
return struct {
text: []const u8,
highlight: bool,
queue: *App.Queue,
pub fn init(model: *const App.Model, queue: *App.Queue) @This() {
return .{
.text = @tagName(page),
.highlight = switch (page) {
.about => model.page == .about,
.blog => model.page == .blog,
else => @compileError("NavigationButton initiated with non page `App.Event` variant"),
},
.queue = queue,
};
}
pub fn element(this: *@This()) App.Element {
return .{
.ptr = this,
.vtable = &.{
.handle = handle,
.content = content,
},
};
}
fn handle(ctx: *anyopaque, _: *App.Model, event: App.Event) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
switch (event) {
.about, .blog => this.highlight = event == page,
// TODO accept key input too?
.mouse => |mouse| if (mouse.button == .left and mouse.kind == .release) this.queue.push(page),
else => {},
}
}
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));
assert(size.x == this.text.len + 2);
assert(size.y == 1);
for (1.., this.text) |idx, cp| {
cells[idx].style.fg = if (this.highlight) .black else .default;
cells[idx].style.bg = if (this.highlight) .green else .default;
cells[idx].style.emphasis = &.{.underline};
cells[idx].cp = cp;
}
}
};
}
};
return navigation_struct.navigation_fn;
}
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const zterm = @import("zterm");