Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 40s
This also means that currently the dynamic resizing through the app's detached thread is not working, as it cannot send size updates. The examples have been overhauled to still implement intermediate mode applications accordingly.
163 lines
5.1 KiB
Zig
163 lines
5.1 KiB
Zig
const std = @import("std");
|
|
const zterm = @import("zterm");
|
|
|
|
const App = zterm.App(union(enum) {});
|
|
|
|
const log = std.log.scoped(.default);
|
|
|
|
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, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
|
_ = ctx;
|
|
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
_ = origin;
|
|
|
|
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;
|
|
}
|
|
}
|
|
};
|
|
|
|
const InfoText = struct {
|
|
const text = "Press any key; Non-Ascii inputs (i.e. `Enter` or `Backspace`) to trigger an exception";
|
|
|
|
pub fn element(this: *@This()) App.Element {
|
|
return .{ .ptr = this, .vtable = &.{ .content = content } };
|
|
}
|
|
|
|
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
|
_ = ctx;
|
|
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
_ = origin;
|
|
|
|
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;
|
|
}
|
|
}
|
|
};
|
|
|
|
const ErrorNotification = struct {
|
|
msg: ?[]const u8 = null,
|
|
|
|
pub fn element(this: *@This()) App.Element {
|
|
return .{ .ptr = this, .vtable = &.{ .handle = handle, .content = content } };
|
|
}
|
|
|
|
fn handle(ctx: *anyopaque, event: App.Event) !void {
|
|
const this: *@This() = @ptrCast(@alignCast(ctx));
|
|
switch (event) {
|
|
.key => |key| if (!key.isAscii()) return error.UnsupportedKey,
|
|
.err => |err| this.msg = err.msg,
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
fn content(ctx: *anyopaque, cells: []zterm.Cell, origin: zterm.Point, size: zterm.Point) !void {
|
|
const this: *@This() = @ptrCast(@alignCast(ctx));
|
|
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
_ = origin;
|
|
|
|
if (this.msg) |msg| {
|
|
const row = size.y -| 2;
|
|
const col = size.x -| 2 -| msg.len;
|
|
const anchor = (row * size.x) + col;
|
|
|
|
for (msg, 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;
|
|
}
|
|
|
|
this.msg = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn main() !void {
|
|
errdefer |err| log.err("Application Error: {any}", .{err});
|
|
|
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
|
defer if (gpa.deinit() == .leak) log.err("memory leak", .{});
|
|
|
|
const allocator = gpa.allocator();
|
|
|
|
var app: App = .init;
|
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
|
defer renderer.deinit();
|
|
|
|
var quit_text: QuitText = .{};
|
|
var info_text: InfoText = .{};
|
|
var error_notification: ErrorNotification = .{};
|
|
|
|
var container = try App.Container.init(allocator, .{
|
|
.layout = .{
|
|
.gap = 2,
|
|
.padding = .{ .top = 5, .bottom = 3, .left = 3, .right = 3 },
|
|
},
|
|
}, quit_text.element());
|
|
defer container.deinit();
|
|
|
|
try container.append(try App.Container.init(allocator, .{}, info_text.element()));
|
|
try container.append(try App.Container.init(allocator, .{}, error_notification.element()));
|
|
|
|
try app.start();
|
|
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err});
|
|
|
|
while (true) {
|
|
const event = app.nextEvent();
|
|
log.debug("received event: {s}", .{@tagName(event)});
|
|
|
|
// pre event handling
|
|
switch (event) {
|
|
.key => |key| if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(),
|
|
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
|
|
else => {},
|
|
}
|
|
|
|
container.handle(event) catch |err| app.postEvent(.{
|
|
.err = .{
|
|
.err = err,
|
|
.msg = "Container Event handling failed",
|
|
},
|
|
});
|
|
|
|
// post event handling
|
|
switch (event) {
|
|
.quit => break,
|
|
else => {},
|
|
}
|
|
|
|
try renderer.resize();
|
|
container.reposition(.{});
|
|
container.resize(renderer.size);
|
|
try renderer.render(@TypeOf(container), &container);
|
|
try renderer.flush();
|
|
}
|
|
}
|