2 Commits
0.0.1 ... 0.0.2

Author SHA1 Message Date
8602db4d43 mod: bump version to 0.0.2
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 54s
Release Zig Application / Release zig project (release) Successful in 1m18s
2025-10-30 16:31:56 +01:00
5acca12abf 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
2025-10-30 16:28:49 +01:00
4 changed files with 205 additions and 31 deletions

View File

@@ -1,7 +1,7 @@
.{
.name = .tui_website,
// This is a [Semantic Version](https://semver.org/).
.version = "0.0.1",
.version = "0.0.2",
.fingerprint = 0x93d98a4d9d000e9c, // Changing this has security and trust implications.
.minimum_zig_version = "0.16.0-dev.463+f624191f9",
.dependencies = .{

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 Allocator = std.mem.Allocator;
const assert = std.debug.assert;

View File

@@ -19,21 +19,26 @@ pub fn main() !void {
}, .{});
defer container.deinit();
// header with navigation buttons and content's title
{
var header: App.Container = try .init(allocator, .{
.size = .{
.dim = .{ .y = 1 },
.grow = .horizontal,
},
}, .{});
var navigation: App.Container = try .init(allocator, .{
.layout = .{
.separator = .{ .enabled = true },
},
}, .{});
// title
{
var title: Title = .init();
try header.append(try .init(allocator, .{}, title.element()));
}
// about navigation button
{
var button: App.Button(.about) = .init(&app.queue, .init(.default, "about"));
try navigation.append(try .init(allocator, .{
var button: NavigationButton(.about) = .init(&app.model, &app.queue);
try header.append(try .init(allocator, .{
.size = .{
.dim = .{ .x = 5 + 2 },
.grow = .vertical,
@@ -42,18 +47,37 @@ pub fn main() !void {
}
// blog navigation button
{
var button: App.Button(.blog) = .init(&app.queue, .init(.default, "blog"));
try navigation.append(try .init(allocator, .{
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 header.append(navigation);
try container.append(header);
}
// main actual tui_website page content
{
// intermediate container for *padding*
var content: Content = .init(allocator);
try container.append(try .init(allocator, .{}, content.element()));
var content_container: App.Container = try .init(allocator, .{
.layout = .{ .padding = .horizontal(2) },
}, .{});
try content_container.append(try .init(allocator, .{}, content.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 app.start();
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 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 {
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");