Replace vaxis with zterm #1
90
src/app.zig
Normal file
90
src/app.zig
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
//! Application type for TUI-applications
|
||||||
|
const std = @import("std");
|
||||||
|
const terminal = @import("terminal.zig");
|
||||||
|
const mergeTaggedUnions = @import("event.zig").mergeTaggedUnions;
|
||||||
|
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||||
|
|
||||||
|
const BuiltinEvent = @import("event.zig").BuiltinEvent;
|
||||||
|
const Queue = @import("queue.zig").Queue;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.app);
|
||||||
|
|
||||||
|
// Create the App Type with the associated user events `E` which describes
|
||||||
|
// an tagged union for all the user events that can be send through the
|
||||||
|
// applications event loop.
|
||||||
|
pub fn App(comptime E: type) type {
|
||||||
|
if (!isTaggedUnion(E)) {
|
||||||
|
@compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`.");
|
||||||
|
}
|
||||||
|
return struct {
|
||||||
|
pub const Event = mergeTaggedUnions(BuiltinEvent, E);
|
||||||
|
pub const Layout = @import("layout.zig").Layout(Event);
|
||||||
|
pub const Widget = @import("widget.zig").Widget(Event);
|
||||||
|
|
||||||
|
queue: Queue(Event, 256) = .{},
|
||||||
|
thread: ?std.Thread = null,
|
||||||
|
quit: bool = false,
|
||||||
|
termios: ?std.posix.termios = null,
|
||||||
|
|
||||||
|
// TODO: event loop function?
|
||||||
|
// layout handling?
|
||||||
|
|
||||||
|
pub fn init() @This() {
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(this: *@This()) !void {
|
||||||
|
if (this.thread) |_| return;
|
||||||
|
|
||||||
|
this.thread = try std.Thread.spawn(.{}, @This().run, .{this});
|
||||||
|
var termios: std.posix.termios = undefined;
|
||||||
|
try terminal.enableRawMode(&termios);
|
||||||
|
this.termios = termios;
|
||||||
|
try terminal.saveScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(this: *@This()) !void {
|
||||||
|
if (this.termios) |*termios| {
|
||||||
|
try terminal.disableRawMode(termios);
|
||||||
|
try terminal.restoreScreen();
|
||||||
|
}
|
||||||
|
this.quit = true;
|
||||||
|
if (this.thread) |thread| {
|
||||||
|
thread.join();
|
||||||
|
this.thread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next available event, blocking until one is available.
|
||||||
|
pub fn nextEvent(this: *@This()) Event {
|
||||||
|
return this.queue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post an event into the queue. Blocks if there is no capacity for the event.
|
||||||
|
pub fn postEvent(this: *@This(), event: Event) void {
|
||||||
|
this.queue.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(this: *@This()) !void {
|
||||||
|
// thread to read user inputs
|
||||||
|
const size = terminal.getTerminalSize();
|
||||||
|
this.postEvent(.{ .resize = size });
|
||||||
|
// read input in loop
|
||||||
|
const buf: [256]u8 = undefined;
|
||||||
|
_ = buf;
|
||||||
|
while (!this.quit) {
|
||||||
|
std.time.sleep(5 * std.time.ns_per_s);
|
||||||
|
break;
|
||||||
|
// try terminal.read(buf[0..]);
|
||||||
|
// TODO: send corresponding events with key_presses
|
||||||
|
// -> create corresponding event
|
||||||
|
// -> handle key inputs (modifier, op codes, etc.)
|
||||||
|
// -> I could take inspiration from `libvaxis` for this
|
||||||
|
}
|
||||||
|
// FIXME: here is a race-condition -> i.e. there could be events in
|
||||||
|
// the queue, but they will not be executed because the main loop
|
||||||
|
// will close!
|
||||||
|
this.postEvent(.quit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Events which are defined by the library. They might be extended by user
|
//! Events which are defined by the library. They might be extended by user
|
||||||
//! events.
|
//! events. See `App` for more details about user defined events.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const terminal = @import("terminal.zig");
|
const terminal = @import("terminal.zig");
|
||||||
|
|
||||||
@@ -8,21 +8,23 @@ const terminal = @import("terminal.zig");
|
|||||||
// message, while `err` represents an error which is propagated. `Widget`s or
|
// message, while `err` represents an error which is propagated. `Widget`s or
|
||||||
// `Layout`s may react to the event but should continue throwing the message up
|
// `Layout`s may react to the event but should continue throwing the message up
|
||||||
// to the application event loop.
|
// to the application event loop.
|
||||||
pub const ApplicationEvent = union(enum) {
|
const ApplicationEvent = union(enum) {
|
||||||
none,
|
none,
|
||||||
|
quit,
|
||||||
err: []const u8,
|
err: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
// System events which contain information about events triggered from outside
|
// System events which contain information about events triggered from outside
|
||||||
// of the application which impact the application. E.g. the terminal window
|
// of the application which impact the application. E.g. the terminal window
|
||||||
// size has changed, etc.
|
// size has changed, etc.
|
||||||
pub const SystemEvent = union(enum) {
|
const SystemEvent = union(enum) {
|
||||||
resize: terminal.Size,
|
resize: terminal.Size,
|
||||||
|
// key_press: terminal.Key,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const BuiltinEvent = MergeTaggedUnions(SystemEvent, ApplicationEvent);
|
pub const BuiltinEvent = mergeTaggedUnions(SystemEvent, ApplicationEvent);
|
||||||
|
|
||||||
pub fn MergeTaggedUnions(comptime A: type, comptime B: type) type {
|
pub fn mergeTaggedUnions(comptime A: type, comptime B: type) type {
|
||||||
if (!isTaggedUnion(A) or !isTaggedUnion(B)) {
|
if (!isTaggedUnion(A) or !isTaggedUnion(B)) {
|
||||||
@compileError("Both types for merging tagged unions need to be of type `union(enum)`.");
|
@compileError("Both types for merging tagged unions need to be of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Dynamic dispatch for layout implementations.
|
//! Dynamic dispatch for layout implementations.
|
||||||
//! Each layout should at last implement these functions:
|
//! Each layout should at last implement these functions:
|
||||||
//! - handle(this: *@This(), event: Event) Event {}
|
//! - handle(this: *@This(), event: Event) anyerror!*std.ArrayList(Event) {}
|
||||||
//! - content(this: *@This()) *std.ArrayList(u8) {}
|
//! - content(this: *@This()) anyerror!*std.ArrayList(u8) {}
|
||||||
//! - deinit(this: *@This()) void {}
|
//! - deinit(this: *@This()) void {}
|
||||||
//!
|
//!
|
||||||
//! Create a `Layout` using `createFrom(object: anytype)` and use them through
|
//! Create a `Layout` using `createFrom(object: anytype)` and use them through
|
||||||
@@ -14,18 +14,17 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lib_event = @import("event.zig");
|
const lib_event = @import("event.zig");
|
||||||
|
|
||||||
pub fn Layout(comptime E: type) type {
|
pub fn Layout(comptime Event: type) type {
|
||||||
if (!lib_event.isTaggedUnion(E)) {
|
if (!lib_event.isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `E` for `Layout(comptime E: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
pub const Event = lib_event.MergeTaggedUnions(lib_event.BuiltinEvent, E);
|
|
||||||
const LayoutType = @This();
|
const LayoutType = @This();
|
||||||
const Ptr = usize;
|
const Ptr = usize;
|
||||||
|
|
||||||
const VTable = struct {
|
const VTable = struct {
|
||||||
handle: *const fn (this: *LayoutType, event: Event) Event,
|
handle: *const fn (this: *LayoutType, event: Event) anyerror!*std.ArrayList(Event),
|
||||||
content: *const fn (this: *LayoutType) *std.ArrayList(u8),
|
content: *const fn (this: *LayoutType) anyerror!*std.ArrayList(u8),
|
||||||
deinit: *const fn (this: *LayoutType) void,
|
deinit: *const fn (this: *LayoutType) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,13 +32,13 @@ pub fn Layout(comptime E: type) type {
|
|||||||
vtable: *const VTable = undefined,
|
vtable: *const VTable = undefined,
|
||||||
|
|
||||||
// Handle the provided `Event` for this `Widget`.
|
// Handle the provided `Event` for this `Widget`.
|
||||||
pub fn handle(this: *LayoutType, event: Event) Event {
|
pub fn handle(this: *LayoutType, event: Event) !*std.ArrayList(Event) {
|
||||||
return this.vtable.handle(this, event);
|
return try this.vtable.handle(this, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the entire content of this `Widget`.
|
// Return the entire content of this `Widget`.
|
||||||
pub fn content(this: *LayoutType) *std.ArrayList(u8) {
|
pub fn content(this: *LayoutType) !*std.ArrayList(u8) {
|
||||||
return this.vtable.content(this);
|
return try this.vtable.content(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *LayoutType) void {
|
pub fn deinit(this: *LayoutType) void {
|
||||||
@@ -53,16 +52,16 @@ pub fn Layout(comptime E: type) type {
|
|||||||
.vtable = &.{
|
.vtable = &.{
|
||||||
.handle = struct {
|
.handle = struct {
|
||||||
// Handle the provided `Event` for this `Widget`.
|
// Handle the provided `Event` for this `Widget`.
|
||||||
fn handle(this: *LayoutType, event: Event) Event {
|
fn handle(this: *LayoutType, event: Event) !*std.ArrayList(Event) {
|
||||||
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
||||||
return layout.handle(event);
|
return try layout.handle(event);
|
||||||
}
|
}
|
||||||
}.handle,
|
}.handle,
|
||||||
.content = struct {
|
.content = struct {
|
||||||
// Return the entire content of this `Widget`.
|
// Return the entire content of this `Widget`.
|
||||||
fn content(this: *LayoutType) *std.ArrayList(u8) {
|
fn content(this: *LayoutType) !*std.ArrayList(u8) {
|
||||||
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
const layout: @TypeOf(object) = @ptrFromInt(this.object);
|
||||||
return layout.content();
|
return try layout.content();
|
||||||
}
|
}
|
||||||
}.content,
|
}.content,
|
||||||
.deinit = struct {
|
.deinit = struct {
|
||||||
@@ -76,6 +75,6 @@ pub fn Layout(comptime E: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// import and export of `Layout` implementations
|
// import and export of `Layout` implementations
|
||||||
pub const Pane = @import("layout/Pane.zig").Layout(E);
|
pub const Pane = @import("layout/Pane.zig").Layout(Event);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,42 @@ const std = @import("std");
|
|||||||
const lib_event = @import("../event.zig");
|
const lib_event = @import("../event.zig");
|
||||||
const widget = @import("../widget.zig");
|
const widget = @import("../widget.zig");
|
||||||
|
|
||||||
pub fn Layout(comptime E: type) type {
|
pub fn Layout(comptime Event: type) type {
|
||||||
if (!lib_event.isTaggedUnion(E)) {
|
if (!lib_event.isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `E` for `Layout(comptime E: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
pub const Event = lib_event.MergeTaggedUnions(lib_event.BuiltinEvent, E);
|
w: widget.Widget(Event) = undefined,
|
||||||
|
events: std.ArrayList(Event) = undefined,
|
||||||
w: widget.Widget(E) = undefined,
|
|
||||||
c: std.ArrayList(u8) = undefined,
|
c: std.ArrayList(u8) = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, w: widget.Widget(E)) @This() {
|
pub fn init(allocator: std.mem.Allocator, w: widget.Widget(Event)) @This() {
|
||||||
return .{
|
return .{
|
||||||
.w = w,
|
.w = w,
|
||||||
|
.events = std.ArrayList(Event).init(allocator),
|
||||||
.c = std.ArrayList(u8).init(allocator),
|
.c = std.ArrayList(u8).init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
this.w.deinit();
|
this.w.deinit();
|
||||||
|
this.events.deinit();
|
||||||
this.c.deinit();
|
this.c.deinit();
|
||||||
this.* = undefined;
|
this.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(this: *@This(), event: Event) Event {
|
pub fn handle(this: *@This(), event: Event) !*std.ArrayList(Event) {
|
||||||
return this.w.handle(event);
|
this.events.clearRetainingCapacity();
|
||||||
|
if (this.w.handle(event)) |e| {
|
||||||
|
try this.events.append(e);
|
||||||
|
}
|
||||||
|
return &this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) *std.ArrayList(u8) {
|
pub fn content(this: *@This()) !*std.ArrayList(u8) {
|
||||||
const widget_content = this.w.content();
|
const widget_content = try this.w.content();
|
||||||
this.c.clearRetainingCapacity();
|
this.c.clearRetainingCapacity();
|
||||||
this.c.appendSlice(widget_content.items) catch @panic("OOM");
|
try this.c.appendSlice(widget_content.items);
|
||||||
return &this.c;
|
return &this.c;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
59
src/main.zig
59
src/main.zig
@@ -1,18 +1,15 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const terminal = @import("terminal.zig");
|
||||||
const zlog = @import("zlog");
|
const zlog = @import("zlog");
|
||||||
|
|
||||||
const terminal = @import("terminal.zig");
|
const App = @import("app.zig").App(union(enum) {});
|
||||||
|
|
||||||
const UserEvent = union(enum) {};
|
|
||||||
|
|
||||||
const Widget = @import("widget.zig").Widget(UserEvent);
|
|
||||||
const Layout = @import("layout.zig").Layout(UserEvent);
|
|
||||||
|
|
||||||
pub const std_options = zlog.std_options;
|
pub const std_options = zlog.std_options;
|
||||||
const log = std.log.scoped(.main);
|
const log = std.log.scoped(.main);
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
errdefer |err| log.err("Application Error: {any}", .{err});
|
||||||
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer {
|
defer {
|
||||||
const deinit_status = gpa.deinit();
|
const deinit_status = gpa.deinit();
|
||||||
@@ -23,18 +20,46 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
const size = terminal.getTerminalSize();
|
var app = App.init();
|
||||||
|
|
||||||
log.debug("Size := [x: {d}, y: {d}]", .{ size.cols, size.rows });
|
var rawText = App.Widget.RawText.init(allocator);
|
||||||
var rawText = Widget.RawText.init(allocator);
|
const widget = App.Widget.createFrom(&rawText);
|
||||||
const widget = Widget.createFrom(&rawText);
|
var layout = App.Layout.Pane.init(allocator, widget);
|
||||||
var layout = Layout.Pane.init(allocator, widget);
|
|
||||||
defer layout.deinit();
|
defer layout.deinit();
|
||||||
|
|
||||||
// single 'draw' loop
|
try app.start();
|
||||||
_ = layout.handle(.none);
|
defer app.stop() catch unreachable;
|
||||||
log.debug("Layout result: {s}", .{layout.content().items});
|
|
||||||
|
|
||||||
|
// NOTE: necessary for fullscreen tui applications
|
||||||
|
try terminal.enterAltScreen();
|
||||||
|
defer terminal.existAltScreen() catch unreachable;
|
||||||
|
|
||||||
|
// App.Event loop
|
||||||
|
while (true) {
|
||||||
|
const event = app.nextEvent();
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
.none => continue,
|
||||||
|
.quit => break,
|
||||||
|
.resize => |size| {
|
||||||
|
// NOTE: draw actions should not happen here (still here for testing)
|
||||||
|
// NOTE: clearing the screen and positioning the cursor is only necessary for full screen applications
|
||||||
|
// - in-line applications should use relative movements instead and should only clear lines (which they draw)
|
||||||
|
// - in-line applications should not enter the alt screen
|
||||||
|
try terminal.clearScreen();
|
||||||
|
try terminal.setCursorPositionHome();
|
||||||
|
log.debug("Size := [x: {d}, y: {d}]", .{ size.cols, size.rows });
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
const events = try layout.handle(event);
|
||||||
|
for (events.items) |e| {
|
||||||
|
app.postEvent(e);
|
||||||
|
}
|
||||||
|
log.debug("Layout result: {s}", .{(try layout.content()).items});
|
||||||
|
}
|
||||||
|
// TODO: I could use the ascii codes in vaxis
|
||||||
|
// - see https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b
|
||||||
// how would I draw?
|
// how would I draw?
|
||||||
// use array for screen contents? <-> support partial re-draws
|
// use array for screen contents? <-> support partial re-draws
|
||||||
// support widget type drawing similar to the already existing widgets
|
// support widget type drawing similar to the already existing widgets
|
||||||
@@ -43,3 +68,7 @@ pub fn main() !void {
|
|||||||
// - contents of corresponding locations
|
// - contents of corresponding locations
|
||||||
// resize event
|
// resize event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("queue.zig");
|
||||||
|
}
|
||||||
|
|||||||
322
src/queue.zig
Normal file
322
src/queue.zig
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
// taken from https://github.com/rockorager/libvaxis/blob/main/src/queue.zig (MIT-License)
|
||||||
|
// with slight modifications
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
/// Thread safe. Fixed size. Blocking push and pop.
|
||||||
|
pub fn Queue(comptime T: type, comptime size: usize) type {
|
||||||
|
return struct {
|
||||||
|
buf: [size]T = undefined,
|
||||||
|
|
||||||
|
read_index: usize = 0,
|
||||||
|
write_index: usize = 0,
|
||||||
|
|
||||||
|
mutex: std.Thread.Mutex = .{},
|
||||||
|
// blocks when the buffer is full
|
||||||
|
not_full: std.Thread.Condition = .{},
|
||||||
|
// ...or empty
|
||||||
|
not_empty: std.Thread.Condition = .{},
|
||||||
|
|
||||||
|
const QueueType = @This();
|
||||||
|
|
||||||
|
/// Pop an item from the queue. Blocks until an item is available.
|
||||||
|
pub fn pop(this: *QueueType) T {
|
||||||
|
this.mutex.lock();
|
||||||
|
defer this.mutex.unlock();
|
||||||
|
while (this.isEmptyLH()) {
|
||||||
|
this.not_empty.wait(&this.mutex);
|
||||||
|
}
|
||||||
|
assert(!this.isEmptyLH());
|
||||||
|
if (this.isFullLH()) {
|
||||||
|
// If we are full, wake up a push that might be
|
||||||
|
// waiting here.
|
||||||
|
this.not_full.signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = this.buf[this.mask(this.read_index)];
|
||||||
|
this.read_index = this.mask2(this.read_index + 1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push an item into the queue. Blocks until an item has been
|
||||||
|
/// put in the queue.
|
||||||
|
pub fn push(this: *QueueType, item: T) void {
|
||||||
|
this.mutex.lock();
|
||||||
|
defer this.mutex.unlock();
|
||||||
|
while (this.isFullLH()) {
|
||||||
|
this.not_full.wait(&this.mutex);
|
||||||
|
}
|
||||||
|
if (this.isEmptyLH()) {
|
||||||
|
// If we were empty, wake up a pop if it was waiting.
|
||||||
|
this.not_empty.signal();
|
||||||
|
}
|
||||||
|
assert(!this.isFullLH());
|
||||||
|
|
||||||
|
this.buf[this.mask(this.write_index)] = item;
|
||||||
|
this.write_index = this.mask2(this.write_index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push an item into the queue. Returns true when the item
|
||||||
|
/// was successfully placed in the queue, false if the queue
|
||||||
|
/// was full.
|
||||||
|
pub fn tryPush(this: *QueueType, item: T) bool {
|
||||||
|
this.mutex.lock();
|
||||||
|
if (this.isFullLH()) {
|
||||||
|
this.mutex.unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.mutex.unlock();
|
||||||
|
this.push(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop an item from the queue. Returns null when no item is
|
||||||
|
/// available.
|
||||||
|
pub fn tryPop(this: *QueueType) ?T {
|
||||||
|
this.mutex.lock();
|
||||||
|
if (this.isEmptyLH()) {
|
||||||
|
this.mutex.unlock();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.mutex.unlock();
|
||||||
|
return this.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll the queue. This call blocks until events are in the queue
|
||||||
|
pub fn poll(this: *QueueType) void {
|
||||||
|
this.mutex.lock();
|
||||||
|
defer this.mutex.unlock();
|
||||||
|
while (this.isEmptyLH()) {
|
||||||
|
this.not_empty.wait(&this.mutex);
|
||||||
|
}
|
||||||
|
assert(!this.isEmptyLH());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isEmptyLH(this: QueueType) bool {
|
||||||
|
return this.write_index == this.read_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isFullLH(this: QueueType) bool {
|
||||||
|
return this.mask2(this.write_index + this.buf.len) ==
|
||||||
|
this.read_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the queue is empty and `false` otherwise.
|
||||||
|
pub fn isEmpty(this: *QueueType) bool {
|
||||||
|
this.mutex.lock();
|
||||||
|
defer this.mutex.unlock();
|
||||||
|
return this.isEmptyLH();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the queue is full and `false` otherwise.
|
||||||
|
pub fn isFull(this: *QueueType) bool {
|
||||||
|
this.mutex.lock();
|
||||||
|
defer this.mutex.unlock();
|
||||||
|
return this.isFullLH();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length
|
||||||
|
fn len(this: QueueType) usize {
|
||||||
|
const wrap_offset = 2 * this.buf.len *
|
||||||
|
@intFromBool(this.write_index < this.read_index);
|
||||||
|
const adjusted_write_index = this.write_index + wrap_offset;
|
||||||
|
return adjusted_write_index - this.read_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `index` modulo the length of the backing slice.
|
||||||
|
fn mask(this: QueueType, index: usize) usize {
|
||||||
|
return index % this.buf.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `index` modulo twice the length of the backing slice.
|
||||||
|
fn mask2(this: QueueType, index: usize) usize {
|
||||||
|
return index % (2 * this.buf.len);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const cfg = Thread.SpawnConfig{ .allocator = testing.allocator };
|
||||||
|
test "Queue: simple push / pop" {
|
||||||
|
var queue: Queue(u8, 16) = .{};
|
||||||
|
queue.push(1);
|
||||||
|
queue.push(2);
|
||||||
|
const pop = queue.pop();
|
||||||
|
try testing.expectEqual(1, pop);
|
||||||
|
try testing.expectEqual(2, queue.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
const Thread = std.Thread;
|
||||||
|
fn testPushPop(q: *Queue(u8, 2)) !void {
|
||||||
|
q.push(3);
|
||||||
|
try testing.expectEqual(2, q.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Fill, wait to push, pop once in another thread" {
|
||||||
|
var queue: Queue(u8, 2) = .{};
|
||||||
|
queue.push(1);
|
||||||
|
queue.push(2);
|
||||||
|
const t = try Thread.spawn(cfg, testPushPop, .{&queue});
|
||||||
|
try testing.expectEqual(false, queue.tryPush(3));
|
||||||
|
try testing.expectEqual(1, queue.pop());
|
||||||
|
t.join();
|
||||||
|
try testing.expectEqual(3, queue.pop());
|
||||||
|
try testing.expectEqual(null, queue.tryPop());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testPush(q: *Queue(u8, 2)) void {
|
||||||
|
q.push(0);
|
||||||
|
q.push(1);
|
||||||
|
q.push(2);
|
||||||
|
q.push(3);
|
||||||
|
q.push(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Try to pop, fill from another thread" {
|
||||||
|
var queue: Queue(u8, 2) = .{};
|
||||||
|
const thread = try Thread.spawn(cfg, testPush, .{&queue});
|
||||||
|
for (0..5) |idx| {
|
||||||
|
try testing.expectEqual(@as(u8, @intCast(idx)), queue.pop());
|
||||||
|
}
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleepyPop(q: *Queue(u8, 2)) !void {
|
||||||
|
// First we wait for the queue to be full.
|
||||||
|
while (!q.isFull())
|
||||||
|
try Thread.yield();
|
||||||
|
|
||||||
|
// Then we spuriously wake it up, because that's a thing that can
|
||||||
|
// happen.
|
||||||
|
q.not_full.signal();
|
||||||
|
q.not_empty.signal();
|
||||||
|
|
||||||
|
// Then give the other thread a good chance of waking up. It's not
|
||||||
|
// clear that yield guarantees the other thread will be scheduled,
|
||||||
|
// so we'll throw a sleep in here just to be sure. The queue is
|
||||||
|
// still full and the push in the other thread is still blocked
|
||||||
|
// waiting for space.
|
||||||
|
try Thread.yield();
|
||||||
|
std.time.sleep(std.time.ns_per_s);
|
||||||
|
// Finally, let that other thread go.
|
||||||
|
try std.testing.expectEqual(1, q.pop());
|
||||||
|
|
||||||
|
// This won't continue until the other thread has had a chance to
|
||||||
|
// put at least one item in the queue.
|
||||||
|
while (!q.isFull())
|
||||||
|
try Thread.yield();
|
||||||
|
// But we want to ensure that there's a second push waiting, so
|
||||||
|
// here's another sleep.
|
||||||
|
std.time.sleep(std.time.ns_per_s / 2);
|
||||||
|
|
||||||
|
// Another spurious wake...
|
||||||
|
q.not_full.signal();
|
||||||
|
q.not_empty.signal();
|
||||||
|
// And another chance for the other thread to see that it's
|
||||||
|
// spurious and go back to sleep.
|
||||||
|
try Thread.yield();
|
||||||
|
std.time.sleep(std.time.ns_per_s / 2);
|
||||||
|
|
||||||
|
// Pop that thing and we're done.
|
||||||
|
try std.testing.expectEqual(2, q.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Fill, block, fill, block" {
|
||||||
|
// Fill the queue, block while trying to write another item, have
|
||||||
|
// a background thread unblock us, then block while trying to
|
||||||
|
// write yet another thing. Have the background thread unblock
|
||||||
|
// that too (after some time) then drain the queue. This test
|
||||||
|
// fails if the while loop in `push` is turned into an `if`.
|
||||||
|
|
||||||
|
var queue: Queue(u8, 2) = .{};
|
||||||
|
const thread = try Thread.spawn(cfg, sleepyPop, .{&queue});
|
||||||
|
queue.push(1);
|
||||||
|
queue.push(2);
|
||||||
|
const now = std.time.milliTimestamp();
|
||||||
|
queue.push(3); // This one should block.
|
||||||
|
const then = std.time.milliTimestamp();
|
||||||
|
|
||||||
|
// Just to make sure the sleeps are yielding to this thread, make
|
||||||
|
// sure it took at least 900ms to do the push.
|
||||||
|
try std.testing.expect(then - now > 900);
|
||||||
|
|
||||||
|
// This should block again, waiting for the other thread.
|
||||||
|
queue.push(4);
|
||||||
|
|
||||||
|
// And once that push has gone through, the other thread's done.
|
||||||
|
thread.join();
|
||||||
|
try std.testing.expectEqual(3, queue.pop());
|
||||||
|
try std.testing.expectEqual(4, queue.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleepyPush(q: *Queue(u8, 1)) !void {
|
||||||
|
// Try to ensure the other thread has already started trying to pop.
|
||||||
|
try Thread.yield();
|
||||||
|
std.time.sleep(std.time.ns_per_s / 2);
|
||||||
|
|
||||||
|
// Spurious wake
|
||||||
|
q.not_full.signal();
|
||||||
|
q.not_empty.signal();
|
||||||
|
|
||||||
|
try Thread.yield();
|
||||||
|
std.time.sleep(std.time.ns_per_s / 2);
|
||||||
|
|
||||||
|
// Stick something in the queue so it can be popped.
|
||||||
|
q.push(1);
|
||||||
|
// Ensure it's been popped.
|
||||||
|
while (!q.isEmpty())
|
||||||
|
try Thread.yield();
|
||||||
|
// Give the other thread time to block again.
|
||||||
|
try Thread.yield();
|
||||||
|
std.time.sleep(std.time.ns_per_s / 2);
|
||||||
|
|
||||||
|
// Spurious wake
|
||||||
|
q.not_full.signal();
|
||||||
|
q.not_empty.signal();
|
||||||
|
|
||||||
|
q.push(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Drain, block, drain, block" {
|
||||||
|
// This is like fill/block/fill/block, but on the pop end. This
|
||||||
|
// test should fail if the `while` loop in `pop` is turned into an
|
||||||
|
// `if`.
|
||||||
|
|
||||||
|
var queue: Queue(u8, 1) = .{};
|
||||||
|
const thread = try Thread.spawn(cfg, sleepyPush, .{&queue});
|
||||||
|
try std.testing.expectEqual(1, queue.pop());
|
||||||
|
try std.testing.expectEqual(2, queue.pop());
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readerThread(q: *Queue(u8, 1)) !void {
|
||||||
|
try testing.expectEqual(1, q.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "2 readers" {
|
||||||
|
// 2 threads read, one thread writes
|
||||||
|
var queue: Queue(u8, 1) = .{};
|
||||||
|
const t1 = try Thread.spawn(cfg, readerThread, .{&queue});
|
||||||
|
const t2 = try Thread.spawn(cfg, readerThread, .{&queue});
|
||||||
|
try Thread.yield();
|
||||||
|
std.time.sleep(std.time.ns_per_s / 2);
|
||||||
|
queue.push(1);
|
||||||
|
queue.push(1);
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writerThread(q: *Queue(u8, 1)) !void {
|
||||||
|
q.push(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "2 writers" {
|
||||||
|
var queue: Queue(u8, 1) = .{};
|
||||||
|
const t1 = try Thread.spawn(cfg, writerThread, .{&queue});
|
||||||
|
const t2 = try Thread.spawn(cfg, writerThread, .{&queue});
|
||||||
|
|
||||||
|
try testing.expectEqual(1, queue.pop());
|
||||||
|
try testing.expectEqual(1, queue.pop());
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const posix = std.posix;
|
|
||||||
const fmt = std.fmt;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.terminal);
|
const log = std.log.scoped(.terminal);
|
||||||
|
|
||||||
pub const Size = struct {
|
pub const Size = struct {
|
||||||
@@ -26,20 +23,44 @@ pub const ReportMode = enum {
|
|||||||
|
|
||||||
/// Gets number of rows and columns in the terminal
|
/// Gets number of rows and columns in the terminal
|
||||||
pub fn getTerminalSize() Size {
|
pub fn getTerminalSize() Size {
|
||||||
var ws: posix.winsize = undefined;
|
var ws: std.posix.winsize = undefined;
|
||||||
_ = posix.system.ioctl(posix.STDERR_FILENO, posix.T.IOCGWINSZ, &ws);
|
_ = std.posix.system.ioctl(std.posix.STDERR_FILENO, std.posix.T.IOCGWINSZ, &ws);
|
||||||
return .{ .cols = ws.ws_col, .rows = ws.ws_row };
|
return .{ .cols = ws.ws_col, .rows = ws.ws_row };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn saveScreen() !void {
|
||||||
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[?47h");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restoreScreen() !void {
|
||||||
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[?47l");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enterAltScreen() !void {
|
||||||
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[?1049h");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn existAltScreen() !void {
|
||||||
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[?1049l");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearScreen() !void {
|
||||||
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[2J");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCursorPositionHome() !void {
|
||||||
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[H");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getCursorPosition() !Position {
|
pub fn getCursorPosition() !Position {
|
||||||
// Needs Raw mode (no wait for \n) to work properly cause
|
// Needs Raw mode (no wait for \n) to work properly cause
|
||||||
// control sequence will not be written without it.
|
// control sequence will not be written without it.
|
||||||
_ = try posix.write(posix.STDERR_FILENO, "\x1b[6n");
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[6n");
|
||||||
|
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
|
|
||||||
// format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R"
|
// format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R"
|
||||||
const len = try posix.read(posix.STDIN_FILENO, &buf);
|
const len = try std.posix.read(std.posix.STDIN_FILENO, &buf);
|
||||||
|
|
||||||
if (!isCursorPosition(buf[0..len])) {
|
if (!isCursorPosition(buf[0..len])) {
|
||||||
return error.InvalidValueReturned;
|
return error.InvalidValueReturned;
|
||||||
@@ -73,8 +94,8 @@ pub fn getCursorPosition() !Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.row = try fmt.parseInt(u16, row[0..ridx], 10),
|
.row = try std.fmt.parseInt(u16, row[0..ridx], 10),
|
||||||
.col = try fmt.parseInt(u16, col[0..cidx], 10),
|
.col = try std.fmt.parseInt(u16, col[0..cidx], 10),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +123,8 @@ pub fn isCursorPosition(buf: []u8) bool {
|
|||||||
///
|
///
|
||||||
/// `bak`: pointer to store termios struct backup before
|
/// `bak`: pointer to store termios struct backup before
|
||||||
/// altering, this is used to disable raw mode.
|
/// altering, this is used to disable raw mode.
|
||||||
pub fn enableRawMode(bak: *posix.termios) !void {
|
pub fn enableRawMode(bak: *std.posix.termios) !void {
|
||||||
var termios = try posix.tcgetattr(posix.STDIN_FILENO);
|
var termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO);
|
||||||
bak.* = termios;
|
bak.* = termios;
|
||||||
|
|
||||||
termios.iflag.IXON = false;
|
termios.iflag.IXON = false;
|
||||||
@@ -114,18 +135,18 @@ pub fn enableRawMode(bak: *posix.termios) !void {
|
|||||||
termios.lflag.IEXTEN = false;
|
termios.lflag.IEXTEN = false;
|
||||||
termios.lflag.ISIG = false;
|
termios.lflag.ISIG = false;
|
||||||
|
|
||||||
try posix.tcsetattr(
|
try std.posix.tcsetattr(
|
||||||
posix.STDIN_FILENO,
|
std.posix.STDIN_FILENO,
|
||||||
posix.TCSA.FLUSH,
|
.FLUSH,
|
||||||
termios,
|
termios,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reverts `enableRawMode` to restore initial functionality.
|
/// Reverts `enableRawMode` to restore initial functionality.
|
||||||
pub fn disableRawMode(bak: *posix.termios) !void {
|
pub fn disableRawMode(bak: *std.posix.termios) !void {
|
||||||
try posix.tcsetattr(
|
try std.posix.tcsetattr(
|
||||||
posix.STDIN_FILENO,
|
std.posix.STDIN_FILENO,
|
||||||
posix.TCSA.FLUSH,
|
.FLUSH,
|
||||||
bak.*,
|
bak.*,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -134,12 +155,12 @@ pub fn disableRawMode(bak: *posix.termios) !void {
|
|||||||
pub fn canSynchornizeOutput() !bool {
|
pub fn canSynchornizeOutput() !bool {
|
||||||
// Needs Raw mode (no wait for \n) to work properly cause
|
// Needs Raw mode (no wait for \n) to work properly cause
|
||||||
// control sequence will not be written without it.
|
// control sequence will not be written without it.
|
||||||
_ = try posix.write(posix.STDERR_FILENO, "\x1b[?2026$p");
|
_ = try std.posix.write(std.posix.STDERR_FILENO, "\x1b[?2026$p");
|
||||||
|
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
|
|
||||||
// format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y"
|
// format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y"
|
||||||
const len = try posix.read(posix.STDIN_FILENO, &buf);
|
const len = try std.posix.read(std.posix.STDIN_FILENO, &buf);
|
||||||
if (!std.mem.eql(u8, buf[0..len], "\x1b[?2026;") or len < 9) {
|
if (!std.mem.eql(u8, buf[0..len], "\x1b[?2026;") or len < 9) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Dynamic dispatch for widget implementations.
|
//! Dynamic dispatch for widget implementations.
|
||||||
//! Each widget should at last implement these functions:
|
//! Each widget should at last implement these functions:
|
||||||
//! - handle(this: *@This(), event: Event) Event {}
|
//! - handle(this: *@This(), event: Event) ?Event {}
|
||||||
//! - content(this: *@This()) *std.ArrayList(u8) {}
|
//! - content(this: *@This()) *std.ArrayList(u8) {}
|
||||||
//! - deinit(this: *@This()) void {}
|
//! - deinit(this: *@This()) void {}
|
||||||
//!
|
//!
|
||||||
@@ -13,18 +13,17 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lib_event = @import("event.zig");
|
const lib_event = @import("event.zig");
|
||||||
|
|
||||||
pub fn Widget(comptime E: type) type {
|
pub fn Widget(comptime Event: type) type {
|
||||||
if (!lib_event.isTaggedUnion(E)) {
|
if (!lib_event.isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `E` for `Layout(comptime E: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
pub const Event = lib_event.MergeTaggedUnions(lib_event.BuiltinEvent, E);
|
|
||||||
const WidgetType = @This();
|
const WidgetType = @This();
|
||||||
const Ptr = usize;
|
const Ptr = usize;
|
||||||
|
|
||||||
const VTable = struct {
|
const VTable = struct {
|
||||||
handle: *const fn (this: *WidgetType, event: Event) Event,
|
handle: *const fn (this: *WidgetType, event: Event) ?Event,
|
||||||
content: *const fn (this: *WidgetType) *std.ArrayList(u8),
|
content: *const fn (this: *WidgetType) anyerror!*std.ArrayList(u8),
|
||||||
deinit: *const fn (this: *WidgetType) void,
|
deinit: *const fn (this: *WidgetType) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,13 +31,13 @@ pub fn Widget(comptime E: type) type {
|
|||||||
vtable: *const VTable = undefined,
|
vtable: *const VTable = undefined,
|
||||||
|
|
||||||
// Handle the provided `Event` for this `Widget`.
|
// Handle the provided `Event` for this `Widget`.
|
||||||
pub fn handle(this: *WidgetType, event: Event) Event {
|
pub fn handle(this: *WidgetType, event: Event) ?Event {
|
||||||
return this.vtable.handle(this, event);
|
return this.vtable.handle(this, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the entire content of this `Widget`.
|
// Return the entire content of this `Widget`.
|
||||||
pub fn content(this: *WidgetType) *std.ArrayList(u8) {
|
pub fn content(this: *WidgetType) !*std.ArrayList(u8) {
|
||||||
return this.vtable.content(this);
|
return try this.vtable.content(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *WidgetType) void {
|
pub fn deinit(this: *WidgetType) void {
|
||||||
@@ -52,16 +51,16 @@ pub fn Widget(comptime E: type) type {
|
|||||||
.vtable = &.{
|
.vtable = &.{
|
||||||
.handle = struct {
|
.handle = struct {
|
||||||
// Handle the provided `Event` for this `Widget`.
|
// Handle the provided `Event` for this `Widget`.
|
||||||
fn handle(this: *WidgetType, event: Event) Event {
|
fn handle(this: *WidgetType, event: Event) ?Event {
|
||||||
const widget: @TypeOf(object) = @ptrFromInt(this.object);
|
const widget: @TypeOf(object) = @ptrFromInt(this.object);
|
||||||
return widget.handle(event);
|
return widget.handle(event);
|
||||||
}
|
}
|
||||||
}.handle,
|
}.handle,
|
||||||
.content = struct {
|
.content = struct {
|
||||||
// Return the entire content of this `Widget`.
|
// Return the entire content of this `Widget`.
|
||||||
fn content(this: *WidgetType) *std.ArrayList(u8) {
|
fn content(this: *WidgetType) !*std.ArrayList(u8) {
|
||||||
const widget: @TypeOf(object) = @ptrFromInt(this.object);
|
const widget: @TypeOf(object) = @ptrFromInt(this.object);
|
||||||
return widget.content();
|
return try widget.content();
|
||||||
}
|
}
|
||||||
}.content,
|
}.content,
|
||||||
.deinit = struct {
|
.deinit = struct {
|
||||||
@@ -75,7 +74,7 @@ pub fn Widget(comptime E: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: import and export of `Widget` implementations (with corresponding intialization using `Event`)
|
// TODO: import and export of `Widget` implementations (with corresponding intialization using `Event`)
|
||||||
pub const RawText = @import("widget/RawText.zig").Widget(E);
|
pub const RawText = @import("widget/RawText.zig").Widget(Event);
|
||||||
// pub const Header = @import("widget/Header.zig");
|
// pub const Header = @import("widget/Header.zig");
|
||||||
// pub const ViewPort = @import("widget/ViewPort.zig");
|
// pub const ViewPort = @import("widget/ViewPort.zig");
|
||||||
// pub const PopupMenu = @import("widget/PopupMenu.zig");
|
// pub const PopupMenu = @import("widget/PopupMenu.zig");
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lib_event = @import("../event.zig");
|
const lib_event = @import("../event.zig");
|
||||||
|
|
||||||
pub fn Widget(comptime E: type) type {
|
pub fn Widget(comptime Event: type) type {
|
||||||
if (!lib_event.isTaggedUnion(E)) {
|
if (!lib_event.isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `E` for `Layout(comptime E: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
pub const Event = lib_event.MergeTaggedUnions(lib_event.BuiltinEvent, E);
|
|
||||||
|
|
||||||
c: std.ArrayList(u8) = undefined,
|
c: std.ArrayList(u8) = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
var c = std.ArrayList(u8).init(allocator);
|
return .{ .c = std.ArrayList(u8).init(allocator) };
|
||||||
c.appendSlice("This is a simple test") catch @panic("OOM");
|
|
||||||
return .{
|
|
||||||
.c = c,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(this: *@This()) void {
|
pub fn deinit(this: *@This()) void {
|
||||||
@@ -23,14 +17,16 @@ pub fn Widget(comptime E: type) type {
|
|||||||
this.* = undefined;
|
this.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(this: *@This(), event: Event) Event {
|
pub fn handle(this: *@This(), event: Event) ?Event {
|
||||||
// ignore the event for now
|
// ignore the event for now
|
||||||
_ = this;
|
_ = this;
|
||||||
_ = event;
|
_ = event;
|
||||||
return .none;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) *std.ArrayList(u8) {
|
pub fn content(this: *@This()) !*std.ArrayList(u8) {
|
||||||
|
this.c.clearRetainingCapacity();
|
||||||
|
try this.c.appendSlice("This is a simple test");
|
||||||
return &this.c;
|
return &this.c;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user