feat: rework header; add footer; padding for content
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
88
src/main.zig
88
src/main.zig
@@ -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
63
src/navigation.zig
Normal 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");
|
||||||
Reference in New Issue
Block a user