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(.{
|
||||
.name = "vaxis",
|
||||
.root_source_file = .{ .path = "examples/main.zig" },
|
||||
.root_source_file = .{ .path = "examples/text_input.zig" },
|
||||
.target = target,
|
||||
.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.
|
||||
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,
|
||||
|
||||
/// 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 Winsize = @import("Tty.zig").Winsize;
|
||||
|
||||
pub const widgets = @import("widgets/main.zig");
|
||||
|
||||
/// Initialize a Vaxis application.
|
||||
pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) {
|
||||
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