From 6ccab74c949646655f679c8f906647e0b656edd4 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Fri, 21 Feb 2025 22:57:14 +0100 Subject: [PATCH] add(examples/demo): application to showcase a more complex application Further improvements for example applications; Demo example is now default build target (when not providing example configuration). --- build.zig | 13 ++- examples/demo.zig | 144 ++++++++++++++++++++++++++++++++ examples/elements/button.zig | 4 +- examples/elements/input.zig | 4 +- examples/errors.zig | 5 +- examples/layouts/grid.zig | 5 +- examples/layouts/horizontal.zig | 5 +- examples/layouts/mixed.zig | 5 +- examples/layouts/vertical.zig | 5 +- examples/styles/palette.zig | 5 +- examples/styles/text.zig | 5 +- 11 files changed, 172 insertions(+), 28 deletions(-) create mode 100644 examples/demo.zig diff --git a/build.zig b/build.zig index 1d89c02..ed9a1a5 100644 --- a/build.zig +++ b/build.zig @@ -5,6 +5,7 @@ pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const Examples = enum { + demo, // elements: button, input, @@ -21,7 +22,7 @@ pub fn build(b: *std.Build) void { errors, }; - const example = b.option(Examples, "example", "Example to build and/or run. (default: vertical)") orelse .vertical; + const example = b.option(Examples, "example", "Example to build and/or run. (default: demo)") orelse .demo; const options = b.addOptions(); options.addOption(Examples, "example", example); @@ -42,6 +43,15 @@ pub fn build(b: *std.Build) void { //--- Examples --- + // demo: + const demo = b.addExecutable(.{ + .name = "demo", + .root_source_file = b.path("examples/demo.zig"), + .target = target, + .optimize = optimize, + }); + demo.root_module.addImport("zterm", lib); + // elements: const button = b.addExecutable(.{ .name = "button", @@ -128,6 +138,7 @@ pub fn build(b: *std.Build) void { // mapping of user selected example to compile step const exe = switch (example) { + .demo => demo, // elements: .button => button, .input => input, diff --git a/examples/demo.zig b/examples/demo.zig new file mode 100644 index 0000000..c8d9f26 --- /dev/null +++ b/examples/demo.zig @@ -0,0 +1,144 @@ +const std = @import("std"); +const zterm = @import("zterm"); + +const input = zterm.input; +const App = zterm.App(union(enum) {}); + +const log = std.log.scoped(.default); + +const QuitText = struct { + const text = "Press ctrl+c to quit. Press ctrl+n to launch helix."; + + pub fn element(this: *@This()) App.Element { + return .{ .ptr = this, .vtable = &.{ .content = content } }; + } + + pub fn content(ctx: *anyopaque, cells: []zterm.Cell, size: zterm.Size) !void { + _ = ctx; + std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows)); + + const row = 2; + const col = size.cols / 2 -| (text.len / 2); + const anchor = (row * size.cols) + 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}); + + // TODO: maybe create own allocator as some sort of arena allocator to have consistent memory usage + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .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 = .{}; + + // TODO: what should the demo application do? + // - some sort of chat? -> write messages and have them displayed in a scrollable array at the right hand side? + // - on the left some buttons? + var box = try App.Container.init(allocator, .{ + .layout = .{ + .gap = 1, + .padding = .vertical(2), + }, + .min_size = .{ .cols = 50 }, + }, .{}); + try box.append(try App.Container.init(allocator, .{ + .rectangle = .{ .fill = .light_green }, + }, .{})); + try box.append(try App.Container.init(allocator, .{ + .rectangle = .{ .fill = .light_green }, + }, .{})); + try box.append(try App.Container.init(allocator, .{ + .rectangle = .{ .fill = .light_green }, + }, .{})); + defer box.deinit(); + + var scrollable: App.Scrollable = .{ + .container = box, + }; + + var container = try App.Container.init(allocator, .{ + .border = .{ + .separator = .{ + .enabled = true, + .line = .double, + }, + }, + .layout = .{ + .gap = 2, + .padding = .{ .top = 5, .bottom = 3, .left = 3, .right = 3 }, + .direction = .horizontal, + }, + }, quit_text.element()); + try container.append(try App.Container.init(allocator, .{}, scrollable.element())); + + try container.append(try App.Container.init(allocator, .{ + .border = .{ + .color = .light_blue, + .sides = .all, + }, + }, .{})); + try container.append(try App.Container.init(allocator, .{ + .rectangle = .{ .fill = .blue }, + }, .{})); + defer container.deinit(); // also de-initializes the children + + try app.start(); + defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err}); + + // event loop + while (true) { + const event = app.nextEvent(); + log.debug("received event: {s}", .{@tagName(event)}); + + switch (event) { + .init => continue, + .quit => break, + .resize => |size| try renderer.resize(size), + .key => |key| { + if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(); + + if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { + try app.interrupt(); + defer app.start() catch @panic("could not start app event loop"); + var child = std.process.Child.init(&.{"hx"}, allocator); + _ = child.spawnAndWait() catch |err| app.postEvent(.{ + .err = .{ + .err = err, + .msg = "Spawning $EDITOR failed", + }, + }); + continue; + } + }, + // NOTE: errors could be displayed in another container in case one was received, etc. to provide the user with feedback + .err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }), + else => {}, + } + + // NOTE: returned errors should be propagated back to the application + container.handle(event) catch |err| app.postEvent(.{ + .err = .{ + .err = err, + .msg = "Container Event handling failed", + }, + }); + try renderer.render(@TypeOf(container), &container); + try renderer.flush(); + } +} diff --git a/examples/elements/button.zig b/examples/elements/button.zig index 5ed28dd..2b28288 100644 --- a/examples/elements/button.zig +++ b/examples/elements/button.zig @@ -52,9 +52,7 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); const allocator = gpa.allocator(); diff --git a/examples/elements/input.zig b/examples/elements/input.zig index 2c1b0d7..31b5c08 100644 --- a/examples/elements/input.zig +++ b/examples/elements/input.zig @@ -72,9 +72,7 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); const allocator = gpa.allocator(); diff --git a/examples/errors.zig b/examples/errors.zig index 6172f3f..118f6b5 100644 --- a/examples/errors.zig +++ b/examples/errors.zig @@ -101,9 +101,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init; diff --git a/examples/layouts/grid.zig b/examples/layouts/grid.zig index 2c1d6e3..5c19cab 100644 --- a/examples/layouts/grid.zig +++ b/examples/layouts/grid.zig @@ -38,9 +38,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init; diff --git a/examples/layouts/horizontal.zig b/examples/layouts/horizontal.zig index bf13626..f7e78c8 100644 --- a/examples/layouts/horizontal.zig +++ b/examples/layouts/horizontal.zig @@ -38,9 +38,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init; diff --git a/examples/layouts/mixed.zig b/examples/layouts/mixed.zig index 3f19100..ed0c2cb 100644 --- a/examples/layouts/mixed.zig +++ b/examples/layouts/mixed.zig @@ -38,9 +38,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init; diff --git a/examples/layouts/vertical.zig b/examples/layouts/vertical.zig index 3fdb5e8..fa6c4fc 100644 --- a/examples/layouts/vertical.zig +++ b/examples/layouts/vertical.zig @@ -38,9 +38,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init; diff --git a/examples/styles/palette.zig b/examples/styles/palette.zig index 182bc79..4688cae 100644 --- a/examples/styles/palette.zig +++ b/examples/styles/palette.zig @@ -35,9 +35,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init; diff --git a/examples/styles/text.zig b/examples/styles/text.zig index c53f48d..7e44b3e 100644 --- a/examples/styles/text.zig +++ b/examples/styles/text.zig @@ -86,9 +86,8 @@ pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) { - log.err("memory leak", .{}); - }; + defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); + const allocator = gpa.allocator(); var app: App = .init;