Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 59s
This should be the way it should be done all the time, such that you are not rendering for every input, but instead handle all `App.Event`s that happened between the last render and the current. This shares similarities with the continuous event loops, which also batches the events only with the exception that it instead blocks (see `App.Queue.poll`).
268 lines
7.9 KiB
Zig
268 lines
7.9 KiB
Zig
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;
|
|
}
|
|
}
|
|
};
|
|
|
|
const TableText = struct {
|
|
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;
|
|
_ = size;
|
|
var idx: usize = 0;
|
|
{
|
|
const text = "Normal ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Bold ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Dim ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Italic ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Underl ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Blink ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Invert ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Hidden ";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
{
|
|
const text = "Strikethrough";
|
|
for (text) |cp| {
|
|
cells[idx].cp = cp;
|
|
idx += 1;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const TextStyles = struct {
|
|
const text = "Example";
|
|
|
|
pub fn element(this: *@This()) App.Element {
|
|
return .{
|
|
.ptr = this,
|
|
.vtable = &.{
|
|
.minSize = minSize,
|
|
.content = content,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn minSize(ctx: *anyopaque, size: zterm.Point) zterm.Point {
|
|
_ = ctx;
|
|
_ = size;
|
|
return .{
|
|
.x = std.meta.fields(zterm.Style.Emphasis).len * TextStyles.text.len,
|
|
.y = (std.meta.fields(zterm.Color).len - 1) * (std.meta.fields(zterm.Color).len - 2),
|
|
};
|
|
}
|
|
|
|
fn content(ctx: *anyopaque, _: *const App.Model, cells: []zterm.Cell, size: zterm.Point) !void {
|
|
@setEvalBranchQuota(10000);
|
|
_ = ctx;
|
|
assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
|
|
var row: usize = 0;
|
|
var col: usize = 0;
|
|
|
|
// Color
|
|
inline for (std.meta.fields(zterm.Color)) |bg_field| {
|
|
if (bg_field.value == 0) continue; // zterm.Color.default == 0 -> skip
|
|
|
|
inline for (std.meta.fields(zterm.Color)) |fg_field| {
|
|
if (fg_field.value == 0) continue; // zterm.Color.default == 0 -> skip
|
|
if (fg_field.value == bg_field.value) continue;
|
|
|
|
// witouth any emphasis
|
|
for (text) |cp| {
|
|
cells[(row * size.x) + col].style.bg = @enumFromInt(bg_field.value);
|
|
cells[(row * size.x) + col].style.fg = @enumFromInt(fg_field.value);
|
|
cells[(row * size.x) + col].cp = cp;
|
|
col += 1;
|
|
}
|
|
|
|
// emphasis (no combinations)
|
|
inline for (std.meta.fields(zterm.Style.Emphasis)) |emp_field| {
|
|
if (emp_field.value == 0) continue; // zterm.Style.Emphasis.reset == 0 -> skip
|
|
const emphasis: zterm.Style.Emphasis = @enumFromInt(emp_field.value);
|
|
|
|
for (text) |cp| {
|
|
cells[(row * size.x) + col].style.bg = @enumFromInt(bg_field.value);
|
|
cells[(row * size.x) + col].style.fg = @enumFromInt(fg_field.value);
|
|
cells[(row * size.x) + col].style.emphasis = &.{emphasis};
|
|
cells[(row * size.x) + col].cp = cp;
|
|
col += 1;
|
|
}
|
|
}
|
|
row += 1;
|
|
col = 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn main() !void {
|
|
errdefer |err| log.err("Application Error: {any}", .{err});
|
|
|
|
var gpa: std.heap.DebugAllocator(.{}) = .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 = .{};
|
|
const element = quit_text.element();
|
|
|
|
var text_styles: TextStyles = .{};
|
|
|
|
var container = try App.Container.init(allocator, .{
|
|
.layout = .{
|
|
// .gap = 2,
|
|
.padding = .{ .top = 5, .bottom = 3, .left = 3, .right = 3 },
|
|
.direction = .vertical,
|
|
},
|
|
}, element);
|
|
defer container.deinit();
|
|
|
|
var table_head: TableText = .{};
|
|
try container.append(try .init(allocator, .{
|
|
.size = .{
|
|
.dim = .{ .y = 1 },
|
|
.grow = .horizontal,
|
|
},
|
|
}, table_head.element()));
|
|
|
|
var box = try App.Container.init(allocator, .{
|
|
.layout = .{ .direction = .vertical },
|
|
}, text_styles.element());
|
|
defer box.deinit();
|
|
|
|
var scrollable: App.Scrollable = .init(box, .disabled);
|
|
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
|
|
|
try app.start();
|
|
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err});
|
|
|
|
// event loop
|
|
loop: while (true) {
|
|
// batch events since last iteration
|
|
const len = blk: {
|
|
app.queue.poll();
|
|
app.queue.lock();
|
|
defer app.queue.unlock();
|
|
break :blk app.queue.len();
|
|
};
|
|
|
|
// handle events
|
|
for (0..len) |_| {
|
|
const event = app.queue.pop();
|
|
log.debug("handling 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(&app.model, event) catch |err| app.postEvent(.{
|
|
.err = .{
|
|
.err = err,
|
|
.msg = "Container Event handling failed",
|
|
},
|
|
});
|
|
|
|
// post event handling
|
|
switch (event) {
|
|
.quit => break :loop,
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
container.resize(try renderer.resize());
|
|
container.reposition(.{});
|
|
try renderer.render(@TypeOf(container), &container, App.Model, &app.model);
|
|
try renderer.flush();
|
|
}
|
|
}
|
|
|
|
pub const panic = App.panic_handler;
|
|
const log = std.log.scoped(.default);
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const zterm = @import("zterm");
|
|
|
|
const App = zterm.App(struct {}, union(enum) {});
|