|
|
|
|
@@ -28,16 +28,24 @@ const QuitText = struct {
|
|
|
|
|
const InputField = struct {
|
|
|
|
|
/// Offset from the end describing the current position of the cursor.
|
|
|
|
|
cursor_offset: usize = 0,
|
|
|
|
|
/// Configuration for the InputField.
|
|
|
|
|
configuration: Configuration,
|
|
|
|
|
/// 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,
|
|
|
|
|
|
|
|
|
|
/// Configuration for InputField's.
|
|
|
|
|
pub const Configuration = struct {
|
|
|
|
|
color: Color = .default,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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() {
|
|
|
|
|
pub fn init(allocator: std.mem.Allocator, queue: *App.Queue, configuration: Configuration) @This() {
|
|
|
|
|
return .{
|
|
|
|
|
.configuration = configuration,
|
|
|
|
|
.input = .init(allocator),
|
|
|
|
|
.queue = queue,
|
|
|
|
|
};
|
|
|
|
|
@@ -63,7 +71,7 @@ const InputField = struct {
|
|
|
|
|
.key => |key| {
|
|
|
|
|
assert(this.cursor_offset >= 0 and this.cursor_offset <= this.input.items.len);
|
|
|
|
|
|
|
|
|
|
// see *form* implementation in `zk`, which might become part of the library
|
|
|
|
|
// readline commands
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
@@ -75,22 +83,6 @@ const InputField = struct {
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
@@ -115,10 +107,52 @@ const InputField = struct {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key.eql(.{ .cp = 'b', .mod = .{ .alt = true } }) or key.eql(.{ .cp = zterm.input.Left, .mod = .{ .ctrl = true } }) or key.eql(.{ .cp = zterm.input.KpLeft, .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
|
|
|
|
|
this.cursor_offset += 1;
|
|
|
|
|
if (this.cursor_offset == this.input.items.len) break;
|
|
|
|
|
const next = this.input.items[this.input.items.len - this.cursor_offset - 1];
|
|
|
|
|
if (next != ' ') non_whitespace = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key.eql(.{ .cp = 'f', .mod = .{ .alt = true } }) or key.eql(.{ .cp = zterm.input.Right, .mod = .{ .ctrl = true } }) or key.eql(.{ .cp = zterm.input.KpRight, .mod = .{ .ctrl = true } })) {
|
|
|
|
|
var non_whitespace = false;
|
|
|
|
|
while (this.cursor_offset > 0) {
|
|
|
|
|
if (non_whitespace and this.input.items[this.input.items.len - this.cursor_offset - 1] == ' ') {
|
|
|
|
|
this.cursor_offset += 1; // correct cursor position back again to make sure the cursor is not on the whitespace, but at the end of the jumped word
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// see backspace
|
|
|
|
|
this.cursor_offset -= 1;
|
|
|
|
|
const next = this.input.items[this.input.items.len - this.cursor_offset - 1];
|
|
|
|
|
if (next != ' ') non_whitespace = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// usual input keys
|
|
|
|
|
if (key.isAscii()) try this.input.insert(this.input.items.len - this.cursor_offset, key.cp);
|
|
|
|
|
|
|
|
|
|
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 enter to accept?
|
|
|
|
|
// - shift+enter is not recognized by the input reader of `zterm`, so currently it is not possible to add newlines into the text box?
|
|
|
|
|
if (key.eql(.{ .cp = zterm.input.Enter }) or key.eql(.{ .cp = zterm.input.KpEnter })) {
|
|
|
|
|
this.queue.push(.{ .accept = try this.input.toOwnedSlice() });
|
|
|
|
|
this.cursor_offset = 0;
|
|
|
|
|
@@ -132,17 +166,24 @@ const InputField = struct {
|
|
|
|
|
const this: *@This() = @ptrCast(@alignCast(ctx));
|
|
|
|
|
assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
|
|
|
|
|
|
|
|
|
// TODO add configuration for coloring the text!
|
|
|
|
|
for (this.input.items, 0..) |cp, idx| {
|
|
|
|
|
cells[idx].style.fg = .black;
|
|
|
|
|
const offset = if (this.input.items.len - this.cursor_offset + 1 >= cells.len) this.input.items.len + 1 - cells.len else 0;
|
|
|
|
|
for (this.input.items[offset..], 0..) |cp, idx| {
|
|
|
|
|
cells[idx].style.fg = this.configuration.color;
|
|
|
|
|
cells[idx].cp = cp;
|
|
|
|
|
// display ellipse at the beginning
|
|
|
|
|
if (offset > 0 and idx == 0) cells[idx].cp = '…';
|
|
|
|
|
|
|
|
|
|
// NOTE do not write over the contents of this `Container`'s `Size`
|
|
|
|
|
if (idx == cells.len - 1) break;
|
|
|
|
|
if (idx == cells.len - 1) {
|
|
|
|
|
// display ellipse at the end
|
|
|
|
|
if (this.input.items.len >= cells.len and this.cursor_offset > 0) cells[idx].cp = '…';
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (this.input.items.len < cells.len)
|
|
|
|
|
cells[this.input.items.len - this.cursor_offset].style.cursor = true
|
|
|
|
|
else
|
|
|
|
|
cells[this.input.items.len - offset - this.cursor_offset].style.cursor = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -191,7 +232,9 @@ pub fn main() !void {
|
|
|
|
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
|
|
|
|
defer renderer.deinit();
|
|
|
|
|
|
|
|
|
|
var input_field: InputField = .init(allocator, &app.queue);
|
|
|
|
|
var input_field: InputField = .init(allocator, &app.queue, .{
|
|
|
|
|
.color = .black,
|
|
|
|
|
});
|
|
|
|
|
defer input_field.deinit();
|
|
|
|
|
|
|
|
|
|
var mouse_draw: MouseDraw = .{};
|
|
|
|
|
@@ -211,7 +254,7 @@ pub fn main() !void {
|
|
|
|
|
.rectangle = .{ .fill = .light_grey },
|
|
|
|
|
.size = .{
|
|
|
|
|
.grow = .horizontal,
|
|
|
|
|
.dim = .{ .y = 10 },
|
|
|
|
|
.dim = .{ .y = 1 },
|
|
|
|
|
},
|
|
|
|
|
}, input_field.element()));
|
|
|
|
|
|
|
|
|
|
@@ -284,6 +327,8 @@ const log = std.log.scoped(.default);
|
|
|
|
|
const std = @import("std");
|
|
|
|
|
const assert = std.debug.assert;
|
|
|
|
|
const zterm = @import("zterm");
|
|
|
|
|
const Color = zterm.Color;
|
|
|
|
|
|
|
|
|
|
const App = zterm.App(union(enum) {
|
|
|
|
|
accept: []u21,
|
|
|
|
|
});
|
|
|
|
|
|