feat(example/input): readline shortcuts; better navigation;
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 58s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 58s
This will be the basis for a Textfield `Element` implementation to make it easier to add user inputs into the container structures.
This commit is contained in:
@@ -24,10 +24,18 @@ const QuitText = struct {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO create an own `Element` implementation from this
|
||||
const InputField = struct {
|
||||
/// Offset from the end describing the current position of the cursor.
|
||||
cursor_offset: usize = 0,
|
||||
/// Array holding the value of the input.
|
||||
input: std.ArrayList(u21),
|
||||
/// Reference to the app's queue to issue the associated event to trigger when completing the input.
|
||||
queue: *App.Queue,
|
||||
|
||||
// TODO make the event to trigger user defined (needs to be `comptime`)
|
||||
// - can this even be agnostic to `u8` / `u21`?
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, queue: *App.Queue) @This() {
|
||||
return .{
|
||||
.input = .init(allocator),
|
||||
@@ -53,13 +61,68 @@ const InputField = struct {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
.key => |key| {
|
||||
if (key.isAscii()) try this.input.append(key.cp);
|
||||
assert(this.cursor_offset >= 0 and this.cursor_offset <= this.input.items.len);
|
||||
|
||||
if (key.eql(.{ .cp = zterm.input.Enter }) or key.eql(.{ .cp = zterm.input.KpEnter }))
|
||||
// see *form* implementation in `zk`, which might become part of the library
|
||||
if (key.eql(.{ .cp = zterm.input.Left }) or key.eql(.{ .cp = zterm.input.KpLeft }) or key.eql(.{ .cp = 'b', .mod = .{ .ctrl = true } })) {
|
||||
if (this.cursor_offset < this.input.items.len) this.cursor_offset += 1;
|
||||
}
|
||||
|
||||
if (key.eql(.{ .cp = zterm.input.Right }) or key.eql(.{ .cp = zterm.input.KpRight }) or key.eql(.{ .cp = 'f', .mod = .{ .ctrl = true } }))
|
||||
this.cursor_offset -|= 1;
|
||||
|
||||
if (key.eql(.{ .cp = 'e', .mod = .{ .ctrl = true } })) this.cursor_offset = 0;
|
||||
|
||||
if (key.eql(.{ .cp = 'a', .mod = .{ .ctrl = true } })) this.cursor_offset = this.input.items.len;
|
||||
|
||||
if (key.eql(.{ .cp = zterm.input.Backspace })) {
|
||||
if (this.cursor_offset < this.input.items.len) {
|
||||
_ = if (this.cursor_offset == 0) this.input.pop() else this.input.orderedRemove(this.input.items.len - this.cursor_offset - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (key.eql(.{ .cp = zterm.input.Delete }) or key.eql(.{ .cp = zterm.input.KpDelete })) {
|
||||
if (this.cursor_offset > 0) {
|
||||
_ = this.input.orderedRemove(this.input.items.len - this.cursor_offset);
|
||||
this.cursor_offset -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO support readline keybindings (i.e. alt bindings alt-b, alt-f
|
||||
// TODO maybe even ctrl-left, ctrl-right?)
|
||||
// readline commands
|
||||
if (key.eql(.{ .cp = 'k', .mod = .{ .ctrl = true } })) {
|
||||
while (this.cursor_offset > 0) {
|
||||
_ = this.input.pop();
|
||||
this.cursor_offset -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (key.eql(.{ .cp = 'u', .mod = .{ .ctrl = true } })) {
|
||||
const len = this.input.items.len - this.cursor_offset;
|
||||
for (0..len) |_| _ = this.input.orderedRemove(0);
|
||||
this.cursor_offset = this.input.items.len;
|
||||
}
|
||||
|
||||
if (key.eql(.{ .cp = 'w', .mod = .{ .ctrl = true } })) {
|
||||
var non_whitespace = false;
|
||||
while (this.cursor_offset < this.input.items.len) {
|
||||
if (non_whitespace and this.input.items[this.input.items.len - this.cursor_offset - 1] == ' ') break;
|
||||
|
||||
// see backspace
|
||||
const removed = if (this.cursor_offset == 0) this.input.pop() else this.input.orderedRemove(this.input.items.len - this.cursor_offset - 1);
|
||||
if (removed != ' ') non_whitespace = true;
|
||||
}
|
||||
}
|
||||
|
||||
// usual input keys
|
||||
if (key.isAscii()) try this.input.insert(this.input.items.len - this.cursor_offset, key.cp);
|
||||
|
||||
// TODO enter to accept?
|
||||
if (key.eql(.{ .cp = zterm.input.Enter }) or key.eql(.{ .cp = zterm.input.KpEnter })) {
|
||||
this.queue.push(.{ .accept = try this.input.toOwnedSlice() });
|
||||
|
||||
if (key.eql(.{ .cp = zterm.input.Backspace }) or key.eql(.{ .cp = zterm.input.Delete }) or key.eql(.{ .cp = zterm.input.KpDelete }))
|
||||
_ = this.input.pop();
|
||||
this.cursor_offset = 0;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -69,22 +132,17 @@ const InputField = struct {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
|
||||
if (this.input.items.len == 0) return;
|
||||
|
||||
const row = 1;
|
||||
const col = 1;
|
||||
const anchor = (row * size.x) + col;
|
||||
|
||||
// TODO add configuration for coloring the text!
|
||||
for (this.input.items, 0..) |cp, idx| {
|
||||
cells[anchor + idx].style.fg = .black;
|
||||
cells[anchor + idx].style.cursor = false;
|
||||
cells[anchor + idx].cp = cp;
|
||||
cells[idx].style.fg = .black;
|
||||
cells[idx].cp = cp;
|
||||
|
||||
// NOTE do not write over the contents of this `Container`'s `Size`
|
||||
if (anchor + idx == cells.len - 1) break;
|
||||
|
||||
if (idx == this.input.items.len - 1) cells[anchor + idx + 1].style.cursor = true;
|
||||
if (idx == cells.len - 1) break;
|
||||
}
|
||||
// TODO show ellipse `..` (maybe with configuration where - start, middle, end)
|
||||
// show cursor after text (if there is still space available)
|
||||
if (this.input.items.len < cells.len) cells[this.input.items.len - this.cursor_offset].style.cursor = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user