mirror of
https://github.com/fairyglade/ly.git
synced 2025-12-21 03:34:54 +00:00
Nobody expects the Ziguanas (#517)
* Add build.zig, remove makefile, add .idea directory to .gitignore * Remove submodules, add projects directly * Remove submodules * Add projects * Rename sub/ to dep/, remove makefiles * Rewrite main.c * Remove Argoat dependency * Remove unused dependencies * Rewrite config.c * Add files * Change default fg to 8 in config.ini * Partially rewrite utils.c * Use Zig package manager * Rewrite INPUTS enum in Zig * Commit unfinished full rewrite (Zig 0.11.0) What needs to be dealt with: - Matrix animation - Authentication part - Testing on actual TTY (not just virtual console) Signed-off-by: AnErrupTion <anerruption@disroot.org> * Implement more (untested) authentication code Signed-off-by: AnErrupTion <anerruption@disroot.org> * Fix some bugs (hopefully) Signed-off-by: AnErrupTion <anerruption@disroot.org> * Try to fix some more bugs Signed-off-by: AnErrupTion <anerruption@disroot.org> * Oops, forgot to allocate hehe Signed-off-by: AnErrupTion <anerruption@disroot.org> * Changes in the Zig rewrite (#596) * Everything * make matrix.zig a bit cleaner * make long lines shorter and add changelog * vi mode * update changelog * get errors from child process and (hopefully) fix some other things * fix utmp entry * run authentication in a child process * update changelog * small code improvements * change that * clear terminal on SIGTERM * Remove LogFile * moved ini to a lib, fixed alternative langs * fix logging out * oops * code improvements * consistency * clearing the env isn't needed anymore (afaik) * replace vi_mode with a bool * type aliases, avoiding zeroes(), breaking a long line * lowercase insert/normal, merge conditionals, code improvements * Add experimental save file migrator + bug fixes + add "-dev" version suffix Signed-off-by: AnErrupTion <anerruption@disroot.org> * Resolve conflicts Signed-off-by: AnErrupTion <anerruption@disroot.org> * Clean up when SIGTERM is received (#597) * clean up child processes on SIGTERM * small code improvement * consistency.. i guess? * Properly set XDG_CURRENT_DESKTOP Signed-off-by: AnErrupTion <anerruption@disroot.org> * Zig 0.12.0 and more! (#599) * less alloc, update migrator, get DesktopNames from .desktop * small cleanup * Update zigini to improve compatibility with old config * Code improvements * Update to zig version 0.12.0 * Some fixes * tiny changes * remove useless comment * migrator changes, and small things * set XDG env vars differently * free memory on error when appending environments * Fix out of bounds issue when using the Delete key Signed-off-by: AnErrupTion <anerruption@disroot.org> * Update zig-ini to fix configuration issue (#603) * Mention display-manager-init for Gentoo/OpenRC in readme.md Signed-off-by: AnErrupTion <anerruption@disroot.org> * Tidy up readme.md Signed-off-by: AnErrupTion <anerruption@disroot.org> * Fix authentication in a few edge cases (#604) * fix loginConv and auth * fix potential mem leak with configs * BIG changes --------- Signed-off-by: AnErrupTion <anerruption@disroot.org> Co-authored-by: アシュ <120780645+Kawaii-Ash@users.noreply.github.com>
This commit is contained in:
177
src/tui/TerminalBuffer.zig
Normal file
177
src/tui/TerminalBuffer.zig
Normal file
@@ -0,0 +1,177 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const interop = @import("../interop.zig");
|
||||
const utils = @import("utils.zig");
|
||||
const Config = @import("../config/Config.zig");
|
||||
|
||||
const Random = std.rand.Random;
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
const TerminalBuffer = @This();
|
||||
|
||||
random: Random,
|
||||
width: u64,
|
||||
height: u64,
|
||||
buffer: [*]termbox.tb_cell,
|
||||
fg: u8,
|
||||
bg: u8,
|
||||
border_fg: u8,
|
||||
box_chars: struct {
|
||||
left_up: u32,
|
||||
left_down: u32,
|
||||
right_up: u32,
|
||||
right_down: u32,
|
||||
top: u32,
|
||||
bottom: u32,
|
||||
left: u32,
|
||||
right: u32,
|
||||
},
|
||||
labels_max_length: u64,
|
||||
box_x: u64,
|
||||
box_y: u64,
|
||||
box_width: u64,
|
||||
box_height: u64,
|
||||
margin_box_v: u8,
|
||||
margin_box_h: u8,
|
||||
|
||||
pub fn init(config: Config, labels_max_length: u64) TerminalBuffer {
|
||||
var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp()));
|
||||
|
||||
return .{
|
||||
.random = prng.random(),
|
||||
.width = @intCast(termbox.tb_width()),
|
||||
.height = @intCast(termbox.tb_height()),
|
||||
.buffer = termbox.tb_cell_buffer(),
|
||||
.fg = config.fg,
|
||||
.bg = config.bg,
|
||||
.border_fg = config.border_fg,
|
||||
.box_chars = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) .{
|
||||
.left_up = 0x250C,
|
||||
.left_down = 0x2514,
|
||||
.right_up = 0x2510,
|
||||
.right_down = 0x2518,
|
||||
.top = 0x2500,
|
||||
.bottom = 0x2500,
|
||||
.left = 0x2502,
|
||||
.right = 0x2502,
|
||||
} else .{
|
||||
.left_up = '+',
|
||||
.left_down = '+',
|
||||
.right_up = '+',
|
||||
.right_down = '+',
|
||||
.top = '-',
|
||||
.bottom = '-',
|
||||
.left = '|',
|
||||
.right = '|',
|
||||
},
|
||||
.labels_max_length = labels_max_length,
|
||||
.box_x = 0,
|
||||
.box_y = 0,
|
||||
.box_width = (2 * config.margin_box_h) + config.input_len + 1 + labels_max_length,
|
||||
.box_height = 7 + (2 * config.margin_box_v),
|
||||
.margin_box_v = config.margin_box_v,
|
||||
.margin_box_h = config.margin_box_h,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cascade(self: TerminalBuffer) bool {
|
||||
var changes = false;
|
||||
|
||||
var y = self.height - 2;
|
||||
while (y > 0) : (y -= 1) {
|
||||
for (0..self.width) |x| {
|
||||
const c: u8 = @truncate(self.buffer[(y - 1) * self.width + x].ch);
|
||||
if (std.ascii.isWhitespace(c)) continue;
|
||||
|
||||
const c_under: u8 = @truncate(self.buffer[y * self.width + x].ch);
|
||||
if (!std.ascii.isWhitespace(c_under)) continue;
|
||||
|
||||
changes = true;
|
||||
|
||||
if ((self.random.int(u16) % 10) > 7) continue;
|
||||
|
||||
self.buffer[y * self.width + x] = self.buffer[(y - 1) * self.width + x];
|
||||
self.buffer[(y - 1) * self.width + x].ch = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
|
||||
const x1 = (self.width - self.box_width) / 2;
|
||||
const y1 = (self.height - self.box_height) / 2;
|
||||
const x2 = (self.width + self.box_width) / 2;
|
||||
const y2 = (self.height + self.box_height) / 2;
|
||||
|
||||
self.box_x = x1;
|
||||
self.box_y = y1;
|
||||
|
||||
if (show_borders) {
|
||||
termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg);
|
||||
termbox.tb_change_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg);
|
||||
termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg);
|
||||
termbox.tb_change_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg);
|
||||
|
||||
var c1 = utils.initCell(self.box_chars.top, self.border_fg, self.bg);
|
||||
var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg);
|
||||
|
||||
for (0..self.box_width) |i| {
|
||||
termbox.tb_put_cell(@intCast(x1 + i), @intCast(y1 - 1), &c1);
|
||||
termbox.tb_put_cell(@intCast(x1 + i), @intCast(y2), &c2);
|
||||
}
|
||||
|
||||
c1.ch = self.box_chars.left;
|
||||
c2.ch = self.box_chars.right;
|
||||
|
||||
for (0..self.box_height) |i| {
|
||||
termbox.tb_put_cell(@intCast(x1 - 1), @intCast(y1 + i), &c1);
|
||||
termbox.tb_put_cell(@intCast(x2), @intCast(y1 + i), &c2);
|
||||
}
|
||||
}
|
||||
|
||||
if (blank_box) {
|
||||
const blank = utils.initCell(' ', self.fg, self.bg);
|
||||
|
||||
for (0..self.box_height) |y| {
|
||||
for (0..self.box_width) |x| {
|
||||
termbox.tb_put_cell(@intCast(x1 + x), @intCast(y1 + y), &blank);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
|
||||
x: u64,
|
||||
y: u64,
|
||||
visible_length: u64,
|
||||
} {
|
||||
const x = self.box_x + self.margin_box_h + self.labels_max_length + 1;
|
||||
const y = self.box_y + self.margin_box_v;
|
||||
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
|
||||
|
||||
return .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.visible_length = visible_length,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void {
|
||||
const yc: c_int = @intCast(y);
|
||||
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
||||
var utf8 = utf8view.iterator();
|
||||
|
||||
var i = x;
|
||||
while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
|
||||
termbox.tb_change_cell(@intCast(i), yc, codepoint, self.fg, self.bg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void {
|
||||
const yc: c_int = @intCast(y);
|
||||
const cell = utils.initCell(char, self.fg, self.bg);
|
||||
|
||||
for (0..length) |xx| termbox.tb_put_cell(@intCast(x + xx), yc, &cell);
|
||||
}
|
||||
223
src/tui/components/Desktop.zig
Normal file
223
src/tui/components/Desktop.zig
Normal file
@@ -0,0 +1,223 @@
|
||||
const std = @import("std");
|
||||
const enums = @import("../../enums.zig");
|
||||
const interop = @import("../../interop.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const Ini = @import("zigini").Ini;
|
||||
const Lang = @import("../../config/Lang.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const EnvironmentList = std.ArrayList(Environment);
|
||||
|
||||
const DisplayServer = enums.DisplayServer;
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
const Desktop = @This();
|
||||
|
||||
pub const Environment = struct {
|
||||
entry_ini: ?Ini(Entry) = null,
|
||||
name: [:0]const u8 = "",
|
||||
xdg_session_desktop: [:0]const u8 = "",
|
||||
xdg_desktop_names: ?[:0]const u8 = "",
|
||||
cmd: []const u8 = "",
|
||||
specifier: []const u8 = "",
|
||||
display_server: DisplayServer = .wayland,
|
||||
};
|
||||
|
||||
const DesktopEntry = struct {
|
||||
Exec: []const u8 = "",
|
||||
Name: [:0]const u8 = "",
|
||||
DesktopNames: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const Entry = struct { @"Desktop Entry": DesktopEntry = DesktopEntry{} };
|
||||
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
environments: EnvironmentList,
|
||||
current: u64,
|
||||
visible_length: u64,
|
||||
x: u64,
|
||||
y: u64,
|
||||
lang: Lang,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64, lang: Lang) !Desktop {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.buffer = buffer,
|
||||
.environments = try EnvironmentList.initCapacity(allocator, max_length),
|
||||
.current = 0,
|
||||
.visible_length = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.lang = lang,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Desktop) void {
|
||||
for (self.environments.items) |*environment| {
|
||||
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
|
||||
if (environment.xdg_desktop_names) |desktop_name| self.allocator.free(desktop_name);
|
||||
self.allocator.free(environment.xdg_session_desktop);
|
||||
}
|
||||
|
||||
self.environments.deinit();
|
||||
}
|
||||
|
||||
pub fn position(self: *Desktop, x: u64, y: u64, visible_length: u64) void {
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
self.visible_length = visible_length;
|
||||
}
|
||||
|
||||
pub fn addEnvironment(self: *Desktop, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
|
||||
var xdg_desktop_names: ?[:0]const u8 = null;
|
||||
if (entry.DesktopNames) |desktop_names| {
|
||||
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
|
||||
for (desktop_names_z) |*c| {
|
||||
if (c.* == ';') c.* = ':';
|
||||
}
|
||||
xdg_desktop_names = desktop_names_z;
|
||||
}
|
||||
|
||||
errdefer {
|
||||
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
|
||||
}
|
||||
|
||||
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
|
||||
errdefer self.allocator.free(session_desktop);
|
||||
|
||||
try self.environments.append(.{
|
||||
.entry_ini = null,
|
||||
.name = entry.Name,
|
||||
.xdg_session_desktop = session_desktop,
|
||||
.xdg_desktop_names = xdg_desktop_names,
|
||||
.cmd = entry.Exec,
|
||||
.specifier = switch (display_server) {
|
||||
.wayland => self.lang.wayland,
|
||||
.x11 => self.lang.x11,
|
||||
else => self.lang.other,
|
||||
},
|
||||
.display_server = display_server,
|
||||
});
|
||||
|
||||
self.current = self.environments.items.len - 1;
|
||||
}
|
||||
|
||||
pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
|
||||
const entry = entry_ini.data.@"Desktop Entry";
|
||||
var xdg_desktop_names: ?[:0]const u8 = null;
|
||||
if (entry.DesktopNames) |desktop_names| {
|
||||
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
|
||||
for (desktop_names_z) |*c| {
|
||||
if (c.* == ';') c.* = ':';
|
||||
}
|
||||
xdg_desktop_names = desktop_names_z;
|
||||
}
|
||||
|
||||
errdefer {
|
||||
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
|
||||
}
|
||||
|
||||
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
|
||||
errdefer self.allocator.free(session_desktop);
|
||||
|
||||
try self.environments.append(.{
|
||||
.entry_ini = entry_ini,
|
||||
.name = entry.Name,
|
||||
.xdg_session_desktop = session_desktop,
|
||||
.xdg_desktop_names = xdg_desktop_names,
|
||||
.cmd = entry.Exec,
|
||||
.specifier = switch (display_server) {
|
||||
.wayland => self.lang.wayland,
|
||||
.x11 => self.lang.x11,
|
||||
else => self.lang.other,
|
||||
},
|
||||
.display_server = display_server,
|
||||
});
|
||||
|
||||
self.current = self.environments.items.len - 1;
|
||||
}
|
||||
|
||||
pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void {
|
||||
var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return;
|
||||
defer iterable_directory.close();
|
||||
|
||||
var iterator = iterable_directory.iterate();
|
||||
while (try iterator.next()) |item| {
|
||||
if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue;
|
||||
|
||||
const entry_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ path, item.name });
|
||||
defer self.allocator.free(entry_path);
|
||||
var entry_ini = Ini(Entry).init(self.allocator);
|
||||
_ = try entry_ini.readFileToStruct(entry_path);
|
||||
errdefer entry_ini.deinit();
|
||||
|
||||
var xdg_session_desktop: []const u8 = undefined;
|
||||
const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
|
||||
if (maybe_desktop_names) |desktop_names| {
|
||||
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
|
||||
} else {
|
||||
// if DesktopNames is empty, we'll take the name of the session file
|
||||
xdg_session_desktop = std.fs.path.stem(item.name);
|
||||
}
|
||||
|
||||
try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(self: *Desktop, 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 => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y));
|
||||
}
|
||||
|
||||
pub fn draw(self: Desktop) void {
|
||||
const environment = self.environments.items[self.current];
|
||||
|
||||
const length = @min(environment.name.len, self.visible_length - 3);
|
||||
if (length == 0) return;
|
||||
|
||||
const x = self.buffer.box_x + self.buffer.margin_box_h;
|
||||
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
|
||||
self.buffer.drawLabel(environment.specifier, x, y);
|
||||
|
||||
termbox.tb_change_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
|
||||
termbox.tb_change_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
|
||||
|
||||
self.buffer.drawLabel(environment.name, self.x + 2, self.y);
|
||||
}
|
||||
|
||||
fn goLeft(self: *Desktop) void {
|
||||
if (self.current == 0) {
|
||||
self.current = self.environments.items.len - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current -= 1;
|
||||
}
|
||||
|
||||
fn goRight(self: *Desktop) void {
|
||||
if (self.current == self.environments.items.len - 1) {
|
||||
self.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current += 1;
|
||||
}
|
||||
11
src/tui/components/InfoLine.zig
Normal file
11
src/tui/components/InfoLine.zig
Normal file
@@ -0,0 +1,11 @@
|
||||
const utils = @import("../utils.zig");
|
||||
|
||||
const InfoLine = @This();
|
||||
|
||||
text: []const u8 = "",
|
||||
width: u8 = 0,
|
||||
|
||||
pub fn setText(self: *InfoLine, text: []const u8) !void {
|
||||
self.width = if (text.len > 0) try utils.strWidth(text) else 0;
|
||||
self.text = text;
|
||||
}
|
||||
142
src/tui/components/Text.zig
Normal file
142
src/tui/components/Text.zig
Normal file
@@ -0,0 +1,142 @@
|
||||
const std = @import("std");
|
||||
const interop = @import("../../interop.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const utils = @import("../utils.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const DynamicString = std.ArrayList(u8);
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
const Text = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
text: DynamicString,
|
||||
end: u64,
|
||||
cursor: u64,
|
||||
visible_start: u64,
|
||||
visible_length: u64,
|
||||
x: u64,
|
||||
y: u64,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Text {
|
||||
const text = try DynamicString.initCapacity(allocator, max_length);
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.buffer = buffer,
|
||||
.text = text,
|
||||
.end = 0,
|
||||
.cursor = 0,
|
||||
.visible_start = 0,
|
||||
.visible_length = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Text) void {
|
||||
self.text.deinit();
|
||||
}
|
||||
|
||||
pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void {
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
self.visible_length = visible_length;
|
||||
}
|
||||
|
||||
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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
|
||||
}
|
||||
|
||||
pub fn draw(self: Text) void {
|
||||
const length = @min(self.text.items.len, self.visible_length);
|
||||
if (length == 0) return;
|
||||
|
||||
const visible_slice = if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) self.text.items[self.visible_start..(self.visible_length + self.visible_start)] else self.text.items[self.visible_start..];
|
||||
self.buffer.drawLabel(visible_slice, self.x, self.y);
|
||||
}
|
||||
|
||||
pub fn drawMasked(self: Text, mask: u8) void {
|
||||
const length = @min(self.text.items.len, self.visible_length - 1);
|
||||
if (length == 0) return;
|
||||
|
||||
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
|
||||
}
|
||||
|
||||
pub fn clear(self: *Text) void {
|
||||
self.text.clearRetainingCapacity();
|
||||
self.end = 0;
|
||||
self.cursor = 0;
|
||||
self.visible_start = 0;
|
||||
}
|
||||
|
||||
fn goLeft(self: *Text) void {
|
||||
if (self.cursor == 0) return;
|
||||
if (self.visible_start > 0) self.visible_start -= 1;
|
||||
|
||||
self.cursor -= 1;
|
||||
}
|
||||
|
||||
fn goRight(self: *Text) void {
|
||||
if (self.cursor >= self.end) return;
|
||||
if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1;
|
||||
|
||||
self.cursor += 1;
|
||||
}
|
||||
|
||||
fn delete(self: *Text) void {
|
||||
if (self.cursor >= self.end) return;
|
||||
|
||||
_ = self.text.orderedRemove(self.cursor);
|
||||
|
||||
self.end -= 1;
|
||||
}
|
||||
|
||||
fn backspace(self: *Text) void {
|
||||
if (self.cursor == 0) return;
|
||||
|
||||
self.goLeft();
|
||||
self.delete();
|
||||
}
|
||||
|
||||
fn write(self: *Text, char: u8) !void {
|
||||
if (char == 0) return;
|
||||
|
||||
try self.text.insert(self.cursor, char);
|
||||
|
||||
self.end += 1;
|
||||
self.goRight();
|
||||
}
|
||||
22
src/tui/utils.zig
Normal file
22
src/tui/utils.zig
Normal file
@@ -0,0 +1,22 @@
|
||||
const std = @import("std");
|
||||
const interop = @import("../interop.zig");
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell {
|
||||
return .{
|
||||
.ch = ch,
|
||||
.fg = fg,
|
||||
.bg = bg,
|
||||
};
|
||||
}
|
||||
|
||||
// Every codepoint is assumed to have a width of 1.
|
||||
// Since ly should be running in a tty, this should be fine.
|
||||
pub fn strWidth(str: []const u8) !u8 {
|
||||
const utf8view = try std.unicode.Utf8View.init(str);
|
||||
var utf8 = utf8view.iterator();
|
||||
var i: u8 = 0;
|
||||
while (utf8.nextCodepoint()) |_| i += 1;
|
||||
return i;
|
||||
}
|
||||
Reference in New Issue
Block a user