From 4ded0210ee5b0c6cbfceb8dffb73b34902fb8548 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Wed, 6 Nov 2024 17:06:45 +0100 Subject: [PATCH] mod(Render): fullscreen option as comptime configuration through App(..) --- src/app.zig | 51 +++++++++++++++++++++++++++++++++++++++++--------- src/main.zig | 15 +++++++-------- src/render.zig | 12 ++++++++---- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/app.zig b/src/app.zig index a1dc6a7..e651ea5 100644 --- a/src/app.zig +++ b/src/app.zig @@ -11,10 +11,34 @@ 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 { +/// 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. +/// +/// _R_ is the type function for the `Renderer` to use. The parameter boolean +/// will be set to the _fullscreen_ value at compile time. The corresponding +/// `Renderer` type is accessable through the generated type of this function. +/// +/// _fullscreen_ will be used to configure the `App` and the `Renderer` to +/// respect the corresponding configuration whether to render a fullscreen tui +/// or an inline tui. +/// +/// # Example +/// +/// Create an `App` which renders using the `PlainRenderer` in fullscreen with +/// an empty user Event: +/// +/// ```zig +/// const App = @import("app.zig").App( +/// union(enum) {}, +/// @import("render.zig").PlainRenderer, +/// true, +/// ); +/// // later on use +/// var app: App = .{}; +/// var renderer: App.Renderer = .{}; +/// ``` +pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fullscreen: bool) type { if (!isTaggedUnion(E)) { @compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`."); } @@ -22,6 +46,7 @@ pub fn App(comptime E: type) type { pub const Event = mergeTaggedUnions(SystemEvent, E); pub const Layout = @import("layout.zig").Layout(Event); pub const Widget = @import("widget.zig").Widget(Event); + pub const Renderer = R(fullscreen); queue: Queue(Event, 256) = .{}, thread: ?std.Thread = null, @@ -59,7 +84,10 @@ pub fn App(comptime E: type) type { var termios: std.posix.termios = undefined; try terminal.enableRawMode(&termios); this.termios = termios; - try terminal.saveScreen(); + if (fullscreen) { + try terminal.saveScreen(); + try terminal.enterAltScreen(); + } } pub fn interrupt(this: *@This()) !void { @@ -74,10 +102,15 @@ pub fn App(comptime E: type) type { try this.interrupt(); if (this.termios) |*termios| { try terminal.disableRawMode(termios); - try terminal.restoreScreen(); + if (fullscreen) { + try terminal.existAltScreen(); + try terminal.restoreScreen(); + } } } + /// Quit the application loop. + /// This will stop the internal input thread and post a **.quit** `Event`. pub fn quit(this: *@This()) void { this.quit_event.set(); this.postEvent(.quit); @@ -115,9 +148,9 @@ pub fn App(comptime E: type) type { fn run(this: *@This()) !void { // send initial terminal size - // changes are handled by the winch signal handler (see `init` and `registerWinch`) - const size = terminal.getTerminalSize(); - this.postEvent(.{ .resize = size }); + // changes are handled by the winch signal handler + // see `App.start` and `App.registerWinch` for details + this.postEvent(.{ .resize = terminal.getTerminalSize() }); // thread to read user inputs var buf: [256]u8 = undefined; diff --git a/src/main.zig b/src/main.zig index 33c7821..b2c024a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,8 +2,11 @@ const std = @import("std"); const terminal = @import("terminal.zig"); const zlog = @import("zlog"); -const App = @import("app.zig").App(union(enum) {}); -const Renderer = @import("render.zig").PlainRenderer(); +const App = @import("app.zig").App( + union(enum) {}, + @import("render.zig").PlainRenderer, + true, +); const Key = @import("key.zig"); pub const std_options = zlog.std_options; @@ -23,20 +26,16 @@ pub fn main() !void { const allocator = gpa.allocator(); var app: App = .{}; - var renderer: Renderer = .{}; + var renderer: App.Renderer = .{}; var rawText = App.Widget.RawText.init(allocator); const widget = App.Widget.createFrom(&rawText); var layout = App.Layout.Pane.init(allocator, widget); - defer layout.deinit(); + defer layout.deinit(); // deinitializes the contained widget try app.start(); defer app.stop() catch unreachable; - // NOTE: necessary for fullscreen tui applications - try terminal.enterAltScreen(); - defer terminal.existAltScreen() catch unreachable; - // App.Event loop while (true) { const event = app.nextEvent(); diff --git a/src/render.zig b/src/render.zig index b71dba3..58022ea 100644 --- a/src/render.zig +++ b/src/render.zig @@ -2,7 +2,7 @@ const std = @import("std"); const terminal = @import("terminal.zig"); -pub fn BufferedRenderer() type { +pub fn BufferedRenderer(comptime fullscreen: bool) type { return struct { refresh: bool = false, size: terminal.Size = undefined, @@ -10,6 +10,7 @@ pub fn BufferedRenderer() type { pub fn init(allocator: std.mem.Allocator) @This() { return .{ + .fullscreen = fullscreen, .screen = std.ArrayList(u8).init(allocator), }; } @@ -36,12 +37,15 @@ pub fn BufferedRenderer() type { }; } -pub fn PlainRenderer() type { +pub fn PlainRenderer(comptime fullscreen: bool) type { return struct { pub fn render(this: *@This(), content: *std.ArrayList(u8)) !void { _ = this; - try terminal.clearScreen(); - try terminal.setCursorPositionHome(); + if (fullscreen) { + try terminal.clearScreen(); + try terminal.setCursorPositionHome(); + } + // TODO: how would I clear the screen in case of a non fullscreen application (i.e. to clear to the start of the command) _ = try terminal.write(content.items); } };