feat: embedded content incl. rendering; simple layout with nav-bar
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m1s

This commit is contained in:
2025-10-29 16:30:09 +01:00
parent dd859dfaa1
commit bf0d068b4b
2 changed files with 77 additions and 128 deletions

View File

@@ -17,6 +17,18 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
.imports = &.{ .imports = &.{
.{ .name = "zterm", .module = zterm.module("zterm") }, .{ .name = "zterm", .module = zterm.module("zterm") },
.{
.name = "about",
.module = b.createModule(.{
.root_source_file = b.path("doc/about.md"),
}),
},
.{
.name = "blog",
.module = b.createModule(.{
.root_source_file = b.path("doc/blog.md"),
}),
},
}, },
}), }),
}); });

View File

@@ -1,29 +1,3 @@
const QuitText = struct {
const text = "Press ctrl+c to quit.";
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 {
_ = ctx;
assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
const row = 2;
const col = size.x / 2 -| (text.len / 2);
const anchor = (row * size.x) + col;
for (text, 0..) |cp, idx| {
cells[anchor + idx].style.fg = .white;
cells[anchor + idx].style.bg = .black;
cells[anchor + idx].cp = cp;
// NOTE do not write over the contents of this `Container`'s `Size`
if (anchor + idx == cells.len - 1) break;
}
}
};
pub fn main() !void { pub fn main() !void {
errdefer |err| log.err("Application Error: {any}", .{err}); errdefer |err| log.err("Application Error: {any}", .{err});
@@ -36,105 +10,65 @@ pub fn main() !void {
var renderer = zterm.Renderer.Buffered.init(allocator); var renderer = zterm.Renderer.Buffered.init(allocator);
defer renderer.deinit(); defer renderer.deinit();
var progress_percent: u8 = 0;
var quit_text: QuitText = .{};
var container = try App.Container.init(allocator, .{ var container = try App.Container.init(allocator, .{
.layout = .{ .padding = .all(5), .direction = .vertical }, .layout = .{
}, quit_text.element()); .padding = .horizontal(2),
.direction = .vertical,
.separator = .{ .enabled = true },
},
}, .{});
defer container.deinit(); defer container.deinit();
{ var header: App.Container = try .init(allocator, .{
var progress: App.Progress(.progress) = .init(&app.queue, .{ .size = .{
.percent = .{ .dim = .{ .y = 1 },
.enabled = true, .grow = .horizontal,
.alignment = .left,
}, },
.fg = .blue, }, .{});
.bg = .grey, var navigation: App.Container = try .init(allocator, .{
}); .layout = .{
try container.append(try App.Container.init(allocator, .{}, progress.element())); .separator = .{ .enabled = true },
}
{
var progress: App.Progress(.progress) = .init(&app.queue, .{
.percent = .{
.enabled = true,
.alignment = .middle, // default
}, },
.fg = .red, }, .{});
.bg = .grey, // about navigation button
});
try container.append(try App.Container.init(allocator, .{}, progress.element()));
}
{ {
var progress: App.Progress(.progress) = .init(&app.queue, .{ var button: App.Button(.about) = .init(&app.queue, .init(.default, "about"));
.percent = .{ try navigation.append(try .init(allocator, .{
.enabled = true, .size = .{
.alignment = .right, .dim = .{ .x = 5 + 2 },
.grow = .vertical,
}, },
.fg = .green, }, button.element()));
.bg = .grey,
});
try container.append(try App.Container.init(allocator, .{}, progress.element()));
} }
// blog navigation button
{ {
var progress: App.Progress(.progress) = .init(&app.queue, .{ var button: App.Button(.blog) = .init(&app.queue, .init(.default, "blog"));
.percent = .{ .enabled = false }, try navigation.append(try .init(allocator, .{
.fg = .default, .size = .{
.bg = .grey, .dim = .{ .x = 4 + 2 },
}); .grow = .vertical,
try container.append(try App.Container.init(allocator, .{}, progress.element())); },
}, button.element()));
} }
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});
var framerate: u64 = 60; // event loop
var tick_ms: u64 = @divFloor(time.ms_per_s, framerate); while (true) {
var next_frame_ms: u64 = 0;
var increase_progress: u64 = 10;
// Continuous drawing
// draw loop
draw: while (true) {
const now_ms: u64 = @intCast(time.milliTimestamp());
if (now_ms >= next_frame_ms) {
next_frame_ms = now_ms + tick_ms;
} else {
std.Thread.sleep((next_frame_ms - now_ms) * time.ns_per_ms);
next_frame_ms += tick_ms;
}
// NOTE time based progress increasion
increase_progress -= 1;
if (increase_progress == 0) {
increase_progress = 10;
progress_percent += 1;
if (progress_percent > 100) progress_percent = 0;
app.postEvent(.{ .progress = progress_percent });
}
const len = blk: {
app.queue.lock();
defer app.queue.unlock();
break :blk app.queue.len();
};
// handle events // handle events
for (0..len) |_| { const event = app.nextEvent();
const event = app.queue.drain() orelse break;
log.debug("handling event: {s}", .{@tagName(event)});
// pre event handling // pre event handling
switch (event) { switch (event) {
.key => |key| { .key => |key| {
if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(); if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit();
}, },
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }), .err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
.focus => |b| {
// NOTE reduce framerate in case the window is not focused and restore again when focused
framerate = if (b) 60 else 15;
tick_ms = @divFloor(time.ms_per_s, framerate);
},
else => {}, else => {},
} }
@@ -147,13 +81,13 @@ pub fn main() !void {
// post event handling // post event handling
switch (event) { switch (event) {
.quit => break :draw, .quit => break,
else => {}, else => {},
} }
}
container.resize(try renderer.resize()); container.resize(try renderer.resize());
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container, App.Model, &app.model); try renderer.render(@TypeOf(container), &container, App.Model, &app.model);
try renderer.flush(); try renderer.flush();
} }
@@ -163,16 +97,19 @@ pub const panic = App.panic_handler;
const log = std.log.scoped(.default); const log = std.log.scoped(.default);
const std = @import("std"); const std = @import("std");
const time = std.time;
const assert = std.debug.assert; const assert = std.debug.assert;
const zterm = @import("zterm"); const zterm = @import("zterm");
const App = zterm.App( const App = zterm.App(
struct {}, Model,
union(enum) { union(enum) {
progress: u8, about,
blog,
}, },
); );
const Model = @import("model.zig");
const Content = @import("content.zig").Content(App);
test { test {
std.testing.refAllDeclsRecursive(@This()); std.testing.refAllDeclsRecursive(@This());
} }