diff --git a/examples/container.zig b/examples/container.zig new file mode 100644 index 0000000..2b4f1e7 --- /dev/null +++ b/examples/container.zig @@ -0,0 +1,114 @@ +const std = @import("std"); +const zterm = @import("zterm"); + +const App = zterm.App( + union(enum) {}, + zterm.Renderer.Direct, + true, +); +const Key = zterm.Key; +const Layout = App.Layout; +const Widget = App.Widget; + +const log = std.log.scoped(.container); + +pub fn main() !void { + errdefer |err| log.err("Application Error: {any}", .{err}); + + 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) { + log.err("memory leak", .{}); + } + } + const allocator = gpa.allocator(); + + var app: App = .{}; + var renderer: App.Renderer = .{}; + // TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents + // -> size hint how much should it use? + + var layout = Layout.createFrom(layout: { + var stack = Layout.HContainer.init(allocator, .{ + .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + 15, + }, + .{ + Layout.createFrom(container: { + var container = Layout.VContainer.init(allocator, .{ + .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + 25, + }, + .{ + Widget.createFrom(blk: { + const file = try std.fs.cwd().openFile("./src/app.zig", .{}); + defer file.close(); + var widget = Widget.RawText.init(allocator, file); + break :blk &widget; + }), + 50, + }, + .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + 25, + }, + }); + break :container &container; + }), + 70, + }, + .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + 15, + }, + }); + break :layout &stack; + }); + defer layout.deinit(); + + try app.start(); + defer app.stop() catch unreachable; + + // App.Event loop + while (true) { + const event = app.nextEvent(); + log.debug("received event: {s}", .{@tagName(event)}); + + switch (event) { + .quit => break, + .resize => |size| { + renderer.resize(size); + }, + .key => |key| { + // ctrl+c to quit + if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + app.quit(); + } + }, + .err => |err| { + log.err("Received {any} with message: {s}", .{ err.err, err.msg }); + }, + } + const events = try layout.handle(event); + for (events.items) |e| { + app.postEvent(e); + } + try layout.render(&renderer); + } +} diff --git a/examples/exec.zig b/examples/exec.zig new file mode 100644 index 0000000..b0f55be --- /dev/null +++ b/examples/exec.zig @@ -0,0 +1,114 @@ +const std = @import("std"); +const zterm = @import("zterm"); + +const App = zterm.App( + union(enum) {}, + zterm.Renderer.Direct, + true, +); +const Key = zterm.Key; +const Cell = zterm.Cell; +const Layout = App.Layout; +const Widget = App.Widget; + +const log = std.log.scoped(.exec); + +pub fn main() !void { + errdefer |err| log.err("Application Error: {any}", .{err}); + + 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) { + log.err("memory leak", .{}); + } + } + const allocator = gpa.allocator(); + + var app: App = .{}; + var renderer: App.Renderer = .{}; + // TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents + // -> size hint how much should it use? + + var layout = Layout.createFrom(layout: { + var container = Layout.VContainer.init(allocator, .{ + .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + 45, + }, + .{ + Layout.createFrom(framing: { + var framing = Layout.Framing.init(allocator, .{}, .{ + .widget = Widget.createFrom(blk: { + var widget = Widget.Text.init(&[_]Cell{ + .{ .content = "Press " }, + .{ .content = "Ctrl+n", .style = .{ .fg = .{ .index = 6 } } }, + .{ .content = " to launch $EDITOR" }, + }); + break :blk &widget; + }), + }); + break :framing &framing; + }), + 10, + }, + .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + 45, + }, + }); + break :layout &container; + }); + defer layout.deinit(); + + try app.start(); + defer app.stop() catch unreachable; + + // App.Event loop + while (true) { + const event = app.nextEvent(); + log.debug("received event: {s}", .{@tagName(event)}); + + switch (event) { + .quit => break, + .resize => |size| { + renderer.resize(size); + }, + .key => |key| { + // ctrl+c to quit + if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + app.quit(); + } + if (Key.matches(key, .{ .cp = 'n', .mod = .{ .ctrl = true } })) { + try app.interrupt(); + defer app.start() catch @panic("could not start app event loop"); + // TODO: parse environment variables to extract the value of $EDITOR and use it here instead + var child = std.process.Child.init(&.{"hx"}, allocator); + _ = child.spawnAndWait() catch |err| { + app.postEvent(.{ + .err = .{ + .err = err, + .msg = "Spawning $EDITOR failed", + }, + }); + }; + } + }, + .err => |err| { + log.err("Received {any} with message: {s}", .{ err.err, err.msg }); + }, + } + const events = try layout.handle(event); + for (events.items) |e| { + app.postEvent(e); + } + try layout.render(&renderer); + } +} diff --git a/examples/padding.zig b/examples/padding.zig new file mode 100644 index 0000000..2f260f9 --- /dev/null +++ b/examples/padding.zig @@ -0,0 +1,112 @@ +const std = @import("std"); +const zterm = @import("zterm"); + +const App = zterm.App( + union(enum) {}, + zterm.Renderer.Direct, + true, +); +const Key = zterm.Key; +const Layout = App.Layout; +const Widget = App.Widget; + +const log = std.log.scoped(.padding); + +pub fn main() !void { + errdefer |err| log.err("Application Error: {any}", .{err}); + + 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) { + log.err("memory leak", .{}); + } + } + const allocator = gpa.allocator(); + + var app: App = .{}; + var renderer: App.Renderer = .{}; + // TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents + // -> size hint how much should it use? + + var layout = Layout.createFrom(layout: { + var padding = Layout.Padding.init(allocator, .{ + .padding = 15, + }, .{ + .layout = Layout.createFrom(framing: { + var framing = Layout.Framing.init( + allocator, + .{ + .style = .{ + .fg = .{ + .index = 6, + }, + }, + .frame = .round, + .title = .{ + .str = "Content in Margin", + .style = .{ + .ul_style = .single, + .ul = .{ .index = 6 }, + .bold = true, + }, + }, + }, + .{ + .layout = Layout.createFrom(margin: { + var margin = Layout.Margin.init( + allocator, + .{ + .margin = 10, + }, + .{ + .widget = Widget.createFrom(blk: { + const file = try std.fs.cwd().openFile("./examples/padding.zig", .{}); + defer file.close(); + var widget = Widget.RawText.init(allocator, file); + break :blk &widget; + }), + }, + ); + break :margin &margin; + }), + }, + ); + break :framing &framing; + }), + }); + break :layout &padding; + }); + defer layout.deinit(); + + try app.start(); + defer app.stop() catch unreachable; + + // App.Event loop + while (true) { + const event = app.nextEvent(); + log.debug("received event: {s}", .{@tagName(event)}); + + switch (event) { + .quit => break, + .resize => |size| { + renderer.resize(size); + }, + .key => |key| { + // ctrl+c to quit + if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + app.quit(); + } + }, + .err => |err| { + log.err("Received {any} with message: {s}", .{ err.err, err.msg }); + }, + } + const events = try layout.handle(event); + for (events.items) |e| { + app.postEvent(e); + } + try layout.render(&renderer); + } +} diff --git a/examples/stack.zig b/examples/stack.zig new file mode 100644 index 0000000..debfae0 --- /dev/null +++ b/examples/stack.zig @@ -0,0 +1,156 @@ +const std = @import("std"); +const zterm = @import("zterm"); + +const App = zterm.App( + union(enum) {}, + zterm.Renderer.Direct, + true, +); +const Key = zterm.Key; +const Layout = App.Layout; +const Widget = App.Widget; + +const log = std.log.scoped(.stack); + +pub fn main() !void { + errdefer |err| log.err("Application Error: {any}", .{err}); + + 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) { + log.err("memory leak", .{}); + } + } + const allocator = gpa.allocator(); + + var app: App = .{}; + var renderer: App.Renderer = .{}; + // TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents + // -> size hint how much should it use? + + var layout = Layout.createFrom(layout: { + var framing = Layout.Framing.init(allocator, .{ + .style = .{ + .fg = .{ + .index = 6, + }, + }, + .frame = .round, + .title = .{ + .str = "HStack", + .style = .{ + .ul_style = .single, + .ul = .{ .index = 6 }, + .bold = true, + }, + }, + }, .{ + .layout = Layout.createFrom(hstack: { + var hstack = Layout.HStack.init(allocator, .{ + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + Layout.createFrom(framing: { + var framing = Layout.Framing.init( + allocator, + .{ + .style = .{ + .fg = .{ + .index = 6, + }, + }, + .frame = .round, + .title = .{ + .str = "VStack", + .style = .{ + .ul_style = .single, + .ul = .{ .index = 6 }, + .bold = true, + }, + }, + }, + .{ + .layout = Layout.createFrom( + padding: { + var padding = Layout.Margin.init( + allocator, + .{ + .margin = 10, + }, + .{ + .layout = Layout.createFrom(vstack: { + var vstack = Layout.VStack.init(allocator, .{ + Widget.createFrom(blk: { + const file = try std.fs.cwd().openFile("./examples/stack.zig", .{}); + defer file.close(); + var widget = Widget.RawText.init(allocator, file); + break :blk &widget; + }), + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + Widget.createFrom(blk: { + const file = try std.fs.cwd().openFile("./examples/stack.zig", .{}); + defer file.close(); + var widget = Widget.RawText.init(allocator, file); + break :blk &widget; + }), + }); + break :vstack &vstack; + }), + }, + ); + break :padding &padding; + }, + ), + }, + ); + break :framing &framing; + }), + Widget.createFrom(blk: { + var spacer = Widget.Spacer.init(); + break :blk &spacer; + }), + }); + break :hstack &hstack; + }), + }); + break :layout &framing; + }); + defer layout.deinit(); + + try app.start(); + defer app.stop() catch unreachable; + + // App.Event loop + while (true) { + const event = app.nextEvent(); + log.debug("received event: {s}", .{@tagName(event)}); + + switch (event) { + .quit => break, + .resize => |size| { + renderer.resize(size); + }, + .key => |key| { + // ctrl+c to quit + if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + app.quit(); + } + }, + .err => |err| { + log.err("Received {any} with message: {s}", .{ err.err, err.msg }); + }, + } + // NOTE: this currently re-renders the screen for every key-press -> which might be a bit of an overkill + const events = try layout.handle(event); + for (events.items) |e| { + app.postEvent(e); + } + try layout.render(&renderer); + } +} diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index 7289587..0000000 --- a/src/main.zig +++ /dev/null @@ -1,208 +0,0 @@ -const std = @import("std"); -const zterm = @import("zterm"); - -const App = zterm.App( - union(enum) {}, - zterm.Renderer.Direct, - true, -); -const Key = zterm.Key; -const Layout = App.Layout; -const Widget = App.Widget; - -const log = std.log.scoped(.default); - -pub fn main() !void { - errdefer |err| log.err("Application Error: {any}", .{err}); - - 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) { - log.err("memory leak", .{}); - } - } - const allocator = gpa.allocator(); - - var app: App = .{}; - var renderer: App.Renderer = .{}; - // TODO: when not running fullscreen, the application needs to screen down accordingly to display the contents - // -> size hint how much should it use? - - // var layout = Layout.createFrom(layout: { - // var stack = Layout.HStack.init(allocator, .{ - // Widget.createFrom(blk: { - // var spacer = Widget.Spacer.init(); - // break :blk &spacer; - // }), - // Widget.createFrom(blk: { - // const file = try std.fs.cwd().openFile("./src/app.zig", .{}); - // defer file.close(); - // var widget = Widget.RawText.init(allocator, file); - // break :blk &widget; - // }), - // Widget.createFrom(blk: { - // var spacer = Widget.Spacer.init(); - // break :blk &spacer; - // }), - // }); - // break :layout &stack; - // }); - var layout = Layout.createFrom( - padding: { - var padding = Layout.Margin.init( - allocator, - .{ - .left = 15, - .right = 15, - }, - .{ - .layout = Layout.createFrom(framing: { - var framing = Layout.Framing.init(allocator, .{ .style = .{ .fg = .{ .index = 6 } } }, .{ - .layout = Layout.createFrom(vstack: { - var vstack = Layout.VStack.init(allocator, .{ - Widget.createFrom(blk: { - const file = try std.fs.cwd().openFile("./src/app.zig", .{}); - defer file.close(); - var widget = Widget.RawText.init(allocator, file); - break :blk &widget; - }), - Widget.createFrom(blk: { - var spacer = Widget.Spacer.init(); - break :blk &spacer; - }), - Widget.createFrom(blk: { - const file = try std.fs.cwd().openFile("./src/main.zig", .{}); - defer file.close(); - var widget = Widget.RawText.init(allocator, file); - break :blk &widget; - }), - }); - break :vstack &vstack; - }), - }); - break :framing &framing; - }), - }, - ); - break :padding &padding; - }, - ); - // var layout = Layout.createFrom(layout: { - // var hstack = Layout.HStack.init(allocator, .{ - // Widget.createFrom(blk: { - // var spacer = Widget.Spacer.init(); - // break :blk &spacer; - // }), - // Layout.createFrom(framing: { - // var framing = Layout.Framing.init( - // allocator, - // .{ - // .style = .{ - // .fg = .{ - // .index = 6, - // }, - // }, - // .frame = .round, - // .title = .{ - // .str = "VStack", - // .style = .{ - // .ul_style = .single, - // .ul = .{ .index = 6 }, - // .bold = true, - // }, - // }, - // }, - // .{ - // .layout = Layout.createFrom( - // padding: { - // var padding = Layout.Margin.init( - // allocator, - // .{ - // .margin = 10, - // }, - // .{ - // .layout = Layout.createFrom(vstack: { - // var vstack = Layout.VStack.init(allocator, .{ - // Widget.createFrom(blk: { - // const file = try std.fs.cwd().openFile("./src/app.zig", .{}); - // defer file.close(); - // var widget = Widget.RawText.init(allocator, file); - // break :blk &widget; - // }), - // Widget.createFrom(blk: { - // var spacer = Widget.Spacer.init(); - // break :blk &spacer; - // }), - // Widget.createFrom(blk: { - // const file = try std.fs.cwd().openFile("./src/main.zig", .{}); - // defer file.close(); - // var widget = Widget.RawText.init(allocator, file); - // break :blk &widget; - // }), - // }); - // break :vstack &vstack; - // }), - // }, - // ); - // break :padding &padding; - // }, - // ), - // }, - // ); - // break :framing &framing; - // }), - // Widget.createFrom(blk: { - // var spacer = Widget.Spacer.init(); - // break :blk &spacer; - // }), - // }); - // break :layout &hstack; - // }); - defer layout.deinit(); - - try app.start(); - defer app.stop() catch unreachable; - - // App.Event loop - while (true) { - const event = app.nextEvent(); - log.debug("received event: {s}", .{@tagName(event)}); - - switch (event) { - .quit => break, - .resize => |size| { - renderer.resize(size); - }, - .key => |key| { - // ctrl+c to quit - if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { - app.quit(); - } - if (Key.matches(key, .{ .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 Helix failed", - }, - }); - }; - } - }, - .err => |err| { - log.err("Received {any} with message: {s}", .{ err.err, err.msg }); - }, - } - // NOTE: this currently re-renders the screen for every key-press -> which might be a bit of an overkill - const events = try layout.handle(event); - for (events.items) |e| { - app.postEvent(e); - } - try layout.render(&renderer); - } -}