Add central Widget struct + clean up code

In particular, move all termbox2 usage to TerminalBuffer.zig &
keyboard.zig

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion
2026-02-10 00:22:27 +01:00
parent 99dba44e46
commit 207b352888
23 changed files with 1456 additions and 1029 deletions

View File

@@ -1,9 +1,9 @@
const std = @import("std");
const math = std.math;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
const ColorMix = @This();
@@ -45,14 +45,17 @@ pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) C
};
}
pub fn animation(self: *ColorMix) Animation {
return Animation.init(self, deinit, realloc, draw);
pub fn widget(self: *ColorMix) Widget {
return Widget.init(
self,
null,
null,
draw,
null,
null,
);
}
fn deinit(_: *ColorMix) void {}
fn realloc(_: *ColorMix) anyerror!void {}
fn draw(self: *ColorMix) void {
self.frames +%= 1;
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;

View File

@@ -1,9 +1,9 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
const Doom = @This();
@@ -49,15 +49,22 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
};
}
pub fn animation(self: *Doom) Animation {
return Animation.init(self, deinit, realloc, draw);
pub fn widget(self: *Doom) Widget {
return Widget.init(
self,
deinit,
realloc,
draw,
null,
null,
);
}
fn deinit(self: *Doom) void {
self.allocator.free(self.buffer);
}
fn realloc(self: *Doom) anyerror!void {
fn realloc(self: *Doom) !void {
const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height);
initBuffer(buffer, self.terminal_buffer.width);
self.buffer = buffer;

View File

@@ -9,11 +9,11 @@ const LogFile = ly_core.LogFile;
const enums = @import("../enums.zig");
const DurOffsetAlignment = enums.DurOffsetAlignment;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Color = TerminalBuffer.Color;
const Styling = TerminalBuffer.Styling;
const Widget = @import("../tui/Widget.zig");
fn read_decompress_file(allocator: Allocator, file_path: []const u8) ![]u8 {
const file_buffer = std.fs.cwd().openFile(file_path, .{}) catch {
@@ -403,15 +403,22 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_file: *L
};
}
pub fn animation(self: *DurFile) Animation {
return Animation.init(self, deinit, realloc, draw);
pub fn widget(self: *DurFile) Widget {
return Widget.init(
self,
deinit,
realloc,
draw,
null,
null,
);
}
fn deinit(self: *DurFile) void {
self.dur_movie.deinit();
}
fn realloc(self: *DurFile) anyerror!void {
fn realloc(self: *DurFile) !void {
// when terminal size changes, we need to recalculate the start_pos and frame_size based on the new size
self.start_pos = calc_start_position(self.terminal_buffer, &self.dur_movie, self.offset_alignment, self.offset);
self.frame_size = calc_frame_size(self.terminal_buffer, &self.dur_movie);

View File

@@ -1,9 +1,9 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
const GameOfLife = @This();
@@ -60,8 +60,15 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3
return game;
}
pub fn animation(self: *GameOfLife) Animation {
return Animation.init(self, deinit, realloc, draw);
pub fn widget(self: *GameOfLife) Widget {
return Widget.init(
self,
deinit,
realloc,
draw,
null,
null,
);
}
fn deinit(self: *GameOfLife) void {
@@ -69,7 +76,7 @@ fn deinit(self: *GameOfLife) void {
self.allocator.free(self.next_grid);
}
fn realloc(self: *GameOfLife) anyerror!void {
fn realloc(self: *GameOfLife) !void {
const new_width = self.terminal_buffer.width;
const new_height = self.terminal_buffer.height;
const new_size = new_width * new_height;

View File

@@ -2,9 +2,9 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const Random = std.Random;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Widget = @import("../tui/Widget.zig");
pub const FRAME_DELAY: usize = 8;
@@ -57,8 +57,15 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, hea
};
}
pub fn animation(self: *Matrix) Animation {
return Animation.init(self, deinit, realloc, draw);
pub fn widget(self: *Matrix) Widget {
return Widget.init(
self,
deinit,
realloc,
draw,
null,
null,
);
}
fn deinit(self: *Matrix) void {
@@ -66,7 +73,7 @@ fn deinit(self: *Matrix) void {
self.allocator.free(self.lines);
}
fn realloc(self: *Matrix) anyerror!void {
fn realloc(self: *Matrix) !void {
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1));
const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +0,0 @@
const Animation = @This();
const VTable = struct {
deinit_fn: *const fn (ptr: *anyopaque) void,
realloc_fn: *const fn (ptr: *anyopaque) anyerror!void,
draw_fn: *const fn (ptr: *anyopaque) void,
};
pointer: *anyopaque,
vtable: VTable,
pub fn init(
pointer: anytype,
comptime deinit_fn: fn (ptr: @TypeOf(pointer)) void,
comptime realloc_fn: fn (ptr: @TypeOf(pointer)) anyerror!void,
comptime draw_fn: fn (ptr: @TypeOf(pointer)) void,
) Animation {
const Pointer = @TypeOf(pointer);
const Impl = struct {
pub fn deinitImpl(ptr: *anyopaque) void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(.always_inline, deinit_fn, .{impl});
}
pub fn reallocImpl(ptr: *anyopaque) anyerror!void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(.always_inline, realloc_fn, .{impl});
}
pub fn drawImpl(ptr: *anyopaque) void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(.always_inline, draw_fn, .{impl});
}
const vtable = VTable{
.deinit_fn = deinitImpl,
.realloc_fn = reallocImpl,
.draw_fn = drawImpl,
};
};
return .{
.pointer = pointer,
.vtable = Impl.vtable,
};
}
pub fn deinit(self: *Animation) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(.auto, self.vtable.deinit_fn, .{impl});
}
pub fn realloc(self: *Animation) anyerror!void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(.auto, self.vtable.realloc_fn, .{impl});
}
pub fn draw(self: *Animation) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(.auto, self.vtable.draw_fn, .{impl});
}

View File

@@ -1,5 +1,4 @@
const TerminalBuffer = @import("TerminalBuffer.zig");
const termbox = TerminalBuffer.termbox;
const Cell = @This();
@@ -18,5 +17,5 @@ pub fn init(ch: u32, fg: u32, bg: u32) Cell {
pub fn put(self: Cell, x: usize, y: usize) void {
if (self.ch == 0) return;
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), self.ch, self.fg, self.bg);
TerminalBuffer.setCell(x, y, self.ch, self.fg, self.bg);
}

View File

@@ -144,34 +144,50 @@ pub fn init(allocator: Allocator, options: InitOptions, log_file: *LogFile, rand
pub fn deinit(self: *TerminalBuffer) void {
self.keybinds.deinit();
TerminalBuffer.shutdownStatic();
TerminalBuffer.shutdown();
}
pub fn getWidthStatic() usize {
pub fn getWidth() usize {
return @intCast(termbox.tb_width());
}
pub fn getHeightStatic() usize {
pub fn getHeight() usize {
return @intCast(termbox.tb_height());
}
pub fn setCursorStatic(x: usize, y: usize) void {
pub fn setCursor(x: usize, y: usize) void {
_ = termbox.tb_set_cursor(@intCast(x), @intCast(y));
}
pub fn clearScreenStatic(clear_back_buffer: bool) !void {
pub fn clearScreen(clear_back_buffer: bool) !void {
_ = termbox.tb_clear();
if (clear_back_buffer) try clearBackBuffer();
}
pub fn shutdownStatic() void {
pub fn shutdown() void {
_ = termbox.tb_shutdown();
}
pub fn presentBufferStatic() void {
pub fn presentBuffer() void {
_ = termbox.tb_present();
}
pub fn setCell(
x: usize,
y: usize,
ch: u32,
fg: u32,
bg: u32,
) void {
_ = termbox.tb_set_cell(
@intCast(x),
@intCast(y),
ch,
fg,
bg,
);
}
pub fn reclaim(self: TerminalBuffer) !void {
if (self.termios) |termios| {
// Take back control of the TTY
@@ -257,17 +273,22 @@ pub fn handleKeybind(
allocator: Allocator,
tb_event: termbox.tb_event,
context: *anyopaque,
) !bool {
) !?std.ArrayList(keyboard.Key) {
var keys = try keyboard.getKeyList(allocator, tb_event);
defer keys.deinit(allocator);
for (keys.items) |key| {
if (self.keybinds.get(key)) |callback| {
return @call(.auto, callback, .{context});
const passthrough_event = try @call(.auto, callback, .{context});
if (!passthrough_event) {
keys.deinit(allocator);
return null;
}
return keys;
}
}
return true;
return keys;
}
pub fn drawText(

150
src/tui/Widget.zig Normal file
View File

@@ -0,0 +1,150 @@
const Widget = @This();
const keyboard = @import("keyboard.zig");
const TerminalBuffer = @import("TerminalBuffer.zig");
const VTable = struct {
deinit_fn: *const fn (ptr: *anyopaque) void,
realloc_fn: *const fn (ptr: *anyopaque) anyerror!void,
draw_fn: *const fn (ptr: *anyopaque) void,
update_fn: *const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!void,
handle_fn: *const fn (ptr: *anyopaque, maybe_key: ?keyboard.Key, insert_mode: bool) anyerror!void,
};
pointer: *anyopaque,
vtable: VTable,
pub fn init(
pointer: anytype,
comptime deinit_fn: ?fn (ptr: @TypeOf(pointer)) void,
comptime realloc_fn: ?fn (ptr: @TypeOf(pointer)) anyerror!void,
comptime draw_fn: ?fn (ptr: @TypeOf(pointer)) void,
comptime update_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!void,
comptime handle_fn: ?fn (ptr: @TypeOf(pointer), maybe_key: ?keyboard.Key, insert_mode: bool) anyerror!void,
) Widget {
const Pointer = @TypeOf(pointer);
const Impl = struct {
pub fn deinitImpl(ptr: *anyopaque) void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
if (deinit_fn) |func| {
return @call(
.always_inline,
func,
.{impl},
);
}
}
pub fn reallocImpl(ptr: *anyopaque) !void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
if (realloc_fn) |func| {
return @call(
.always_inline,
func,
.{impl},
);
}
}
pub fn drawImpl(ptr: *anyopaque) void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
if (draw_fn) |func| {
return @call(
.always_inline,
func,
.{impl},
);
}
}
pub fn updateImpl(ptr: *anyopaque, ctx: *anyopaque) !void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
if (update_fn) |func| {
return @call(
.always_inline,
func,
.{ impl, ctx },
);
}
}
pub fn handleImpl(ptr: *anyopaque, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
if (handle_fn) |func| {
return @call(
.always_inline,
func,
.{ impl, maybe_key, insert_mode },
);
}
}
const vtable = VTable{
.deinit_fn = deinitImpl,
.realloc_fn = reallocImpl,
.draw_fn = drawImpl,
.update_fn = updateImpl,
.handle_fn = handleImpl,
};
};
return .{
.pointer = pointer,
.vtable = Impl.vtable,
};
}
pub fn deinit(self: *Widget) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(
.auto,
self.vtable.deinit_fn,
.{impl},
);
}
pub fn realloc(self: *Widget) !void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(
.auto,
self.vtable.realloc_fn,
.{impl},
);
}
pub fn draw(self: *Widget) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(
.auto,
self.vtable.draw_fn,
.{impl},
);
}
pub fn update(self: *Widget, ctx: *anyopaque) !void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(
.auto,
self.vtable.update_fn,
.{ impl, ctx },
);
}
pub fn handle(self: *Widget, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(
.auto,
self.vtable.handle_fn,
.{ impl, maybe_key, insert_mode },
);
}

View File

@@ -0,0 +1,215 @@
const BigLabel = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const en = @import("bigLabelLocales/en.zig");
const fa = @import("bigLabelLocales/fa.zig");
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
pub const CHAR_WIDTH = 5;
pub const CHAR_HEIGHT = 5;
pub const CHAR_SIZE = CHAR_WIDTH * CHAR_HEIGHT;
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
pub const O: u32 = 0;
// zig fmt: off
pub const LocaleChars = struct {
ZERO: [CHAR_SIZE]u21,
ONE: [CHAR_SIZE]u21,
TWO: [CHAR_SIZE]u21,
THREE: [CHAR_SIZE]u21,
FOUR: [CHAR_SIZE]u21,
FIVE: [CHAR_SIZE]u21,
SIX: [CHAR_SIZE]u21,
SEVEN: [CHAR_SIZE]u21,
EIGHT: [CHAR_SIZE]u21,
NINE: [CHAR_SIZE]u21,
S: [CHAR_SIZE]u21,
E: [CHAR_SIZE]u21,
P: [CHAR_SIZE]u21,
A: [CHAR_SIZE]u21,
M: [CHAR_SIZE]u21,
};
// zig fmt: on
pub const BigLabelLocale = enum {
en,
fa,
};
allocator: ?Allocator = null,
buffer: *TerminalBuffer,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
component_pos: Position,
children_pos: Position,
pub fn init(
buffer: *TerminalBuffer,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
) BigLabel {
return .{
.allocator = null,
.buffer = buffer,
.text = text,
.max_width = max_width,
.fg = fg,
.bg = bg,
.locale = locale,
.update_fn = update_fn,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn deinit(self: *BigLabel) void {
if (self.allocator) |allocator| allocator.free(self.text);
}
pub fn widget(self: *BigLabel) Widget {
return Widget.init(
self,
deinit,
null,
draw,
update,
null,
);
}
pub fn setTextAlloc(
self: *BigLabel,
allocator: Allocator,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.allocPrint(allocator, fmt, args);
self.allocator = allocator;
}
pub fn setTextBuf(
self: *BigLabel,
buffer: []u8,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.bufPrint(buffer, fmt, args);
self.allocator = null;
}
pub fn setText(self: *BigLabel, text: []const u8) void {
self.text = text;
self.allocator = null;
}
pub fn positionX(self: *BigLabel, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text) * CHAR_WIDTH);
}
pub fn positionY(self: *BigLabel, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(CHAR_HEIGHT);
}
pub fn positionXY(self: *BigLabel, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
TerminalBuffer.strWidth(self.text) * CHAR_WIDTH,
CHAR_HEIGHT,
).add(original_pos);
}
pub fn childrenPosition(self: BigLabel) Position {
return self.children_pos;
}
pub fn draw(self: *BigLabel) void {
for (self.text, 0..) |c, i| {
const clock_cell = clockCell(
c,
self.fg,
self.bg,
self.locale,
);
alphaBlit(
self.component_pos.x + i * (CHAR_WIDTH + 1),
self.component_pos.y,
self.buffer.width,
self.buffer.height,
clock_cell,
);
}
}
pub fn update(self: *BigLabel, context: *anyopaque) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, context },
);
}
}
fn clockCell(char: u8, fg: u32, bg: u32, locale: BigLabelLocale) [CHAR_SIZE]Cell {
var cells: [CHAR_SIZE]Cell = undefined;
//@divTrunc(time.microseconds, 500000) != 0)
const clock_chars = toBigNumber(char, locale);
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
return cells;
}
fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [CHAR_SIZE]Cell) void {
if (x + CHAR_WIDTH >= tb_width or y + CHAR_HEIGHT >= tb_height) return;
for (0..CHAR_HEIGHT) |yy| {
for (0..CHAR_WIDTH) |xx| {
const cell = cells[yy * CHAR_WIDTH + xx];
cell.put(x + xx, y + yy);
}
}
}
fn toBigNumber(char: u8, locale: BigLabelLocale) [CHAR_SIZE]u21 {
const locale_chars = switch (locale) {
.fa => fa.locale_chars,
.en => en.locale_chars,
};
return switch (char) {
'0' => locale_chars.ZERO,
'1' => locale_chars.ONE,
'2' => locale_chars.TWO,
'3' => locale_chars.THREE,
'4' => locale_chars.FOUR,
'5' => locale_chars.FIVE,
'6' => locale_chars.SIX,
'7' => locale_chars.SEVEN,
'8' => locale_chars.EIGHT,
'9' => locale_chars.NINE,
'p', 'P' => locale_chars.P,
'a', 'A' => locale_chars.A,
'm', 'M' => locale_chars.M,
':' => locale_chars.S,
else => locale_chars.E,
};
}

View File

@@ -3,7 +3,7 @@ const std = @import("std");
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const termbox = TerminalBuffer.termbox;
const Widget = @import("../Widget.zig");
const CenteredBox = @This();
@@ -56,6 +56,17 @@ pub fn init(
};
}
pub fn widget(self: *CenteredBox) Widget {
return Widget.init(
self,
null,
null,
draw,
null,
null,
);
}
pub fn positionXY(self: *CenteredBox, original_pos: Position) void {
if (self.buffer.width < 2 or self.buffer.height < 2) return;
@@ -79,51 +90,55 @@ pub fn childrenPosition(self: CenteredBox) Position {
return self.children_pos;
}
pub fn draw(self: CenteredBox) void {
pub fn draw(self: *CenteredBox) void {
if (self.show_borders) {
_ = termbox.tb_set_cell(
@intCast(self.left_pos.x - 1),
@intCast(self.left_pos.y - 1),
var left_up = Cell.init(
self.buffer.box_chars.left_up,
self.border_fg,
self.bg,
);
_ = termbox.tb_set_cell(
@intCast(self.right_pos.x),
@intCast(self.left_pos.y - 1),
var right_up = Cell.init(
self.buffer.box_chars.right_up,
self.border_fg,
self.bg,
);
_ = termbox.tb_set_cell(
@intCast(self.left_pos.x - 1),
@intCast(self.right_pos.y),
var left_down = Cell.init(
self.buffer.box_chars.left_down,
self.border_fg,
self.bg,
);
_ = termbox.tb_set_cell(
@intCast(self.right_pos.x),
@intCast(self.right_pos.y),
var right_down = Cell.init(
self.buffer.box_chars.right_down,
self.border_fg,
self.bg,
);
var top = Cell.init(
self.buffer.box_chars.top,
self.border_fg,
self.bg,
);
var bottom = Cell.init(
self.buffer.box_chars.bottom,
self.border_fg,
self.bg,
);
var c1 = Cell.init(self.buffer.box_chars.top, self.border_fg, self.bg);
var c2 = Cell.init(self.buffer.box_chars.bottom, self.border_fg, self.bg);
left_up.put(self.left_pos.x - 1, self.left_pos.y - 1);
right_up.put(self.right_pos.x, self.left_pos.y - 1);
left_down.put(self.left_pos.x - 1, self.right_pos.y);
right_down.put(self.right_pos.x, self.right_pos.y);
for (0..self.width) |i| {
c1.put(self.left_pos.x + i, self.left_pos.y - 1);
c2.put(self.left_pos.x + i, self.right_pos.y);
top.put(self.left_pos.x + i, self.left_pos.y - 1);
bottom.put(self.left_pos.x + i, self.right_pos.y);
}
c1.ch = self.buffer.box_chars.left;
c2.ch = self.buffer.box_chars.right;
top.ch = self.buffer.box_chars.left;
bottom.ch = self.buffer.box_chars.right;
for (0..self.height) |i| {
c1.put(self.left_pos.x - 1, self.left_pos.y + i);
c2.put(self.right_pos.x, self.left_pos.y + i);
top.put(self.left_pos.x - 1, self.left_pos.y + i);
bottom.put(self.right_pos.x, self.left_pos.y + i);
}
}

View File

@@ -1,7 +1,9 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
const generic = @import("generic.zig");
const MessageLabel = generic.CyclableLabel(Message, Message);
@@ -43,6 +45,25 @@ pub fn deinit(self: *InfoLine) void {
self.label.deinit();
}
pub fn widget(self: *InfoLine) Widget {
return Widget.init(
self,
deinit,
null,
draw,
null,
handle,
);
}
pub fn draw(self: *InfoLine) void {
self.label.draw();
}
pub fn handle(self: *InfoLine, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
self.label.handle(maybe_key, insert_mode);
}
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
if (text.len == 0) return;

View File

@@ -0,0 +1,131 @@
const Label = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
allocator: ?Allocator,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
update_fn: ?*const fn (*Label, *anyopaque) anyerror!void,
component_pos: Position,
children_pos: Position,
pub fn init(
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
update_fn: ?*const fn (*Label, *anyopaque) anyerror!void,
) Label {
return .{
.allocator = null,
.text = text,
.max_width = max_width,
.fg = fg,
.bg = bg,
.update_fn = update_fn,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn deinit(self: *Label) void {
if (self.allocator) |allocator| allocator.free(self.text);
}
pub fn widget(self: *Label) Widget {
return Widget.init(
self,
deinit,
null,
draw,
update,
null,
);
}
pub fn setTextAlloc(
self: *Label,
allocator: Allocator,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.allocPrint(allocator, fmt, args);
self.allocator = allocator;
}
pub fn setTextBuf(
self: *Label,
buffer: []u8,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.bufPrint(buffer, fmt, args);
self.allocator = null;
}
pub fn setText(self: *Label, text: []const u8) void {
self.text = text;
self.allocator = null;
}
pub fn positionX(self: *Label, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text));
}
pub fn positionY(self: *Label, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(1);
}
pub fn positionXY(self: *Label, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
TerminalBuffer.strWidth(self.text),
1,
).add(original_pos);
}
pub fn childrenPosition(self: Label) Position {
return self.children_pos;
}
pub fn draw(self: *Label) void {
if (self.max_width) |width| {
TerminalBuffer.drawConfinedText(
self.text,
self.component_pos.x,
self.component_pos.y,
width,
self.fg,
self.bg,
);
return;
}
TerminalBuffer.drawText(
self.text,
self.component_pos.x,
self.component_pos.y,
self.fg,
self.bg,
);
}
pub fn update(self: *Label, ctx: *anyopaque) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, ctx },
);
}
}

View File

@@ -4,7 +4,9 @@ const Allocator = std.mem.Allocator;
const enums = @import("../../enums.zig");
const DisplayServer = enums.DisplayServer;
const Environment = @import("../../Environment.zig");
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
const generic = @import("generic.zig");
const UserList = @import("UserList.zig");
@@ -53,6 +55,25 @@ pub fn deinit(self: *Session) void {
self.label.deinit();
}
pub fn widget(self: *Session) Widget {
return Widget.init(
self,
deinit,
null,
draw,
null,
handle,
);
}
pub fn draw(self: *Session) void {
self.label.draw();
}
pub fn handle(self: *Session, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
self.label.handle(maybe_key, insert_mode);
}
pub fn addEnvironment(self: *Session, environment: Environment) !void {
const env = Env{ .environment = environment, .index = self.label.list.items.len };

View File

@@ -1,9 +1,10 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Position = @import("../Position.zig");
const termbox = TerminalBuffer.termbox;
const Widget = @import("../Widget.zig");
const DynamicString = std.ArrayListUnmanaged(u8);
@@ -53,6 +54,17 @@ pub fn deinit(self: *Text) void {
self.text.deinit(self.allocator);
}
pub fn widget(self: *Text) Widget {
return Widget.init(
self,
deinit,
null,
draw,
null,
handle,
);
}
pub fn positionX(self: *Text, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(self.width);
@@ -75,50 +87,37 @@ pub fn childrenPosition(self: Text) Position {
return self.children_pos;
}
pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void {
if (maybe_event) |event| blk: {
if (event.type != termbox.TB_EVENT_KEY) break :blk;
switch (event.key) {
termbox.TB_KEY_ARROW_LEFT => self.goLeft(),
termbox.TB_KEY_ARROW_RIGHT => self.goRight(),
termbox.TB_KEY_DELETE => self.delete(),
termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => {
if (insert_mode) {
self.backspace();
} else {
self.goLeft();
}
},
termbox.TB_KEY_SPACE => try self.write(' '),
else => {
if (event.ch > 31 and event.ch < 127) {
if (insert_mode) {
try self.write(@intCast(event.ch));
} else {
switch (event.ch) {
'h' => self.goLeft(),
'l' => self.goRight(),
else => {},
}
}
}
},
pub fn handle(self: *Text, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
if (maybe_key) |key| {
if (key.left or (!insert_mode and (key.h or key.backspace))) {
self.goLeft();
} else if (key.right or (!insert_mode and key.l)) {
self.goRight();
} else if (key.delete) {
self.delete();
} else if (key.backspace) {
self.backspace();
} else if (insert_mode) {
const maybe_character = key.getEnabledPrintableAscii();
if (maybe_character) |character| try self.write(character);
}
}
if (self.masked and self.maybe_mask == null) {
_ = termbox.tb_set_cursor(@intCast(self.component_pos.x), @intCast(self.component_pos.y));
TerminalBuffer.setCursor(
self.component_pos.x,
self.component_pos.y,
);
return;
}
_ = termbox.tb_set_cursor(
@intCast(self.component_pos.x + (self.cursor - self.visible_start)),
@intCast(self.component_pos.y),
TerminalBuffer.setCursor(
self.component_pos.x + (self.cursor - self.visible_start),
self.component_pos.y,
);
}
pub fn draw(self: Text) void {
pub fn draw(self: *Text) void {
if (self.masked) {
if (self.maybe_mask) |mask| {
if (self.width < 1) return;

View File

@@ -2,7 +2,9 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const SavedUsers = @import("../../config/SavedUsers.zig");
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
const generic = @import("generic.zig");
const Session = @import("Session.zig");
@@ -85,10 +87,29 @@ pub fn deinit(self: *UserList) void {
self.label.deinit();
}
pub fn widget(self: *UserList) Widget {
return Widget.init(
self,
deinit,
null,
draw,
null,
handle,
);
}
pub fn getCurrentUsername(self: UserList) []const u8 {
return self.label.list.items[self.label.current].name;
}
fn draw(self: *UserList) void {
self.label.draw();
}
fn handle(self: *UserList, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
self.label.handle(maybe_key, insert_mode);
}
fn usernameChanged(user: User, maybe_session: ?*Session) void {
if (maybe_session) |session| {
session.label.current = @min(user.session_index.*, session.label.list.items.len - 1);

View File

@@ -1,210 +0,0 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const en = @import("bigLabelLocales/en.zig");
const fa = @import("bigLabelLocales/fa.zig");
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const termbox = TerminalBuffer.termbox;
pub const CHAR_WIDTH = 5;
pub const CHAR_HEIGHT = 5;
pub const CHAR_SIZE = CHAR_WIDTH * CHAR_HEIGHT;
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
pub const O: u32 = 0;
// zig fmt: off
pub const LocaleChars = struct {
ZERO: [CHAR_SIZE]u21,
ONE: [CHAR_SIZE]u21,
TWO: [CHAR_SIZE]u21,
THREE: [CHAR_SIZE]u21,
FOUR: [CHAR_SIZE]u21,
FIVE: [CHAR_SIZE]u21,
SIX: [CHAR_SIZE]u21,
SEVEN: [CHAR_SIZE]u21,
EIGHT: [CHAR_SIZE]u21,
NINE: [CHAR_SIZE]u21,
S: [CHAR_SIZE]u21,
E: [CHAR_SIZE]u21,
P: [CHAR_SIZE]u21,
A: [CHAR_SIZE]u21,
M: [CHAR_SIZE]u21,
};
// zig fmt: on
pub const BigLabelLocale = enum {
en,
fa,
};
pub fn BigLabel(comptime ContextType: type) type {
return struct {
const Self = @This();
buffer: *TerminalBuffer,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*Self, ContextType) anyerror!void,
is_text_allocated: bool,
component_pos: Position,
children_pos: Position,
pub fn init(
buffer: *TerminalBuffer,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*Self, ContextType) anyerror!void,
) Self {
return .{
.buffer = buffer,
.text = text,
.max_width = max_width,
.fg = fg,
.bg = bg,
.locale = locale,
.update_fn = update_fn,
.is_text_allocated = false,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn setTextAlloc(
self: *Self,
allocator: Allocator,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.allocPrint(allocator, fmt, args);
self.is_text_allocated = true;
}
pub fn setTextBuf(
self: *Self,
buffer: []u8,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.bufPrint(buffer, fmt, args);
self.is_text_allocated = false;
}
pub fn setText(self: *Self, text: []const u8) void {
self.text = text;
self.is_text_allocated = false;
}
pub fn deinit(self: Self, allocator: ?Allocator) void {
if (self.is_text_allocated) {
if (allocator) |alloc| alloc.free(self.text);
}
}
pub fn positionX(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text) * CHAR_WIDTH);
}
pub fn positionY(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(CHAR_HEIGHT);
}
pub fn positionXY(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
TerminalBuffer.strWidth(self.text) * CHAR_WIDTH,
CHAR_HEIGHT,
).add(original_pos);
}
pub fn childrenPosition(self: Self) Position {
return self.children_pos;
}
pub fn draw(self: Self) void {
for (self.text, 0..) |c, i| {
const clock_cell = clockCell(
c,
self.fg,
self.bg,
self.locale,
);
alphaBlit(
self.component_pos.x + i * (CHAR_WIDTH + 1),
self.component_pos.y,
self.buffer.width,
self.buffer.height,
clock_cell,
);
}
}
pub fn update(self: *Self, context: ContextType) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, context },
);
}
}
fn clockCell(char: u8, fg: u32, bg: u32, locale: BigLabelLocale) [CHAR_SIZE]Cell {
var cells: [CHAR_SIZE]Cell = undefined;
//@divTrunc(time.microseconds, 500000) != 0)
const clock_chars = toBigNumber(char, locale);
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
return cells;
}
fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [CHAR_SIZE]Cell) void {
if (x + CHAR_WIDTH >= tb_width or y + CHAR_HEIGHT >= tb_height) return;
for (0..CHAR_HEIGHT) |yy| {
for (0..CHAR_WIDTH) |xx| {
const cell = cells[yy * CHAR_WIDTH + xx];
cell.put(x + xx, y + yy);
}
}
}
fn toBigNumber(char: u8, locale: BigLabelLocale) [CHAR_SIZE]u21 {
const locale_chars = switch (locale) {
.fa => fa.locale_chars,
.en => en.locale_chars,
};
return switch (char) {
'0' => locale_chars.ZERO,
'1' => locale_chars.ONE,
'2' => locale_chars.TWO,
'3' => locale_chars.THREE,
'4' => locale_chars.FOUR,
'5' => locale_chars.FIVE,
'6' => locale_chars.SIX,
'7' => locale_chars.SEVEN,
'8' => locale_chars.EIGHT,
'9' => locale_chars.NINE,
'p', 'P' => locale_chars.P,
'a', 'A' => locale_chars.A,
'm', 'M' => locale_chars.M,
':' => locale_chars.S,
else => locale_chars.E,
};
}
};
}

View File

@@ -1,7 +1,7 @@
const bigLabel = @import("../bigLabel.zig");
const LocaleChars = bigLabel.LocaleChars;
const X = bigLabel.X;
const O = bigLabel.O;
const BigLabel = @import("../BigLabel.zig");
const LocaleChars = BigLabel.LocaleChars;
const X = BigLabel.X;
const O = BigLabel.O;
// zig fmt: off
pub const locale_chars = LocaleChars{

View File

@@ -1,7 +1,7 @@
const bigLabel = @import("../bigLabel.zig");
const LocaleChars = bigLabel.LocaleChars;
const X = bigLabel.X;
const O = bigLabel.O;
const BigLabel = @import("../BigLabel.zig");
const LocaleChars = BigLabel.LocaleChars;
const X = BigLabel.X;
const O = BigLabel.O;
// zig fmt: off
pub const locale_chars = LocaleChars{

View File

@@ -1,5 +1,7 @@
const std = @import("std");
const Cell = @import("../Cell.zig");
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Position = @import("../Position.zig");
@@ -10,8 +12,6 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
const DrawItemFn = *const fn (*Self, ItemType, usize, usize, usize) void;
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
const termbox = TerminalBuffer.termbox;
const Self = @This();
allocator: Allocator,
@@ -92,28 +92,18 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
self.current = self.list.items.len - 1;
}
pub fn handle(self: *Self, maybe_event: ?*termbox.tb_event, insert_mode: bool) void {
if (maybe_event) |event| blk: {
if (event.type != termbox.TB_EVENT_KEY) break :blk;
switch (event.key) {
termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(),
termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(),
else => {
if (!insert_mode) {
switch (event.ch) {
'h' => self.goLeft(),
'l' => self.goRight(),
else => {},
}
}
},
pub fn handle(self: *Self, maybe_key: ?keyboard.Key, insert_mode: bool) void {
if (maybe_key) |key| {
if (key.left or (key.ctrl and key.h) or (!insert_mode and key.h)) {
self.goLeft();
} else if (key.right or (key.ctrl and key.l) or (!insert_mode and key.l)) {
self.goRight();
}
}
_ = termbox.tb_set_cursor(
@intCast(self.component_pos.x + self.cursor + 2),
@intCast(self.component_pos.y),
TerminalBuffer.setCursor(
self.component_pos.x + self.cursor + 2,
self.component_pos.y,
);
}
@@ -121,19 +111,13 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
if (self.list.items.len == 0) return;
if (self.width < 2) return;
_ = termbox.tb_set_cell(
@intCast(self.component_pos.x),
@intCast(self.component_pos.y),
'<',
self.fg,
self.bg,
);
_ = termbox.tb_set_cell(
@intCast(self.component_pos.x + self.width - 1),
@intCast(self.component_pos.y),
'>',
self.fg,
self.bg,
var left_arrow = Cell.init('<', self.fg, self.bg);
var right_arrow = Cell.init('>', self.fg, self.bg);
left_arrow.put(self.component_pos.x, self.component_pos.y);
right_arrow.put(
self.component_pos.x + self.width - 1,
self.component_pos.y,
);
const current_item = self.list.items[self.current];

View File

@@ -1,126 +0,0 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const termbox = TerminalBuffer.termbox;
pub fn Label(comptime ContextType: type) type {
return struct {
const Self = @This();
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
update_fn: ?*const fn (*Self, ContextType) anyerror!void,
is_text_allocated: bool,
component_pos: Position,
children_pos: Position,
pub fn init(
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
update_fn: ?*const fn (*Self, ContextType) anyerror!void,
) Self {
return .{
.text = text,
.max_width = max_width,
.fg = fg,
.bg = bg,
.update_fn = update_fn,
.is_text_allocated = false,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn setTextAlloc(
self: *Self,
allocator: Allocator,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.allocPrint(allocator, fmt, args);
self.is_text_allocated = true;
}
pub fn setTextBuf(
self: *Self,
buffer: []u8,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.bufPrint(buffer, fmt, args);
self.is_text_allocated = false;
}
pub fn setText(self: *Self, text: []const u8) void {
self.text = text;
self.is_text_allocated = false;
}
pub fn deinit(self: Self, allocator: ?Allocator) void {
if (self.is_text_allocated) {
if (allocator) |alloc| alloc.free(self.text);
}
}
pub fn positionX(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text));
}
pub fn positionY(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(1);
}
pub fn positionXY(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
TerminalBuffer.strWidth(self.text),
1,
).add(original_pos);
}
pub fn childrenPosition(self: Self) Position {
return self.children_pos;
}
pub fn draw(self: Self) void {
if (self.max_width) |width| {
TerminalBuffer.drawConfinedText(
self.text,
self.component_pos.x,
self.component_pos.y,
width,
self.fg,
self.bg,
);
return;
}
TerminalBuffer.drawText(
self.text,
self.component_pos.x,
self.component_pos.y,
self.fg,
self.bg,
);
}
pub fn update(self: *Self, context: ContextType) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, context },
);
}
}
};
}

View File

@@ -36,8 +36,8 @@ pub const Key = packed struct {
tab: bool,
backspace: bool,
enter: bool,
space: bool,
@" ": bool,
@"!": bool,
@"`": bool,
esc: bool,
@@ -109,6 +109,18 @@ pub const Key = packed struct {
x: bool,
y: bool,
z: bool,
pub fn getEnabledPrintableAscii(self: Key) ?u8 {
if (self.ctrl or self.shift or self.alt) return null;
inline for (std.meta.fields(Key)) |field| {
if (field.name.len == 1 and std.ascii.isPrint(field.name[0]) and @field(self, field.name)) {
return field.name[0];
}
}
return null;
}
};
pub fn getKeyList(allocator: Allocator, tb_event: termbox.tb_event) !KeyList {
@@ -326,7 +338,7 @@ pub fn getKeyList(allocator: Allocator, tb_event: termbox.tb_event) !KeyList {
key._ = true;
},
32 => {
key.space = true;
key.@" " = true;
},
33 => {
key.shift = true;