From 2ef59ca9ea1c386fce1cac2bd0bad3e9f9f0ca61 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Fri, 27 Dec 2024 11:34:51 +0100 Subject: [PATCH] add(widget/Input): widget for user inputs Accepts optional label and placeholders which are rendered accordingly. The widget only is interactable if it is active. Hence it needs further control from the outside scope where it is used (likely a corresponding layout). This also applies to the inputed value, which needs to be received from the within the owning layout to be used for further processing (i.e. in custom user events, etc.). --- src/app.zig | 1 - src/widget.zig | 2 + src/widget/Input.zig | 242 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 src/widget/Input.zig diff --git a/src/app.zig b/src/app.zig index 98f47b7..da9110a 100644 --- a/src/app.zig +++ b/src/app.zig @@ -296,7 +296,6 @@ pub fn App(comptime E: type, comptime R: fn (comptime bool) type, comptime fulls }; this.postEvent(.{ .key = key }); }, - 'I' => this.postEvent(.{ .focus = true }), 'O' => this.postEvent(.{ .focus = false }), // 'M', 'm' => return parseMouse(sequence), // TODO: parse mouse inputs diff --git a/src/widget.zig b/src/widget.zig index 7534220..8a95cd7 100644 --- a/src/widget.zig +++ b/src/widget.zig @@ -91,6 +91,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type { // TODO: implement a minimal size requirement for Widgets to render correctly? // import and export of `Widget` implementations + pub const Input = @import("widget/Input.zig").Widget(Event, Renderer); 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); @@ -98,6 +99,7 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type { }; // test widget implementation satisfies the interface comptime Type.Interface.satisfiedBy(Type); + comptime Type.Interface.satisfiedBy(Type.Input); comptime Type.Interface.satisfiedBy(Type.Text); comptime Type.Interface.satisfiedBy(Type.RawText); comptime Type.Interface.satisfiedBy(Type.Spacer); diff --git a/src/widget/Input.zig b/src/widget/Input.zig new file mode 100644 index 0000000..82be21d --- /dev/null +++ b/src/widget/Input.zig @@ -0,0 +1,242 @@ +const std = @import("std"); +const terminal = @import("../terminal.zig"); + +const isTaggedUnion = @import("../event.zig").isTaggedUnion; +const Cell = terminal.Cell; +const Key = terminal.Key; +const Size = terminal.Size; + +const log = std.log.scoped(.widget_input); + +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 { + active: bool = false, + allocator: std.mem.Allocator = undefined, + label: ?[]const u8 = null, + placeholder: ?[]const u8 = null, + size: Size = undefined, + require_render: bool = false, + value: std.ArrayList(u8) = undefined, + value_len: usize = 0, // value content length + cursor_idx: usize = 0, // current cursor position + + pub fn init(allocator: std.mem.Allocator, label: ?[]const u8, placeholder: ?[]const u8) @This() { + var value = std.ArrayList(u8).init(allocator); + value.resize(32) catch @panic("Input.zig: out of memory"); + return .{ + .allocator = allocator, + .value = value, + .label = label, + .placeholder = placeholder, + }; + } + + pub fn deinit(this: *@This()) void { + this.value.deinit(); + this.* = undefined; + } + + pub fn getValue(this: *const @This()) []const u8 { + return this.value.items[0..this.value_len]; + } + + pub fn handle(this: *@This(), event: Event) ?Event { + switch (event) { + .resize => |size| { + this.size = size; + this.require_render = true; + + var required_cols: u16 = 4; // '...c' + if (this.label) |label| { + required_cols += @as(u16, @truncate(label.len)); //