mod(tui): create own terminal interface framework

This commit is contained in:
2024-11-04 22:27:45 +01:00
parent 0330b3a2f5
commit 14aab9ef50
9 changed files with 559 additions and 96 deletions

90
src/app.zig Normal file
View File

@@ -0,0 +1,90 @@
//! Application type for TUI-applications
const std = @import("std");
const terminal = @import("terminal.zig");
const mergeTaggedUnions = @import("event.zig").mergeTaggedUnions;
const isTaggedUnion = @import("event.zig").isTaggedUnion;
const BuiltinEvent = @import("event.zig").BuiltinEvent;
const Queue = @import("queue.zig").Queue;
const log = std.log.scoped(.app);
// Create the App Type with the associated user events `E` which describes
// an tagged union for all the user events that can be send through the
// applications event loop.
pub fn App(comptime E: type) type {
if (!isTaggedUnion(E)) {
@compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`.");
}
return struct {
pub const Event = mergeTaggedUnions(BuiltinEvent, E);
pub const Layout = @import("layout.zig").Layout(Event);
pub const Widget = @import("widget.zig").Widget(Event);
queue: Queue(Event, 256) = .{},
thread: ?std.Thread = null,
quit: bool = false,
termios: ?std.posix.termios = null,
// TODO: event loop function?
// layout handling?
pub fn init() @This() {
return .{};
}
pub fn start(this: *@This()) !void {
if (this.thread) |_| return;
this.thread = try std.Thread.spawn(.{}, @This().run, .{this});
var termios: std.posix.termios = undefined;
try terminal.enableRawMode(&termios);
this.termios = termios;
try terminal.saveScreen();
}
pub fn stop(this: *@This()) !void {
if (this.termios) |*termios| {
try terminal.disableRawMode(termios);
try terminal.restoreScreen();
}
this.quit = true;
if (this.thread) |thread| {
thread.join();
this.thread = null;
}
}
/// Returns the next available event, blocking until one is available.
pub fn nextEvent(this: *@This()) Event {
return this.queue.pop();
}
/// Post an event into the queue. Blocks if there is no capacity for the event.
pub fn postEvent(this: *@This(), event: Event) void {
this.queue.push(event);
}
fn run(this: *@This()) !void {
// thread to read user inputs
const size = terminal.getTerminalSize();
this.postEvent(.{ .resize = size });
// read input in loop
const buf: [256]u8 = undefined;
_ = buf;
while (!this.quit) {
std.time.sleep(5 * std.time.ns_per_s);
break;
// try terminal.read(buf[0..]);
// TODO: send corresponding events with key_presses
// -> create corresponding event
// -> handle key inputs (modifier, op codes, etc.)
// -> I could take inspiration from `libvaxis` for this
}
// FIXME: here is a race-condition -> i.e. there could be events in
// the queue, but they will not be executed because the main loop
// will close!
this.postEvent(.quit);
}
};
}