widgets: create an initial text_input and border widget
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
558b64544c
commit
5148d20f52
7 changed files with 170 additions and 2 deletions
|
@ -14,7 +14,7 @@ pub fn build(b: *std.Build) void {
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "vaxis",
|
.name = "vaxis",
|
||||||
.root_source_file = .{ .path = "examples/main.zig" },
|
.root_source_file = .{ .path = "examples/text_input.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
79
examples/text_input.zig
Normal file
79
examples/text_input.zig
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const vaxis = @import("vaxis");
|
||||||
|
const Cell = vaxis.Cell;
|
||||||
|
const TextInput = vaxis.widgets.TextInput;
|
||||||
|
const border = vaxis.widgets.border;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.main);
|
||||||
|
pub fn main() !void {
|
||||||
|
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 alloc = gpa.allocator();
|
||||||
|
|
||||||
|
// Initialize Vaxis
|
||||||
|
var vx = try vaxis.init(Event, .{});
|
||||||
|
defer vx.deinit(alloc);
|
||||||
|
|
||||||
|
// Start the read loop. This puts the terminal in raw mode and begins
|
||||||
|
// reading user input
|
||||||
|
try vx.start();
|
||||||
|
defer vx.stop();
|
||||||
|
|
||||||
|
// Optionally enter the alternate screen
|
||||||
|
try vx.enterAltScreen();
|
||||||
|
|
||||||
|
var text_input: TextInput = .{};
|
||||||
|
|
||||||
|
// The main event loop. Vaxis provides a thread safe, blocking, buffered
|
||||||
|
// queue which can serve as the primary event queue for an application
|
||||||
|
outer: while (true) {
|
||||||
|
// nextEvent blocks until an event is in the queue
|
||||||
|
const event = vx.nextEvent();
|
||||||
|
log.debug("event: {}\r\n", .{event});
|
||||||
|
// exhaustive switching ftw. Vaxis will send events if your EventType
|
||||||
|
// enum has the fields for those events (ie "key_press", "winsize")
|
||||||
|
switch (event) {
|
||||||
|
.key_press => |key| {
|
||||||
|
text_input.update(.{ .key_press = key });
|
||||||
|
if (key.codepoint == 'c' and key.mods.ctrl) {
|
||||||
|
break :outer;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.winsize => |ws| {
|
||||||
|
try vx.resize(alloc, ws);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// vx.window() returns the root window. This window is the size of the
|
||||||
|
// terminal and can spawn child windows as logical areas. Child windows
|
||||||
|
// cannot draw outside of their bounds
|
||||||
|
const win = vx.window();
|
||||||
|
// Clear the entire space because we are drawing in immediate mode.
|
||||||
|
// vaxis double buffers the screen. This new frame will be compared to
|
||||||
|
// the old and only updated cells will be drawn
|
||||||
|
win.clear();
|
||||||
|
const child = win.initChild(win.width / 2 - 20, win.height / 2 - 3, .{ .limit = 40 }, .{ .limit = 3 });
|
||||||
|
// draw the text_input using a bordered window
|
||||||
|
text_input.draw(border.all(child, .{}));
|
||||||
|
|
||||||
|
// Render the screen
|
||||||
|
try vx.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our EventType. This can contain internal events as well as Vaxis events.
|
||||||
|
// Internal events can be posted into the same queue as vaxis events to allow
|
||||||
|
// for a single event loop with exhaustive switching. Booya
|
||||||
|
const Event = union(enum) {
|
||||||
|
key_press: vaxis.Key,
|
||||||
|
winsize: vaxis.Winsize,
|
||||||
|
focus_in,
|
||||||
|
foo: u8,
|
||||||
|
};
|
|
@ -14,7 +14,9 @@ pub const Modifiers = packed struct(u8) {
|
||||||
/// the unicode codepoint of the key event.
|
/// the unicode codepoint of the key event.
|
||||||
codepoint: u21,
|
codepoint: u21,
|
||||||
|
|
||||||
/// the text generated from the key event, if any
|
/// the text generated from the key event. This will only contain a value if the
|
||||||
|
/// event generated a multi-codepoint grapheme. If there was only a single
|
||||||
|
/// codepoint, library users can encode the codepoint directly
|
||||||
text: ?[]const u8 = null,
|
text: ?[]const u8 = null,
|
||||||
|
|
||||||
/// the shifted codepoint of this key event. This will only be present if the
|
/// the shifted codepoint of this key event. This will only be present if the
|
||||||
|
|
|
@ -7,6 +7,8 @@ pub const Cell = cell.Cell;
|
||||||
pub const Key = @import("Key.zig");
|
pub const Key = @import("Key.zig");
|
||||||
pub const Winsize = @import("Tty.zig").Winsize;
|
pub const Winsize = @import("Tty.zig").Winsize;
|
||||||
|
|
||||||
|
pub const widgets = @import("widgets/main.zig");
|
||||||
|
|
||||||
/// Initialize a Vaxis application.
|
/// Initialize a Vaxis application.
|
||||||
pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
||||||
return Vaxis(EventType).init(opts);
|
return Vaxis(EventType).init(opts);
|
||||||
|
|
52
src/widgets/TextInput.zig
Normal file
52
src/widgets/TextInput.zig
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Cell = @import("../cell.zig").Cell;
|
||||||
|
const Key = @import("../Key.zig");
|
||||||
|
const Window = @import("../Window.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.text_input);
|
||||||
|
|
||||||
|
const TextInput = @This();
|
||||||
|
|
||||||
|
/// The events that this widget handles
|
||||||
|
const Event = union(enum) {
|
||||||
|
key_press: Key,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Index of our cursor
|
||||||
|
cursor_idx: usize = 0,
|
||||||
|
|
||||||
|
// the actual line of input
|
||||||
|
buffer: [4096]u8 = undefined,
|
||||||
|
buffer_idx: usize = 0,
|
||||||
|
|
||||||
|
pub fn update(self: *TextInput, event: Event) void {
|
||||||
|
switch (event) {
|
||||||
|
.key_press => |key| {
|
||||||
|
switch (key.codepoint) {
|
||||||
|
0x20...0x7E => {
|
||||||
|
self.buffer[self.buffer_idx] = @truncate(key.codepoint);
|
||||||
|
self.buffer_idx += 1;
|
||||||
|
self.cursor_idx += 1;
|
||||||
|
},
|
||||||
|
Key.backspace => {
|
||||||
|
// TODO: this only works at the end of the array. Then
|
||||||
|
// again, we don't have any means to move the cursor yet
|
||||||
|
if (self.buffer_idx == 0) return;
|
||||||
|
self.buffer_idx -= 1;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *TextInput, win: Window) void {
|
||||||
|
for (0.., self.buffer[0..self.buffer_idx]) |i, b| {
|
||||||
|
win.writeCell(i, 0, .{
|
||||||
|
.char = .{
|
||||||
|
.grapheme = &[_]u8{b},
|
||||||
|
.width = 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
31
src/widgets/border.zig
Normal file
31
src/widgets/border.zig
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const Window = @import("../Window.zig");
|
||||||
|
const cell = @import("../cell.zig");
|
||||||
|
const Character = cell.Character;
|
||||||
|
const Style = cell.Style;
|
||||||
|
|
||||||
|
const horizontal = Character{ .grapheme = "─", .width = 1 };
|
||||||
|
const vertical = Character{ .grapheme = "│", .width = 1 };
|
||||||
|
const top_left = Character{ .grapheme = "╭", .width = 1 };
|
||||||
|
const top_right = Character{ .grapheme = "╮", .width = 1 };
|
||||||
|
const bottom_right = Character{ .grapheme = "╯", .width = 1 };
|
||||||
|
const bottom_left = Character{ .grapheme = "╰", .width = 1 };
|
||||||
|
|
||||||
|
pub fn all(win: Window, style: Style) Window {
|
||||||
|
const h = win.height;
|
||||||
|
const w = win.width;
|
||||||
|
win.writeCell(0, 0, .{ .char = top_left, .style = style });
|
||||||
|
win.writeCell(0, h - 1, .{ .char = bottom_left, .style = style });
|
||||||
|
win.writeCell(w - 1, 0, .{ .char = top_right, .style = style });
|
||||||
|
win.writeCell(w - 1, h - 1, .{ .char = bottom_right, .style = style });
|
||||||
|
var i: usize = 1;
|
||||||
|
while (i < (h - 1)) : (i += 1) {
|
||||||
|
win.writeCell(0, i, .{ .char = vertical, .style = style });
|
||||||
|
win.writeCell(w - 1, i, .{ .char = vertical, .style = style });
|
||||||
|
}
|
||||||
|
i = 1;
|
||||||
|
while (i < w - 1) : (i += 1) {
|
||||||
|
win.writeCell(i, 0, .{ .char = horizontal, .style = style });
|
||||||
|
win.writeCell(i, h - 1, .{ .char = horizontal, .style = style });
|
||||||
|
}
|
||||||
|
return win.initChild(1, 1, .{ .limit = w - 2 }, .{ .limit = w - 2 });
|
||||||
|
}
|
2
src/widgets/main.zig
Normal file
2
src/widgets/main.zig
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub const TextInput = @import("TextInput.zig");
|
||||||
|
pub const border = @import("border.zig");
|
Loading…
Reference in a new issue