use vaxis for tui
This commit is contained in:
parent
9c6bae6963
commit
bd336e5ba8
|
@ -99,6 +99,12 @@ fn buildTui(b: *Build, target: ResolvedTarget, optimize: OptimizeMode) !void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const vaxis_dep = b.dependency("vaxis", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
tui.root_module.addImport("vaxis", vaxis_dep.module("vaxis"));
|
||||
|
||||
b.installArtifact(tui);
|
||||
b.step("run-tui", "Run TUI").dependOn(&b.addRunArtifact(tui).step);
|
||||
}
|
||||
|
|
|
@ -14,5 +14,9 @@
|
|||
.cimgui = .{
|
||||
.path = "3rd-party/cimgui",
|
||||
},
|
||||
.vaxis = .{
|
||||
.url = "git+https://github.com/rockorager/libvaxis?ref=main#235e0bb27bc89655d8c66fc6378b31d4a98a117a",
|
||||
.hash = "1220fef553676a4c90035db27c13ad609d5823198a94cc971e95ab9c680e3dcd2df0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
199
src/tui/main.zig
199
src/tui/main.zig
|
@ -1,11 +1,196 @@
|
|||
// const std = @import("std");
|
||||
|
||||
// pub fn main() !void {
|
||||
// const stdout = std.io.getStdOut().writer();
|
||||
// try stdout.print("Can spawn process: {}\n", .{std.process.can_spawn});
|
||||
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// const alloc = gpa.allocator();
|
||||
// var lldb_child = std.process.Child.init(&[_][]const u8{"lldb-vscode"}, alloc);
|
||||
// _ = try lldb_child.spawnAndWait();
|
||||
// }
|
||||
|
||||
const std = @import("std");
|
||||
const vaxis = @import("vaxis");
|
||||
|
||||
pub fn main() !void {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
try stdout.print("Can spawn process: {}\n", .{std.process.can_spawn});
|
||||
/// Set the default panic handler to the vaxis panic_handler. This will clean up the terminal if any
|
||||
/// panics occur
|
||||
pub const panic = vaxis.panic_handler;
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const alloc = gpa.allocator();
|
||||
var lldb_child = std.process.Child.init(&[_][]const u8{"lldb-vscode"}, alloc);
|
||||
_ = try lldb_child.spawnAndWait();
|
||||
/// Set some scope levels for the vaxis scopes
|
||||
pub const std_options: std.Options = .{
|
||||
.log_scope_levels = &.{
|
||||
.{ .scope = .vaxis, .level = .warn },
|
||||
.{ .scope = .vaxis_parser, .level = .warn },
|
||||
},
|
||||
};
|
||||
|
||||
/// Tagged union of all events our application will handle. These can be generated by Vaxis or your
|
||||
/// own custom events
|
||||
const Event = union(enum) {
|
||||
key_press: vaxis.Key,
|
||||
key_release: vaxis.Key,
|
||||
mouse: vaxis.Mouse,
|
||||
focus_in, // window has gained focus
|
||||
focus_out, // window has lost focus
|
||||
paste_start, // bracketed paste start
|
||||
paste_end, // bracketed paste end
|
||||
paste: []const u8, // osc 52 paste, caller must free
|
||||
color_report: vaxis.Color.Report, // osc 4, 10, 11, 12 response
|
||||
color_scheme: vaxis.Color.Scheme, // light / dark OS theme changes
|
||||
winsize: vaxis.Winsize, // the window size has changed. This event is always sent when the loop
|
||||
// is started
|
||||
};
|
||||
|
||||
/// The application state
|
||||
const MyApp = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
// A flag for if we should quit
|
||||
should_quit: bool,
|
||||
/// The tty we are talking to
|
||||
tty: vaxis.Tty,
|
||||
/// The vaxis instance
|
||||
vx: vaxis.Vaxis,
|
||||
/// A mouse event that we will handle in the draw cycle
|
||||
mouse: ?vaxis.Mouse,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !MyApp {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.should_quit = false,
|
||||
.tty = try vaxis.Tty.init(),
|
||||
.vx = try vaxis.init(allocator, .{}),
|
||||
.mouse = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *MyApp) void {
|
||||
// Deinit takes an optional allocator. You can choose to pass an allocator to clean up
|
||||
// memory, or pass null if your application is shutting down and let the OS clean up the
|
||||
// memory
|
||||
self.vx.deinit(self.allocator, self.tty.anyWriter());
|
||||
self.tty.deinit();
|
||||
}
|
||||
|
||||
pub fn run(self: *MyApp) !void {
|
||||
// Initialize our event loop. This particular loop requires intrusive init
|
||||
var loop: vaxis.Loop(Event) = .{
|
||||
.tty = &self.tty,
|
||||
.vaxis = &self.vx,
|
||||
};
|
||||
try loop.init();
|
||||
|
||||
// Start the event loop. Events will now be queued
|
||||
try loop.start();
|
||||
|
||||
try self.vx.enterAltScreen(self.tty.anyWriter());
|
||||
|
||||
// Query the terminal to detect advanced features, such as kitty keyboard protocol, etc.
|
||||
// This will automatically enable the features in the screen you are in, so you will want to
|
||||
// call it after entering the alt screen if you are a full screen application. The second
|
||||
// arg is a timeout for the terminal to send responses. Typically the response will be very
|
||||
// fast, however it could be slow on ssh connections.
|
||||
try self.vx.queryTerminal(self.tty.anyWriter(), 1 * std.time.ns_per_s);
|
||||
|
||||
// Enable mouse events
|
||||
try self.vx.setMouseMode(self.tty.anyWriter(), true);
|
||||
|
||||
// This is the main event loop. The basic structure is
|
||||
// 1. Handle events
|
||||
// 2. Draw application
|
||||
// 3. Render
|
||||
while (!self.should_quit) {
|
||||
// pollEvent blocks until we have an event
|
||||
loop.pollEvent();
|
||||
// tryEvent returns events until the queue is empty
|
||||
while (loop.tryEvent()) |event| {
|
||||
try self.update(event);
|
||||
}
|
||||
// Draw our application after handling events
|
||||
self.draw();
|
||||
|
||||
// It's best to use a buffered writer for the render method. TTY provides one, but you
|
||||
// may use your own. The provided bufferedWriter has a buffer size of 4096
|
||||
var buffered = self.tty.bufferedWriter();
|
||||
// Render the application to the screen
|
||||
try self.vx.render(buffered.writer().any());
|
||||
try buffered.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// Update our application state from an event
|
||||
pub fn update(self: *MyApp, event: Event) !void {
|
||||
switch (event) {
|
||||
.key_press => |key| {
|
||||
// key.matches does some basic matching algorithms. Key matching can be complex in
|
||||
// the presence of kitty keyboard encodings, this will generally be a good approach.
|
||||
// There are other matching functions available for specific purposes, as well
|
||||
if (key.matches('c', .{ .ctrl = true }))
|
||||
self.should_quit = true;
|
||||
},
|
||||
.mouse => |mouse| self.mouse = mouse,
|
||||
.winsize => |ws| try self.vx.resize(self.allocator, self.tty.anyWriter(), ws),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw our current state
|
||||
pub fn draw(self: *MyApp) void {
|
||||
const msg = "Hello, world!";
|
||||
|
||||
// Window is a bounded area with a view to the screen. You cannot draw outside of a windows
|
||||
// bounds. They are light structures, not intended to be stored.
|
||||
const win = self.vx.window();
|
||||
|
||||
// Clearing the window has the effect of setting each cell to it's "default" state. Vaxis
|
||||
// applications typically will be immediate mode, and you will redraw your entire
|
||||
// application during the draw cycle.
|
||||
win.clear();
|
||||
|
||||
// In addition to clearing our window, we want to clear the mouse shape state since we may
|
||||
// be changing that as well
|
||||
self.vx.setMouseShape(.default);
|
||||
|
||||
const child = win.child(.{
|
||||
.x_off = (win.width / 2) - 7,
|
||||
.y_off = win.height / 2 + 1,
|
||||
.width = .{ .limit = msg.len },
|
||||
.height = .{ .limit = 1 },
|
||||
});
|
||||
|
||||
// mouse events are much easier to handle in the draw cycle. Windows have a helper method to
|
||||
// determine if the event occurred in the target window. This method returns null if there
|
||||
// is no mouse event, or if it occurred outside of the window
|
||||
const style: vaxis.Style = if (child.hasMouse(self.mouse)) |_| blk: {
|
||||
// We handled the mouse event, so set it to null
|
||||
self.mouse = null;
|
||||
self.vx.setMouseShape(.pointer);
|
||||
break :blk .{ .reverse = true };
|
||||
} else .{};
|
||||
|
||||
// Print a text segment to the screen. This is a helper function which iterates over the
|
||||
// text field for graphemes. Alternatively, you can implement your own print functions and
|
||||
// use the writeCell API.
|
||||
_ = try child.printSegment(.{ .text = msg, .style = style }, .{});
|
||||
}
|
||||
};
|
||||
|
||||
/// Keep our main function small. Typically handling arg parsing and initialization only
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer {
|
||||
const deinit_status = gpa.deinit();
|
||||
//fail test; can't try in defer as defer is executed after we return
|
||||
if (deinit_status == .leak) {
|
||||
std.log.err("memory leak", .{});
|
||||
}
|
||||
}
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// Initialize our application
|
||||
var app = try MyApp.init(allocator);
|
||||
defer app.deinit();
|
||||
|
||||
// Run the application
|
||||
try app.run();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue