Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 55s
The renderer will provide `resize`, `reposition` and `minSize` (for the `Scrollable` `Element`) with a read-only pointer to the model of the application (similar to how it is already done for `handle` and `content`). Every interface function now has the same data that it can each use for implementing its corresponding task based on local and shared variables through the element instance and model pointer.
178 lines
5.6 KiB
Zig
178 lines
5.6 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;
|
|
}
|
|
}
|
|
};
|
|
|
|
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 threaded_io: std.Io.Threaded = .init(allocator);
|
|
defer threaded_io.deinit();
|
|
|
|
var app: App = .init(threaded_io.ioBasic(), .{});
|
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
|
defer renderer.deinit();
|
|
|
|
var progress_percent: u8 = 0;
|
|
var quit_text: QuitText = .{};
|
|
|
|
var container = try App.Container.init(allocator, .{
|
|
.layout = .{ .padding = .all(5), .direction = .vertical },
|
|
}, quit_text.element());
|
|
defer container.deinit();
|
|
|
|
{
|
|
var progress: App.Progress(.progress) = .init(&app.queue, .{
|
|
.percent = .{
|
|
.enabled = true,
|
|
.alignment = .left,
|
|
},
|
|
.fg = .blue,
|
|
.bg = .grey,
|
|
});
|
|
try container.append(try App.Container.init(allocator, .{}, progress.element()));
|
|
}
|
|
{
|
|
var progress: App.Progress(.progress) = .init(&app.queue, .{
|
|
.percent = .{
|
|
.enabled = true,
|
|
.alignment = .middle, // default
|
|
},
|
|
.fg = .red,
|
|
.bg = .grey,
|
|
});
|
|
try container.append(try App.Container.init(allocator, .{}, progress.element()));
|
|
}
|
|
{
|
|
var progress: App.Progress(.progress) = .init(&app.queue, .{
|
|
.percent = .{
|
|
.enabled = true,
|
|
.alignment = .right,
|
|
},
|
|
.fg = .green,
|
|
.bg = .grey,
|
|
});
|
|
try container.append(try App.Container.init(allocator, .{}, progress.element()));
|
|
}
|
|
{
|
|
var progress: App.Progress(.progress) = .init(&app.queue, .{
|
|
.percent = .{ .enabled = false },
|
|
.fg = .default,
|
|
.bg = .grey,
|
|
});
|
|
try container.append(try App.Container.init(allocator, .{}, progress.element()));
|
|
}
|
|
try app.start();
|
|
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err});
|
|
|
|
var framerate: u64 = 60;
|
|
var tick_ms: u64 = @divFloor(time.ms_per_s, framerate);
|
|
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
|
|
for (0..len) |_| {
|
|
const event = app.queue.drain() orelse break;
|
|
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 }),
|
|
.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 => {},
|
|
}
|
|
|
|
container.handle(&app.model, event) catch |err| app.postEvent(.{
|
|
.err = .{
|
|
.err = err,
|
|
.msg = "Container Event handling failed",
|
|
},
|
|
});
|
|
|
|
// post event handling
|
|
switch (event) {
|
|
.quit => break :draw,
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
container.resize(&app.model, try renderer.resize());
|
|
container.reposition(&app.model, .{});
|
|
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 time = std.time;
|
|
const assert = std.debug.assert;
|
|
const zterm = @import("zterm");
|
|
const App = zterm.App(
|
|
struct {},
|
|
union(enum) {
|
|
progress: u8,
|
|
},
|
|
);
|