mirror of
https://github.com/fairyglade/ly.git
synced 2026-03-25 01:36:05 +00:00
Split UI code into ly-ui library
Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
232
ly-ui/src/components/BigLabel.zig
Normal file
232
ly-ui/src/components/BigLabel.zig
Normal file
@@ -0,0 +1,232 @@
|
||||
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,
|
||||
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
|
||||
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,
|
||||
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
|
||||
) BigLabel {
|
||||
return .{
|
||||
.allocator = null,
|
||||
.buffer = buffer,
|
||||
.text = text,
|
||||
.max_width = max_width,
|
||||
.fg = fg,
|
||||
.bg = bg,
|
||||
.locale = locale,
|
||||
.update_fn = update_fn,
|
||||
.calculate_timeout_fn = calculate_timeout_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(
|
||||
"BigLabel",
|
||||
self,
|
||||
deinit,
|
||||
null,
|
||||
draw,
|
||||
update,
|
||||
null,
|
||||
calculateTimeout,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update(self: *BigLabel, context: *anyopaque) !void {
|
||||
if (self.update_fn) |update_fn| {
|
||||
return @call(
|
||||
.auto,
|
||||
update_fn,
|
||||
.{ self, context },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculateTimeout(self: *BigLabel, ctx: *anyopaque) !?usize {
|
||||
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
|
||||
return @call(
|
||||
.auto,
|
||||
calculate_timeout_fn,
|
||||
.{ self, ctx },
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
189
ly-ui/src/components/CenteredBox.zig
Normal file
189
ly-ui/src/components/CenteredBox.zig
Normal file
@@ -0,0 +1,189 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Cell = @import("../Cell.zig");
|
||||
const Position = @import("../Position.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const Widget = @import("../Widget.zig");
|
||||
|
||||
const CenteredBox = @This();
|
||||
|
||||
buffer: *TerminalBuffer,
|
||||
horizontal_margin: usize,
|
||||
vertical_margin: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
show_borders: bool,
|
||||
blank_box: bool,
|
||||
top_title: ?[]const u8,
|
||||
bottom_title: ?[]const u8,
|
||||
border_fg: u32,
|
||||
title_fg: u32,
|
||||
bg: u32,
|
||||
update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void,
|
||||
left_pos: Position,
|
||||
right_pos: Position,
|
||||
children_pos: Position,
|
||||
|
||||
pub fn init(
|
||||
buffer: *TerminalBuffer,
|
||||
horizontal_margin: usize,
|
||||
vertical_margin: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
show_borders: bool,
|
||||
blank_box: bool,
|
||||
top_title: ?[]const u8,
|
||||
bottom_title: ?[]const u8,
|
||||
border_fg: u32,
|
||||
title_fg: u32,
|
||||
bg: u32,
|
||||
update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void,
|
||||
) CenteredBox {
|
||||
return .{
|
||||
.buffer = buffer,
|
||||
.horizontal_margin = horizontal_margin,
|
||||
.vertical_margin = vertical_margin,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.show_borders = show_borders,
|
||||
.blank_box = blank_box,
|
||||
.top_title = top_title,
|
||||
.bottom_title = bottom_title,
|
||||
.border_fg = border_fg,
|
||||
.title_fg = title_fg,
|
||||
.bg = bg,
|
||||
.update_fn = update_fn,
|
||||
.left_pos = TerminalBuffer.START_POSITION,
|
||||
.right_pos = TerminalBuffer.START_POSITION,
|
||||
.children_pos = TerminalBuffer.START_POSITION,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn widget(self: *CenteredBox) Widget {
|
||||
return Widget.init(
|
||||
"CenteredBox",
|
||||
self,
|
||||
null,
|
||||
null,
|
||||
draw,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn positionXY(self: *CenteredBox, original_pos: Position) void {
|
||||
if (self.buffer.width < 2 or self.buffer.height < 2) return;
|
||||
|
||||
self.left_pos = Position.init(
|
||||
(self.buffer.width - @min(self.buffer.width - 2, self.width)) / 2,
|
||||
(self.buffer.height - @min(self.buffer.height - 2, self.height)) / 2,
|
||||
).add(original_pos);
|
||||
|
||||
self.right_pos = Position.init(
|
||||
(self.buffer.width + @min(self.buffer.width, self.width)) / 2,
|
||||
(self.buffer.height + @min(self.buffer.height, self.height)) / 2,
|
||||
).add(original_pos);
|
||||
|
||||
self.children_pos = Position.init(
|
||||
self.left_pos.x + self.horizontal_margin,
|
||||
self.left_pos.y + self.vertical_margin,
|
||||
).add(original_pos);
|
||||
}
|
||||
|
||||
pub fn childrenPosition(self: CenteredBox) Position {
|
||||
return self.children_pos;
|
||||
}
|
||||
|
||||
fn draw(self: *CenteredBox) void {
|
||||
if (self.show_borders) {
|
||||
var left_up = Cell.init(
|
||||
self.buffer.box_chars.left_up,
|
||||
self.border_fg,
|
||||
self.bg,
|
||||
);
|
||||
var right_up = Cell.init(
|
||||
self.buffer.box_chars.right_up,
|
||||
self.border_fg,
|
||||
self.bg,
|
||||
);
|
||||
var left_down = Cell.init(
|
||||
self.buffer.box_chars.left_down,
|
||||
self.border_fg,
|
||||
self.bg,
|
||||
);
|
||||
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,
|
||||
);
|
||||
|
||||
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| {
|
||||
top.put(self.left_pos.x + i, self.left_pos.y - 1);
|
||||
bottom.put(self.left_pos.x + i, self.right_pos.y);
|
||||
}
|
||||
|
||||
top.ch = self.buffer.box_chars.left;
|
||||
bottom.ch = self.buffer.box_chars.right;
|
||||
|
||||
for (0..self.height) |i| {
|
||||
top.put(self.left_pos.x - 1, self.left_pos.y + i);
|
||||
bottom.put(self.right_pos.x, self.left_pos.y + i);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.blank_box) {
|
||||
for (0..self.height) |y| {
|
||||
for (0..self.width) |x| {
|
||||
self.buffer.blank_cell.put(self.left_pos.x + x, self.left_pos.y + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.top_title) |title| {
|
||||
TerminalBuffer.drawConfinedText(
|
||||
title,
|
||||
self.left_pos.x,
|
||||
self.left_pos.y - 1,
|
||||
self.width,
|
||||
self.title_fg,
|
||||
self.bg,
|
||||
);
|
||||
}
|
||||
|
||||
if (self.bottom_title) |title| {
|
||||
TerminalBuffer.drawConfinedText(
|
||||
title,
|
||||
self.left_pos.x,
|
||||
self.left_pos.y + self.height,
|
||||
self.width,
|
||||
self.title_fg,
|
||||
self.bg,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update(self: *CenteredBox, ctx: *anyopaque) !void {
|
||||
if (self.update_fn) |update_fn| {
|
||||
return @call(
|
||||
.auto,
|
||||
update_fn,
|
||||
.{ self, ctx },
|
||||
);
|
||||
}
|
||||
}
|
||||
110
ly-ui/src/components/InfoLine.zig
Normal file
110
ly-ui/src/components/InfoLine.zig
Normal file
@@ -0,0 +1,110 @@
|
||||
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);
|
||||
|
||||
const InfoLine = @This();
|
||||
|
||||
const Message = struct {
|
||||
width: usize,
|
||||
text: []const u8,
|
||||
bg: u32,
|
||||
fg: u32,
|
||||
};
|
||||
|
||||
label: MessageLabel,
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
width: usize,
|
||||
arrow_fg: u32,
|
||||
arrow_bg: u32,
|
||||
) InfoLine {
|
||||
return .{
|
||||
.label = MessageLabel.init(
|
||||
allocator,
|
||||
buffer,
|
||||
drawItem,
|
||||
null,
|
||||
null,
|
||||
width,
|
||||
true,
|
||||
arrow_fg,
|
||||
arrow_bg,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *InfoLine) void {
|
||||
self.label.deinit();
|
||||
}
|
||||
|
||||
pub fn widget(self: *InfoLine) Widget {
|
||||
return Widget.init(
|
||||
"InfoLine",
|
||||
self,
|
||||
deinit,
|
||||
null,
|
||||
draw,
|
||||
null,
|
||||
handle,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
|
||||
if (text.len == 0) return;
|
||||
|
||||
try self.label.addItem(.{
|
||||
.width = TerminalBuffer.strWidth(text),
|
||||
.text = text,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clearRendered(self: InfoLine, allocator: Allocator) !void {
|
||||
// Draw over the area
|
||||
const spaces = try allocator.alloc(u8, self.label.width - 2);
|
||||
defer allocator.free(spaces);
|
||||
|
||||
@memset(spaces, ' ');
|
||||
|
||||
TerminalBuffer.drawText(
|
||||
spaces,
|
||||
self.label.component_pos.x + 2,
|
||||
self.label.component_pos.y,
|
||||
TerminalBuffer.Color.DEFAULT,
|
||||
TerminalBuffer.Color.DEFAULT,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw(self: *InfoLine) void {
|
||||
self.label.draw();
|
||||
}
|
||||
|
||||
fn handle(self: *InfoLine, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
|
||||
self.label.handle(maybe_key, insert_mode);
|
||||
}
|
||||
|
||||
fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void {
|
||||
if (message.width == 0) return;
|
||||
|
||||
const x_offset = if (label.text_in_center and width >= message.width) (width - message.width) / 2 else 0;
|
||||
|
||||
label.cursor = message.width + x_offset;
|
||||
TerminalBuffer.drawConfinedText(
|
||||
message.text,
|
||||
x + x_offset,
|
||||
y,
|
||||
width,
|
||||
message.fg,
|
||||
message.bg,
|
||||
);
|
||||
}
|
||||
148
ly-ui/src/components/Label.zig
Normal file
148
ly-ui/src/components/Label.zig
Normal file
@@ -0,0 +1,148 @@
|
||||
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,
|
||||
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
|
||||
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,
|
||||
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
|
||||
) Label {
|
||||
return .{
|
||||
.allocator = null,
|
||||
.text = text,
|
||||
.max_width = max_width,
|
||||
.fg = fg,
|
||||
.bg = bg,
|
||||
.update_fn = update_fn,
|
||||
.calculate_timeout_fn = calculate_timeout_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(
|
||||
"Label",
|
||||
self,
|
||||
deinit,
|
||||
null,
|
||||
draw,
|
||||
update,
|
||||
null,
|
||||
calculateTimeout,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
fn update(self: *Label, ctx: *anyopaque) !void {
|
||||
if (self.update_fn) |update_fn| {
|
||||
return @call(
|
||||
.auto,
|
||||
update_fn,
|
||||
.{ self, ctx },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculateTimeout(self: *Label, ctx: *anyopaque) !?usize {
|
||||
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
|
||||
return @call(
|
||||
.auto,
|
||||
calculate_timeout_fn,
|
||||
.{ self, ctx },
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
117
ly-ui/src/components/Session.zig
Normal file
117
ly-ui/src/components/Session.zig
Normal file
@@ -0,0 +1,117 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const ly_core = @import("ly-core");
|
||||
const Environment = ly_core.Environment;
|
||||
|
||||
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");
|
||||
|
||||
const Env = struct {
|
||||
// TODO: Remove dependency on Environment
|
||||
environment: Environment,
|
||||
index: usize,
|
||||
};
|
||||
const EnvironmentLabel = generic.CyclableLabel(Env, *UserList);
|
||||
|
||||
const Session = @This();
|
||||
|
||||
label: EnvironmentLabel,
|
||||
user_list: *UserList,
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
user_list: *UserList,
|
||||
width: usize,
|
||||
text_in_center: bool,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
) Session {
|
||||
return .{
|
||||
.label = EnvironmentLabel.init(
|
||||
allocator,
|
||||
buffer,
|
||||
drawItem,
|
||||
sessionChanged,
|
||||
user_list,
|
||||
width,
|
||||
text_in_center,
|
||||
fg,
|
||||
bg,
|
||||
),
|
||||
.user_list = user_list,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Session) void {
|
||||
for (self.label.list.items) |*env| {
|
||||
if (env.environment.entry_ini) |*entry_ini| entry_ini.deinit();
|
||||
self.label.allocator.free(env.environment.file_name);
|
||||
}
|
||||
|
||||
self.label.deinit();
|
||||
}
|
||||
|
||||
pub fn widget(self: *Session) Widget {
|
||||
return Widget.init(
|
||||
"Session",
|
||||
self,
|
||||
deinit,
|
||||
null,
|
||||
draw,
|
||||
null,
|
||||
handle,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn addEnvironment(self: *Session, environment: Environment) !void {
|
||||
const env = Env{ .environment = environment, .index = self.label.list.items.len };
|
||||
|
||||
try self.label.addItem(env);
|
||||
addedSession(env, self.user_list);
|
||||
}
|
||||
|
||||
fn draw(self: *Session) void {
|
||||
self.label.draw();
|
||||
}
|
||||
|
||||
fn handle(self: *Session, maybe_key: ?keyboard.Key, insert_mode: bool) !void {
|
||||
self.label.handle(maybe_key, insert_mode);
|
||||
}
|
||||
|
||||
fn addedSession(env: Env, user_list: *UserList) void {
|
||||
const user = user_list.label.list.items[user_list.label.current];
|
||||
if (!user.first_run) return;
|
||||
|
||||
user.session_index.* = env.index;
|
||||
}
|
||||
|
||||
fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void {
|
||||
if (maybe_user_list) |user_list| {
|
||||
user_list.label.list.items[user_list.label.current].session_index.* = env.index;
|
||||
}
|
||||
}
|
||||
|
||||
fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize, width: usize) void {
|
||||
if (width < 3) return;
|
||||
|
||||
const length = @min(TerminalBuffer.strWidth(env.environment.name), width - 3);
|
||||
if (length == 0) return;
|
||||
|
||||
const x_offset = if (label.text_in_center and width >= length) (width - length) / 2 else 0;
|
||||
|
||||
label.cursor = length + x_offset;
|
||||
TerminalBuffer.drawConfinedText(
|
||||
env.environment.name,
|
||||
x + x_offset,
|
||||
y,
|
||||
width,
|
||||
label.fg,
|
||||
label.bg,
|
||||
);
|
||||
}
|
||||
210
ly-ui/src/components/Text.zig
Normal file
210
ly-ui/src/components/Text.zig
Normal file
@@ -0,0 +1,210 @@
|
||||
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 Widget = @import("../Widget.zig");
|
||||
|
||||
const DynamicString = std.ArrayListUnmanaged(u8);
|
||||
|
||||
const Text = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
text: DynamicString,
|
||||
end: usize,
|
||||
cursor: usize,
|
||||
visible_start: usize,
|
||||
width: usize,
|
||||
component_pos: Position,
|
||||
children_pos: Position,
|
||||
masked: bool,
|
||||
maybe_mask: ?u32,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
masked: bool,
|
||||
maybe_mask: ?u32,
|
||||
width: usize,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
) Text {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.buffer = buffer,
|
||||
.text = .empty,
|
||||
.end = 0,
|
||||
.cursor = 0,
|
||||
.visible_start = 0,
|
||||
.width = width,
|
||||
.component_pos = TerminalBuffer.START_POSITION,
|
||||
.children_pos = TerminalBuffer.START_POSITION,
|
||||
.masked = masked,
|
||||
.maybe_mask = maybe_mask,
|
||||
.fg = fg,
|
||||
.bg = bg,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Text) void {
|
||||
self.text.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn widget(self: *Text) Widget {
|
||||
return Widget.init(
|
||||
"Text",
|
||||
self,
|
||||
deinit,
|
||||
null,
|
||||
draw,
|
||||
null,
|
||||
handle,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn positionX(self: *Text, original_pos: Position) void {
|
||||
self.component_pos = original_pos;
|
||||
self.children_pos = original_pos.addX(self.width);
|
||||
}
|
||||
|
||||
pub fn positionY(self: *Text, original_pos: Position) void {
|
||||
self.component_pos = original_pos;
|
||||
self.children_pos = original_pos.addY(1);
|
||||
}
|
||||
|
||||
pub fn positionXY(self: *Text, original_pos: Position) void {
|
||||
self.component_pos = original_pos;
|
||||
self.children_pos = Position.init(
|
||||
self.width,
|
||||
1,
|
||||
).add(original_pos);
|
||||
}
|
||||
|
||||
pub fn childrenPosition(self: Text) Position {
|
||||
return self.children_pos;
|
||||
}
|
||||
|
||||
pub fn clear(self: *Text) void {
|
||||
self.text.clearRetainingCapacity();
|
||||
self.end = 0;
|
||||
self.cursor = 0;
|
||||
self.visible_start = 0;
|
||||
}
|
||||
|
||||
pub fn toggleMask(self: *Text) void {
|
||||
self.masked = !self.masked;
|
||||
}
|
||||
|
||||
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) {
|
||||
TerminalBuffer.setCursor(
|
||||
self.component_pos.x,
|
||||
self.component_pos.y,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
TerminalBuffer.setCursor(
|
||||
self.component_pos.x + (self.cursor - self.visible_start),
|
||||
self.component_pos.y,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw(self: *Text) void {
|
||||
if (self.masked) {
|
||||
if (self.maybe_mask) |mask| {
|
||||
if (self.width < 1) return;
|
||||
|
||||
const length = @min(TerminalBuffer.strWidth(self.text.items), self.width - 1);
|
||||
if (length == 0) return;
|
||||
|
||||
TerminalBuffer.drawCharMultiple(
|
||||
mask,
|
||||
self.component_pos.x,
|
||||
self.component_pos.y,
|
||||
length,
|
||||
self.fg,
|
||||
self.bg,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const str_length = TerminalBuffer.strWidth(self.text.items);
|
||||
const length = @min(str_length, self.width);
|
||||
if (length == 0) return;
|
||||
|
||||
const visible_slice = vs: {
|
||||
if (str_length > self.width and self.cursor < str_length) {
|
||||
break :vs self.text.items[self.visible_start..(self.width + self.visible_start)];
|
||||
} else {
|
||||
break :vs self.text.items[self.visible_start..];
|
||||
}
|
||||
};
|
||||
|
||||
TerminalBuffer.drawText(
|
||||
visible_slice,
|
||||
self.component_pos.x,
|
||||
self.component_pos.y,
|
||||
self.fg,
|
||||
self.bg,
|
||||
);
|
||||
}
|
||||
|
||||
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.width - 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.allocator, self.cursor, char);
|
||||
|
||||
self.end += 1;
|
||||
self.goRight();
|
||||
}
|
||||
141
ly-ui/src/components/UserList.zig
Normal file
141
ly-ui/src/components/UserList.zig
Normal file
@@ -0,0 +1,141 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const ly_core = @import("ly-core");
|
||||
const SavedUsers = ly_core.SavedUsers;
|
||||
|
||||
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");
|
||||
|
||||
const StringList = std.ArrayListUnmanaged([]const u8);
|
||||
pub const User = struct {
|
||||
name: []const u8,
|
||||
session_index: *usize,
|
||||
allocated_index: bool,
|
||||
first_run: bool,
|
||||
};
|
||||
const UserLabel = generic.CyclableLabel(User, *Session);
|
||||
|
||||
const UserList = @This();
|
||||
|
||||
label: UserLabel,
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
usernames: StringList,
|
||||
// TODO: Remove dependency on SavedUsers
|
||||
saved_users: *SavedUsers,
|
||||
session: *Session,
|
||||
width: usize,
|
||||
text_in_center: bool,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
) !UserList {
|
||||
var user_list = UserList{
|
||||
.label = UserLabel.init(
|
||||
allocator,
|
||||
buffer,
|
||||
drawItem,
|
||||
usernameChanged,
|
||||
session,
|
||||
width,
|
||||
text_in_center,
|
||||
fg,
|
||||
bg,
|
||||
),
|
||||
};
|
||||
|
||||
for (usernames.items) |username| {
|
||||
if (username.len == 0) continue;
|
||||
|
||||
var maybe_session_index: ?*usize = null;
|
||||
var first_run = true;
|
||||
for (saved_users.user_list.items) |*saved_user| {
|
||||
if (std.mem.eql(u8, username, saved_user.username)) {
|
||||
maybe_session_index = &saved_user.session_index;
|
||||
first_run = saved_user.first_run;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var allocated_index = false;
|
||||
if (maybe_session_index == null) {
|
||||
maybe_session_index = try allocator.create(usize);
|
||||
maybe_session_index.?.* = 0;
|
||||
allocated_index = true;
|
||||
}
|
||||
|
||||
try user_list.label.addItem(.{
|
||||
.name = username,
|
||||
.session_index = maybe_session_index.?,
|
||||
.allocated_index = allocated_index,
|
||||
.first_run = first_run,
|
||||
});
|
||||
}
|
||||
|
||||
return user_list;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *UserList) void {
|
||||
for (self.label.list.items) |user| {
|
||||
if (user.allocated_index) {
|
||||
self.label.allocator.destroy(user.session_index);
|
||||
}
|
||||
}
|
||||
|
||||
self.label.deinit();
|
||||
}
|
||||
|
||||
pub fn widget(self: *UserList) Widget {
|
||||
return Widget.init(
|
||||
"UserList",
|
||||
self,
|
||||
deinit,
|
||||
null,
|
||||
draw,
|
||||
null,
|
||||
handle,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn drawItem(label: *UserLabel, user: User, x: usize, y: usize, width: usize) void {
|
||||
if (width < 3) return;
|
||||
|
||||
const length = @min(TerminalBuffer.strWidth(user.name), width - 3);
|
||||
if (length == 0) return;
|
||||
|
||||
const x_offset = if (label.text_in_center and width >= length) (width - length) / 2 else 0;
|
||||
|
||||
label.cursor = length + x_offset;
|
||||
TerminalBuffer.drawConfinedText(
|
||||
user.name,
|
||||
x + x_offset,
|
||||
y,
|
||||
width,
|
||||
label.fg,
|
||||
label.bg,
|
||||
);
|
||||
}
|
||||
114
ly-ui/src/components/bigLabelLocales/en.zig
Normal file
114
ly-ui/src/components/bigLabelLocales/en.zig
Normal file
@@ -0,0 +1,114 @@
|
||||
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{
|
||||
.ZERO = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.ONE = [_]u21{
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
},
|
||||
.TWO = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.THREE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.FOUR = [_]u21{
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
},
|
||||
.FIVE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.SIX = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.SEVEN = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
},
|
||||
.EIGHT = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.NINE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.S = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.E = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.P = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,O,O,O,
|
||||
},
|
||||
.A = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
},
|
||||
.M = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,O,X,O,X,
|
||||
X,O,X,O,X,
|
||||
X,O,O,O,X,
|
||||
X,O,O,O,X,
|
||||
},
|
||||
};
|
||||
// zig fmt: on
|
||||
114
ly-ui/src/components/bigLabelLocales/fa.zig
Normal file
114
ly-ui/src/components/bigLabelLocales/fa.zig
Normal file
@@ -0,0 +1,114 @@
|
||||
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{
|
||||
.ZERO = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,X,O,X,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.ONE = [_]u21{
|
||||
O,O,X,O,O,
|
||||
O,X,X,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,X,O,O,
|
||||
},
|
||||
.TWO = [_]u21{
|
||||
O,X,O,X,O,
|
||||
O,X,X,X,O,
|
||||
O,X,O,O,O,
|
||||
O,X,O,O,O,
|
||||
O,X,O,O,O,
|
||||
},
|
||||
.THREE = [_]u21{
|
||||
X,O,X,O,X,
|
||||
X,X,X,X,X,
|
||||
X,O,O,O,O,
|
||||
X,O,O,O,O,
|
||||
X,O,O,O,O,
|
||||
},
|
||||
.FOUR = [_]u21{
|
||||
O,X,O,X,X,
|
||||
O,X,X,O,O,
|
||||
O,X,X,X,X,
|
||||
O,X,O,O,O,
|
||||
O,X,O,O,O,
|
||||
},
|
||||
.FIVE = [_]u21{
|
||||
O,O,X,X,O,
|
||||
O,X,O,O,X,
|
||||
X,O,O,O,X,
|
||||
X,O,X,O,X,
|
||||
O,X,O,X,O,
|
||||
},
|
||||
.SIX = [_]u21{
|
||||
O,X,X,O,O,
|
||||
O,X,O,O,X,
|
||||
O,O,X,O,O,
|
||||
O,X,O,O,O,
|
||||
X,O,O,O,O,
|
||||
},
|
||||
.SEVEN = [_]u21{
|
||||
X,O,O,O,X,
|
||||
X,O,O,O,X,
|
||||
O,X,O,X,O,
|
||||
O,X,O,X,O,
|
||||
O,O,X,O,O,
|
||||
},
|
||||
.EIGHT = [_]u21{
|
||||
O,O,O,X,O,
|
||||
O,O,X,O,X,
|
||||
O,O,X,O,X,
|
||||
O,X,O,O,X,
|
||||
O,X,O,O,X,
|
||||
},
|
||||
.NINE = [_]u21{
|
||||
O,X,X,X,O,
|
||||
O,X,O,X,O,
|
||||
O,X,X,X,O,
|
||||
O,O,O,X,O,
|
||||
O,O,O,X,O,
|
||||
},
|
||||
.P = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.A = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.M = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.S = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.E = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
};
|
||||
// zig fmt: on
|
||||
159
ly-ui/src/components/generic.zig
Normal file
159
ly-ui/src/components/generic.zig
Normal file
@@ -0,0 +1,159 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Cell = @import("../Cell.zig");
|
||||
const keyboard = @import("../keyboard.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const Position = @import("../Position.zig");
|
||||
|
||||
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
|
||||
return struct {
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ItemList = std.ArrayListUnmanaged(ItemType);
|
||||
const DrawItemFn = *const fn (*Self, ItemType, usize, usize, usize) void;
|
||||
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
list: ItemList,
|
||||
current: usize,
|
||||
width: usize,
|
||||
component_pos: Position,
|
||||
children_pos: Position,
|
||||
text_in_center: bool,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
cursor: usize,
|
||||
draw_item_fn: DrawItemFn,
|
||||
change_item_fn: ?ChangeItemFn,
|
||||
change_item_arg: ?ChangeItemType,
|
||||
|
||||
pub fn init(
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
draw_item_fn: DrawItemFn,
|
||||
change_item_fn: ?ChangeItemFn,
|
||||
change_item_arg: ?ChangeItemType,
|
||||
width: usize,
|
||||
text_in_center: bool,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.buffer = buffer,
|
||||
.list = .empty,
|
||||
.current = 0,
|
||||
.width = width,
|
||||
.component_pos = TerminalBuffer.START_POSITION,
|
||||
.children_pos = TerminalBuffer.START_POSITION,
|
||||
.text_in_center = text_in_center,
|
||||
.fg = fg,
|
||||
.bg = bg,
|
||||
.cursor = 0,
|
||||
.draw_item_fn = draw_item_fn,
|
||||
.change_item_fn = change_item_fn,
|
||||
.change_item_arg = change_item_arg,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.list.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn positionX(self: *Self, original_pos: Position) void {
|
||||
self.component_pos = original_pos;
|
||||
self.cursor = self.component_pos.x + 2;
|
||||
self.children_pos = original_pos.addX(self.width);
|
||||
}
|
||||
|
||||
pub fn positionY(self: *Self, original_pos: Position) void {
|
||||
self.component_pos = original_pos;
|
||||
self.cursor = self.component_pos.x + 2;
|
||||
self.children_pos = original_pos.addY(1);
|
||||
}
|
||||
|
||||
pub fn positionXY(self: *Self, original_pos: Position) void {
|
||||
self.component_pos = original_pos;
|
||||
self.cursor = self.component_pos.x + 2;
|
||||
self.children_pos = Position.init(
|
||||
self.width,
|
||||
1,
|
||||
).add(original_pos);
|
||||
}
|
||||
|
||||
pub fn childrenPosition(self: Self) Position {
|
||||
return self.children_pos;
|
||||
}
|
||||
|
||||
pub fn addItem(self: *Self, item: ItemType) !void {
|
||||
try self.list.append(self.allocator, item);
|
||||
self.current = self.list.items.len - 1;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
TerminalBuffer.setCursor(
|
||||
self.component_pos.x + self.cursor + 2,
|
||||
self.component_pos.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn draw(self: *Self) void {
|
||||
if (self.list.items.len == 0) return;
|
||||
if (self.width < 2) return;
|
||||
|
||||
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];
|
||||
const x = self.component_pos.x + 2;
|
||||
const y = self.component_pos.y;
|
||||
const width = self.width - 2;
|
||||
|
||||
@call(
|
||||
.auto,
|
||||
self.draw_item_fn,
|
||||
.{ self, current_item, x, y, width },
|
||||
);
|
||||
}
|
||||
|
||||
fn goLeft(self: *Self) void {
|
||||
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
|
||||
|
||||
if (self.change_item_fn) |change_item_fn| {
|
||||
@call(
|
||||
.auto,
|
||||
change_item_fn,
|
||||
.{ self.list.items[self.current], self.change_item_arg },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn goRight(self: *Self) void {
|
||||
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
|
||||
|
||||
if (self.change_item_fn) |change_item_fn| {
|
||||
@call(
|
||||
.auto,
|
||||
change_item_fn,
|
||||
.{ self.list.items[self.current], self.change_item_arg },
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user