From 58982a53f2ab5b5ae940bcd7081f34db1d314afd Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Fri, 15 Nov 2024 21:01:50 +0100 Subject: [PATCH] add(widget): Text widget to display static `Cell` contents --- build.zig | 70 ++++++++++++++++++++----------------------- src/terminal/Cell.zig | 2 +- src/widget.zig | 2 ++ src/widget/Text.zig | 50 +++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 38 deletions(-) create mode 100644 src/widget/Text.zig diff --git a/build.zig b/build.zig index 3755ebd..062f20b 100644 --- a/build.zig +++ b/build.zig @@ -32,41 +32,46 @@ pub fn build(b: *std.Build) void { lib.addImport("interface", interface.module("interface")); lib.addImport("code_point", zg.module("code_point")); - const exe = b.addExecutable(.{ - .name = "zterm", - .root_source_file = b.path("src/main.zig"), + // example executables + const stack_example = b.addExecutable(.{ + .name = "stack", + .root_source_file = b.path("examples/stack.zig"), .target = target, .optimize = optimize, }); - exe.root_module.addImport("zterm", lib); + stack_example.root_module.addImport("zterm", lib); + + const container_example = b.addExecutable(.{ + .name = "container", + .root_source_file = b.path("examples/container.zig"), + .target = target, + .optimize = optimize, + }); + container_example.root_module.addImport("zterm", lib); + + const padding_example = b.addExecutable(.{ + .name = "padding", + .root_source_file = b.path("examples/padding.zig"), + .target = target, + .optimize = optimize, + }); + padding_example.root_module.addImport("zterm", lib); + + const exec_example = b.addExecutable(.{ + .name = "exec", + .root_source_file = b.path("examples/exec.zig"), + .target = target, + .optimize = optimize, + }); + exec_example.root_module.addImport("zterm", lib); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). - b.installArtifact(exe); - - // This *creates* a Run step in the build graph, to be executed when another - // step is evaluated that depends on it. The next line below will establish - // such a dependency. - const run_cmd = b.addRunArtifact(exe); - - // By making the run step depend on the install step, it will be run from the - // installation directory rather than directly from within the cache directory. - // This is not necessary, however, if the application depends on other installed - // files, this ensures they will be present and in the expected location. - run_cmd.step.dependOn(b.getInstallStep()); - - // This allows the user to pass arguments to the application in the build - // command itself, like this: `zig build run -- arg1 arg2 etc` - if (b.args) |args| { - run_cmd.addArgs(args); - } - - // This creates a build step. It will be visible in the `zig build --help` menu, - // and can be selected like this: `zig build run` - // This will evaluate the `run` step rather than the default, which is "install". - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); + b.installArtifact(stack_example); + b.installArtifact(container_example); + b.installArtifact(padding_example); + b.installArtifact(exec_example); // Creates a step for unit testing. This only builds the test executable // but does not run it. @@ -79,18 +84,9 @@ pub fn build(b: *std.Build) void { const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); - const exe_unit_tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); - // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request // running the unit tests. const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_lib_unit_tests.step); - test_step.dependOn(&run_exe_unit_tests.step); } diff --git a/src/terminal/Cell.zig b/src/terminal/Cell.zig index b6d11d0..5bc726c 100644 --- a/src/terminal/Cell.zig +++ b/src/terminal/Cell.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub const Style = @import("Style.zig"); style: Style = .{}, -content: []u8 = undefined, +content: []const u8 = undefined, pub const Result = struct { idx: usize, diff --git a/src/widget.zig b/src/widget.zig index 1fb77db..fc2c0a2 100644 --- a/src/widget.zig +++ b/src/widget.zig @@ -91,11 +91,13 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type { } // import and export of `Widget` implementations + pub const Text = @import("widget/Text.zig").Widget(Event, Renderer); pub const RawText = @import("widget/RawText.zig").Widget(Event, Renderer); pub const Spacer = @import("widget/Spacer.zig").Widget(Event, Renderer); }; // test widget implementation satisfies the interface comptime Type.Interface.satisfiedBy(Type); + comptime Type.Interface.satisfiedBy(Type.Text); comptime Type.Interface.satisfiedBy(Type.RawText); comptime Type.Interface.satisfiedBy(Type.Spacer); return Type; diff --git a/src/widget/Text.zig b/src/widget/Text.zig new file mode 100644 index 0000000..d262ce2 --- /dev/null +++ b/src/widget/Text.zig @@ -0,0 +1,50 @@ +const std = @import("std"); +const terminal = @import("../terminal.zig"); + +const isTaggedUnion = @import("../event.zig").isTaggedUnion; +const Error = @import("../event.zig").Error; +const Cell = terminal.Cell; + +const log = std.log.scoped(.widget_text); + +pub fn Widget(comptime Event: type, comptime Renderer: type) type { + if (!isTaggedUnion(Event)) { + @compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`."); + } + return struct { + contents: []const Cell = undefined, + size: terminal.Size = undefined, + require_render: bool = false, + + pub fn init(contents: []const Cell) @This() { + return .{ + .contents = contents, + }; + } + + pub fn deinit(this: *@This()) void { + this.* = undefined; + } + + pub fn handle(this: *@This(), event: Event) ?Event { + switch (event) { + // store the received size + .resize => |size| { + this.size = size; + this.require_render = true; + }, + else => {}, + } + return null; + } + + pub fn render(this: *@This(), renderer: *Renderer) !void { + if (!this.require_render) { + return; + } + try renderer.clear(this.size); + try renderer.render(this.size, this.contents); + this.require_render = false; + } + }; +}