mirror of
https://github.com/fairyglade/ly.git
synced 2026-03-25 09:46:06 +00:00
Completely refactor widget placement code
Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
133
src/main.zig
133
src/main.zig
@@ -30,6 +30,7 @@ const DisplayServer = enums.DisplayServer;
|
|||||||
const Environment = @import("Environment.zig");
|
const Environment = @import("Environment.zig");
|
||||||
const Entry = Environment.Entry;
|
const Entry = Environment.Entry;
|
||||||
const Animation = @import("tui/Animation.zig");
|
const Animation = @import("tui/Animation.zig");
|
||||||
|
const CenteredBox = @import("tui/components/CenteredBox.zig");
|
||||||
const InfoLine = @import("tui/components/InfoLine.zig");
|
const InfoLine = @import("tui/components/InfoLine.zig");
|
||||||
const Session = @import("tui/components/Session.zig");
|
const Session = @import("tui/components/Session.zig");
|
||||||
const Text = @import("tui/components/Text.zig");
|
const Text = @import("tui/components/Text.zig");
|
||||||
@@ -63,9 +64,11 @@ const UiState = struct {
|
|||||||
auth_fails: u64,
|
auth_fails: u64,
|
||||||
update: bool,
|
update: bool,
|
||||||
buffer: *TerminalBuffer,
|
buffer: *TerminalBuffer,
|
||||||
|
labels_max_length: usize,
|
||||||
animation_timed_out: bool,
|
animation_timed_out: bool,
|
||||||
animation: *?Animation,
|
animation: *?Animation,
|
||||||
can_draw_battery: bool,
|
can_draw_battery: bool,
|
||||||
|
box: *CenteredBox,
|
||||||
info_line: *InfoLine,
|
info_line: *InfoLine,
|
||||||
animate: bool,
|
animate: bool,
|
||||||
resolution_changed: bool,
|
resolution_changed: bool,
|
||||||
@@ -300,11 +303,7 @@ pub fn main() !void {
|
|||||||
.fg = config.fg,
|
.fg = config.fg,
|
||||||
.bg = config.bg,
|
.bg = config.bg,
|
||||||
.border_fg = config.border_fg,
|
.border_fg = config.border_fg,
|
||||||
.margin_box_h = config.margin_box_h,
|
|
||||||
.margin_box_v = config.margin_box_v,
|
|
||||||
.input_len = config.input_len,
|
|
||||||
.full_color = config.full_color,
|
.full_color = config.full_color,
|
||||||
.labels_max_length = labels_max_length,
|
|
||||||
.is_tty = true,
|
.is_tty = true,
|
||||||
};
|
};
|
||||||
var buffer = try TerminalBuffer.init(buffer_options, &log_file, random);
|
var buffer = try TerminalBuffer.init(buffer_options, &log_file, random);
|
||||||
@@ -321,7 +320,23 @@ pub fn main() !void {
|
|||||||
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
||||||
|
|
||||||
// Initialize components
|
// Initialize components
|
||||||
var info_line = InfoLine.init(allocator, &buffer);
|
var box = CenteredBox.init(
|
||||||
|
&buffer,
|
||||||
|
config.margin_box_h,
|
||||||
|
config.margin_box_v,
|
||||||
|
(2 * config.margin_box_h) + config.input_len + 1 + labels_max_length,
|
||||||
|
7 + (2 * config.margin_box_v),
|
||||||
|
!config.hide_borders,
|
||||||
|
config.blank_box,
|
||||||
|
config.box_title,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
var info_line = InfoLine.init(
|
||||||
|
allocator,
|
||||||
|
&buffer,
|
||||||
|
box.width - 2 * box.horizontal_margin,
|
||||||
|
);
|
||||||
defer info_line.deinit();
|
defer info_line.deinit();
|
||||||
|
|
||||||
if (maybe_res == null) {
|
if (maybe_res == null) {
|
||||||
@@ -365,10 +380,24 @@ pub fn main() !void {
|
|||||||
|
|
||||||
var login: UserList = undefined;
|
var login: UserList = undefined;
|
||||||
|
|
||||||
var session = Session.init(allocator, &buffer, &login);
|
var session = Session.init(
|
||||||
|
allocator,
|
||||||
|
&buffer,
|
||||||
|
&login,
|
||||||
|
box.width - 2 * box.horizontal_margin - labels_max_length - 1,
|
||||||
|
config.text_in_center,
|
||||||
|
);
|
||||||
defer session.deinit();
|
defer session.deinit();
|
||||||
|
|
||||||
login = try UserList.init(allocator, &buffer, usernames, &saved_users, &session);
|
login = try UserList.init(
|
||||||
|
allocator,
|
||||||
|
&buffer,
|
||||||
|
usernames,
|
||||||
|
&saved_users,
|
||||||
|
&session,
|
||||||
|
box.width - 2 * box.horizontal_margin - labels_max_length - 1,
|
||||||
|
config.text_in_center,
|
||||||
|
);
|
||||||
defer login.deinit();
|
defer login.deinit();
|
||||||
|
|
||||||
addOtherEnvironment(&session, lang, .shell, null) catch |err| {
|
addOtherEnvironment(&session, lang, .shell, null) catch |err| {
|
||||||
@@ -430,7 +459,13 @@ pub fn main() !void {
|
|||||||
try log_file.err("sys", "no users found", .{});
|
try log_file.err("sys", "no users found", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
var password = Text.init(allocator, &buffer, true, config.asterisk);
|
var password = Text.init(
|
||||||
|
allocator,
|
||||||
|
&buffer,
|
||||||
|
true,
|
||||||
|
config.asterisk,
|
||||||
|
box.width - 2 * box.horizontal_margin - labels_max_length - 1,
|
||||||
|
);
|
||||||
defer password.deinit();
|
defer password.deinit();
|
||||||
|
|
||||||
var is_autologin = false;
|
var is_autologin = false;
|
||||||
@@ -467,9 +502,11 @@ pub fn main() !void {
|
|||||||
.auth_fails = 0,
|
.auth_fails = 0,
|
||||||
.update = true,
|
.update = true,
|
||||||
.buffer = &buffer,
|
.buffer = &buffer,
|
||||||
|
.labels_max_length = labels_max_length,
|
||||||
.animation_timed_out = false,
|
.animation_timed_out = false,
|
||||||
.animation = &animation,
|
.animation = &animation,
|
||||||
.can_draw_battery = true,
|
.can_draw_battery = true,
|
||||||
|
.box = &box,
|
||||||
.info_line = &info_line,
|
.info_line = &info_line,
|
||||||
.animate = config.animation != .none,
|
.animate = config.animation != .none,
|
||||||
.resolution_changed = false,
|
.resolution_changed = false,
|
||||||
@@ -512,25 +549,21 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place components on the screen
|
// Position components
|
||||||
{
|
state.box.position(TerminalBuffer.START_POSITION);
|
||||||
buffer.drawBoxCenter(!config.hide_borders, config.blank_box);
|
state.info_line.label.positionY(state.box.childrenPosition());
|
||||||
|
state.session.label.positionY(state.info_line.label.childrenPosition().addY(1).addX(state.labels_max_length + 1));
|
||||||
|
state.login.label.positionY(state.session.label.childrenPosition().addY(1));
|
||||||
|
state.password.positionY(state.login.label.childrenPosition().addY(1));
|
||||||
|
|
||||||
const coordinates = buffer.calculateComponentCoordinates();
|
switch (state.active_input) {
|
||||||
info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null);
|
.info_line => info_line.label.handle(null, state.insert_mode),
|
||||||
session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center);
|
.session => session.label.handle(null, state.insert_mode),
|
||||||
login.label.position(coordinates.x, coordinates.y + 4, coordinates.visible_length, config.text_in_center);
|
.login => login.label.handle(null, state.insert_mode),
|
||||||
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
|
.password => password.handle(null, state.insert_mode) catch |err| {
|
||||||
|
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||||
switch (state.active_input) {
|
try log_file.err("tui", "failed to handle password input: {s}", .{@errorName(err)});
|
||||||
.info_line => info_line.label.handle(null, state.insert_mode),
|
},
|
||||||
.session => session.label.handle(null, state.insert_mode),
|
|
||||||
.login => login.label.handle(null, state.insert_mode),
|
|
||||||
.password => password.handle(null, state.insert_mode) catch |err| {
|
|
||||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
|
||||||
try log_file.err("tui", "failed to handle password input: {s}", .{@errorName(err)});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the animation, if any
|
// Initialize the animation, if any
|
||||||
@@ -782,7 +815,7 @@ pub fn main() !void {
|
|||||||
if (!config.allow_empty_password and password.text.items.len == 0) {
|
if (!config.allow_empty_password and password.text.items.len == 0) {
|
||||||
// Let's not log this message for security reasons
|
// Let's not log this message for security reasons
|
||||||
try info_line.addMessage(lang.err_empty_password, config.error_bg, config.error_fg);
|
try info_line.addMessage(lang.err_empty_password, config.error_bg, config.error_fg);
|
||||||
InfoLine.clearRendered(allocator, buffer) catch |err| {
|
info_line.clearRendered(allocator) catch |err| {
|
||||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||||
try log_file.err("tui", "failed to clear info line: {s}", .{@errorName(err)});
|
try log_file.err("tui", "failed to clear info line: {s}", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
@@ -792,7 +825,7 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try info_line.addMessage(lang.authenticating, config.bg, config.fg);
|
try info_line.addMessage(lang.authenticating, config.bg, config.fg);
|
||||||
InfoLine.clearRendered(allocator, buffer) catch |err| {
|
info_line.clearRendered(allocator) catch |err| {
|
||||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||||
try log_file.err("tui", "failed to clear info line: {s}", .{@errorName(err)});
|
try log_file.err("tui", "failed to clear info line: {s}", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
@@ -1002,7 +1035,7 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool
|
|||||||
state.can_draw_battery = true;
|
state.can_draw_battery = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.bigclock != .none and state.buffer.box_height + (bigclock.HEIGHT + 2) * 2 < state.buffer.height) {
|
if (config.bigclock != .none and state.box.height + (bigclock.HEIGHT + 2) * 2 < state.buffer.height) {
|
||||||
var format_buf: [16:0]u8 = undefined;
|
var format_buf: [16:0]u8 = undefined;
|
||||||
var clock_buf: [32:0]u8 = undefined;
|
var clock_buf: [32:0]u8 = undefined;
|
||||||
// We need the slice/c-string returned by `bufPrintZ`.
|
// We need the slice/c-string returned by `bufPrintZ`.
|
||||||
@@ -1013,7 +1046,7 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool
|
|||||||
if (config.bigclock_12hr) "%P" else "",
|
if (config.bigclock_12hr) "%P" else "",
|
||||||
});
|
});
|
||||||
const xo = state.buffer.width / 2 - @min(state.buffer.width, (format.len * (bigclock.WIDTH + 1))) / 2;
|
const xo = state.buffer.width / 2 - @min(state.buffer.width, (format.len * (bigclock.WIDTH + 1))) / 2;
|
||||||
const yo = (state.buffer.height - state.buffer.box_height) / 2 - bigclock.HEIGHT - 2;
|
const yo = (state.buffer.height - state.box.height) / 2 - bigclock.HEIGHT - 2;
|
||||||
|
|
||||||
const clock_str = interop.timeAsString(&clock_buf, format);
|
const clock_str = interop.timeAsString(&clock_buf, format);
|
||||||
|
|
||||||
@@ -1024,14 +1057,14 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.buffer.drawBoxCenter(!config.hide_borders, config.blank_box);
|
state.box.draw();
|
||||||
|
|
||||||
if (state.resolution_changed) {
|
if (state.resolution_changed) {
|
||||||
const coordinates = state.buffer.calculateComponentCoordinates();
|
state.box.position(TerminalBuffer.START_POSITION);
|
||||||
state.info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null);
|
state.info_line.label.positionY(state.box.childrenPosition());
|
||||||
state.session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center);
|
state.session.label.positionY(state.info_line.label.childrenPosition().addY(1).addX(state.labels_max_length + 1));
|
||||||
state.login.label.position(coordinates.x, coordinates.y + 4, coordinates.visible_length, config.text_in_center);
|
state.login.label.positionY(state.session.label.childrenPosition().addY(1));
|
||||||
state.password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
|
state.password.positionY(state.login.label.childrenPosition().addY(1));
|
||||||
|
|
||||||
state.resolution_changed = false;
|
state.resolution_changed = false;
|
||||||
}
|
}
|
||||||
@@ -1062,11 +1095,22 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool
|
|||||||
state.buffer.drawLabel(clock_str, state.buffer.width - @min(state.buffer.width, clock_str.len) - config.edge_margin, config.edge_margin);
|
state.buffer.drawLabel(clock_str, state.buffer.width - @min(state.buffer.width, clock_str.len) - config.edge_margin, config.edge_margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
const label_x = state.buffer.box_x + state.buffer.margin_box_h;
|
const env = state.session.label.list.items[state.session.label.current];
|
||||||
const label_y = state.buffer.box_y + state.buffer.margin_box_v;
|
state.buffer.drawLabel(
|
||||||
|
env.environment.specifier,
|
||||||
state.buffer.drawLabel(lang.login, label_x, label_y + 4);
|
state.box.childrenPosition().x,
|
||||||
state.buffer.drawLabel(lang.password, label_x, label_y + 6);
|
state.session.label.component_pos.y,
|
||||||
|
);
|
||||||
|
state.buffer.drawLabel(
|
||||||
|
lang.login,
|
||||||
|
state.box.childrenPosition().x,
|
||||||
|
state.login.label.component_pos.y,
|
||||||
|
);
|
||||||
|
state.buffer.drawLabel(
|
||||||
|
lang.password,
|
||||||
|
state.box.childrenPosition().x,
|
||||||
|
state.password.component_pos.y,
|
||||||
|
);
|
||||||
|
|
||||||
state.info_line.label.draw();
|
state.info_line.label.draw();
|
||||||
|
|
||||||
@@ -1122,13 +1166,8 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.box_title) |title| {
|
|
||||||
state.buffer.drawConfinedLabel(title, state.buffer.box_x, state.buffer.box_y - 1, state.buffer.box_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.vi_mode) {
|
if (config.vi_mode) {
|
||||||
const label_txt = if (state.insert_mode) lang.insert else lang.normal;
|
state.box.bottom_title = if (state.insert_mode) lang.insert else lang.normal;
|
||||||
state.buffer.drawLabel(label_txt, state.buffer.box_x, state.buffer.box_y + state.buffer.box_height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.hide_keyboard_locks and state.can_get_lock_state) draw_lock_state: {
|
if (!config.hide_keyboard_locks and state.can_get_lock_state) draw_lock_state: {
|
||||||
|
|||||||
32
src/tui/Position.zig
Normal file
32
src/tui/Position.zig
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const Position = @This();
|
||||||
|
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
|
||||||
|
pub fn init(x: usize, y: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + other.x,
|
||||||
|
.y = self.y + other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addX(self: Position, x: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addY(self: Position, y: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y + y,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ const LogFile = ly_core.LogFile;
|
|||||||
pub const termbox = @import("termbox2");
|
pub const termbox = @import("termbox2");
|
||||||
|
|
||||||
const Cell = @import("Cell.zig");
|
const Cell = @import("Cell.zig");
|
||||||
|
const Position = @import("Position.zig");
|
||||||
|
|
||||||
const TerminalBuffer = @This();
|
const TerminalBuffer = @This();
|
||||||
|
|
||||||
@@ -14,11 +15,7 @@ pub const InitOptions = struct {
|
|||||||
fg: u32,
|
fg: u32,
|
||||||
bg: u32,
|
bg: u32,
|
||||||
border_fg: u32,
|
border_fg: u32,
|
||||||
margin_box_h: u8,
|
|
||||||
margin_box_v: u8,
|
|
||||||
input_len: u8,
|
|
||||||
full_color: bool,
|
full_color: bool,
|
||||||
labels_max_length: usize,
|
|
||||||
is_tty: bool,
|
is_tty: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +57,8 @@ pub const Color = struct {
|
|||||||
pub const ECOL_WHITE = 8;
|
pub const ECOL_WHITE = 8;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const START_POSITION = Position.init(0, 0);
|
||||||
|
|
||||||
random: Random,
|
random: Random,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
@@ -76,13 +75,6 @@ box_chars: struct {
|
|||||||
left: u32,
|
left: u32,
|
||||||
right: u32,
|
right: u32,
|
||||||
},
|
},
|
||||||
labels_max_length: usize,
|
|
||||||
box_x: usize,
|
|
||||||
box_y: usize,
|
|
||||||
box_width: usize,
|
|
||||||
box_height: usize,
|
|
||||||
margin_box_v: u8,
|
|
||||||
margin_box_h: u8,
|
|
||||||
blank_cell: Cell,
|
blank_cell: Cell,
|
||||||
full_color: bool,
|
full_color: bool,
|
||||||
termios: ?std.posix.termios,
|
termios: ?std.posix.termios,
|
||||||
@@ -134,13 +126,6 @@ pub fn init(options: InitOptions, log_file: *LogFile, random: Random) !TerminalB
|
|||||||
.left = '|',
|
.left = '|',
|
||||||
.right = '|',
|
.right = '|',
|
||||||
},
|
},
|
||||||
.labels_max_length = options.labels_max_length,
|
|
||||||
.box_x = 0,
|
|
||||||
.box_y = 0,
|
|
||||||
.box_width = (2 * options.margin_box_h) + options.input_len + 1 + options.labels_max_length,
|
|
||||||
.box_height = 7 + (2 * options.margin_box_v),
|
|
||||||
.margin_box_v = options.margin_box_v,
|
|
||||||
.margin_box_h = options.margin_box_h,
|
|
||||||
.blank_cell = Cell.init(' ', options.fg, options.bg),
|
.blank_cell = Cell.init(' ', options.fg, options.bg),
|
||||||
.full_color = options.full_color,
|
.full_color = options.full_color,
|
||||||
// Needed to reclaim the TTY after giving up its control
|
// Needed to reclaim the TTY after giving up its control
|
||||||
@@ -216,70 +201,6 @@ pub fn cascade(self: TerminalBuffer) bool {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
|
|
||||||
if (self.width < 2 or self.height < 2) return;
|
|
||||||
const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2;
|
|
||||||
const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2;
|
|
||||||
const x2 = (self.width + @min(self.width, self.box_width)) / 2;
|
|
||||||
const y2 = (self.height + @min(self.height, self.box_height)) / 2;
|
|
||||||
|
|
||||||
self.box_x = x1;
|
|
||||||
self.box_y = y1;
|
|
||||||
|
|
||||||
if (show_borders) {
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg);
|
|
||||||
|
|
||||||
var c1 = Cell.init(self.box_chars.top, self.border_fg, self.bg);
|
|
||||||
var c2 = Cell.init(self.box_chars.bottom, self.border_fg, self.bg);
|
|
||||||
|
|
||||||
for (0..self.box_width) |i| {
|
|
||||||
c1.put(x1 + i, y1 - 1);
|
|
||||||
c2.put(x1 + i, y2);
|
|
||||||
}
|
|
||||||
|
|
||||||
c1.ch = self.box_chars.left;
|
|
||||||
c2.ch = self.box_chars.right;
|
|
||||||
|
|
||||||
for (0..self.box_height) |i| {
|
|
||||||
c1.put(x1 - 1, y1 + i);
|
|
||||||
c2.put(x2, y1 + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blank_box) {
|
|
||||||
for (0..self.box_height) |y| {
|
|
||||||
for (0..self.box_width) |x| {
|
|
||||||
self.blank_cell.put(x1 + x, y1 + y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
|
|
||||||
start_x: usize,
|
|
||||||
x: usize,
|
|
||||||
y: usize,
|
|
||||||
full_visible_length: usize,
|
|
||||||
visible_length: usize,
|
|
||||||
} {
|
|
||||||
const start_x = self.box_x + self.margin_box_h;
|
|
||||||
const x = start_x + self.labels_max_length + 1;
|
|
||||||
const y = self.box_y + self.margin_box_v;
|
|
||||||
const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x;
|
|
||||||
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.start_x = start_x,
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
.full_visible_length = full_visible_length,
|
|
||||||
.visible_length = visible_length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
|
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
|
||||||
drawColorLabel(text, x, y, self.fg, self.bg);
|
drawColorLabel(text, x, y, self.fg, self.bg);
|
||||||
}
|
}
|
||||||
|
|||||||
147
src/tui/components/CenteredBox.zig
Normal file
147
src/tui/components/CenteredBox.zig
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Cell = @import("../Cell.zig");
|
||||||
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
const termbox = TerminalBuffer.termbox;
|
||||||
|
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
) 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,
|
||||||
|
.left_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.right_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
self.buffer.box_chars.left_up,
|
||||||
|
self.buffer.border_fg,
|
||||||
|
self.buffer.bg,
|
||||||
|
);
|
||||||
|
_ = termbox.tb_set_cell(
|
||||||
|
@intCast(self.right_pos.x),
|
||||||
|
@intCast(self.left_pos.y - 1),
|
||||||
|
self.buffer.box_chars.right_up,
|
||||||
|
self.buffer.border_fg,
|
||||||
|
self.buffer.bg,
|
||||||
|
);
|
||||||
|
_ = termbox.tb_set_cell(
|
||||||
|
@intCast(self.left_pos.x - 1),
|
||||||
|
@intCast(self.right_pos.y),
|
||||||
|
self.buffer.box_chars.left_down,
|
||||||
|
self.buffer.border_fg,
|
||||||
|
self.buffer.bg,
|
||||||
|
);
|
||||||
|
_ = termbox.tb_set_cell(
|
||||||
|
@intCast(self.right_pos.x),
|
||||||
|
@intCast(self.right_pos.y),
|
||||||
|
self.buffer.box_chars.right_down,
|
||||||
|
self.buffer.border_fg,
|
||||||
|
self.buffer.bg,
|
||||||
|
);
|
||||||
|
|
||||||
|
var c1 = Cell.init(self.buffer.box_chars.top, self.buffer.border_fg, self.buffer.bg);
|
||||||
|
var c2 = Cell.init(self.buffer.box_chars.bottom, self.buffer.border_fg, self.buffer.bg);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
c1.ch = self.buffer.box_chars.left;
|
||||||
|
c2.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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| {
|
||||||
|
self.buffer.drawConfinedLabel(
|
||||||
|
title,
|
||||||
|
self.left_pos.x,
|
||||||
|
self.left_pos.y - 1,
|
||||||
|
self.width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.bottom_title) |title| {
|
||||||
|
self.buffer.drawConfinedLabel(
|
||||||
|
title,
|
||||||
|
self.left_pos.x,
|
||||||
|
self.left_pos.y + self.height,
|
||||||
|
self.width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,21 @@ const Message = struct {
|
|||||||
|
|
||||||
label: MessageLabel,
|
label: MessageLabel,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
width: usize,
|
||||||
|
) InfoLine {
|
||||||
return .{
|
return .{
|
||||||
.label = MessageLabel.init(allocator, buffer, drawItem, null, null),
|
.label = MessageLabel.init(
|
||||||
|
allocator,
|
||||||
|
buffer,
|
||||||
|
drawItem,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
width,
|
||||||
|
true,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,23 +50,31 @@ pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void {
|
pub fn clearRendered(self: InfoLine, allocator: Allocator) !void {
|
||||||
// Draw over the area
|
// Draw over the area
|
||||||
const y = buffer.box_y + buffer.margin_box_v;
|
const spaces = try allocator.alloc(u8, self.label.width - 2);
|
||||||
const spaces = try allocator.alloc(u8, buffer.box_width);
|
|
||||||
defer allocator.free(spaces);
|
defer allocator.free(spaces);
|
||||||
|
|
||||||
@memset(spaces, ' ');
|
@memset(spaces, ' ');
|
||||||
|
|
||||||
buffer.drawLabel(spaces, buffer.box_x, y);
|
self.label.buffer.drawLabel(
|
||||||
|
spaces,
|
||||||
|
self.label.component_pos.x + 2,
|
||||||
|
self.label.component_pos.y,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool {
|
fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void {
|
||||||
if (message.width == 0 or label.buffer.box_width <= message.width) return false;
|
if (message.width == 0 or width <= message.width) return;
|
||||||
|
|
||||||
const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2);
|
const x_offset = if (label.text_in_center) (width - message.width) / 2 else 0;
|
||||||
label.first_char_x = x + message.width;
|
|
||||||
|
|
||||||
TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg);
|
label.item_width = message.width + x_offset;
|
||||||
return true;
|
TerminalBuffer.drawColorLabel(
|
||||||
|
message.text,
|
||||||
|
x + x_offset,
|
||||||
|
y,
|
||||||
|
message.fg,
|
||||||
|
message.bg,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,23 @@ const Session = @This();
|
|||||||
label: EnvironmentLabel,
|
label: EnvironmentLabel,
|
||||||
user_list: *UserList,
|
user_list: *UserList,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, user_list: *UserList) Session {
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
user_list: *UserList,
|
||||||
|
width: usize,
|
||||||
|
text_in_center: bool,
|
||||||
|
) Session {
|
||||||
return .{
|
return .{
|
||||||
.label = EnvironmentLabel.init(allocator, buffer, drawItem, sessionChanged, user_list),
|
.label = EnvironmentLabel.init(
|
||||||
|
allocator,
|
||||||
|
buffer,
|
||||||
|
drawItem,
|
||||||
|
sessionChanged,
|
||||||
|
user_list,
|
||||||
|
width,
|
||||||
|
text_in_center,
|
||||||
|
),
|
||||||
.user_list = user_list,
|
.user_list = user_list,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -55,14 +69,12 @@ fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize) bool {
|
fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize, width: usize) void {
|
||||||
const length = @min(env.environment.name.len, label.visible_length - 3);
|
const length = @min(env.environment.name.len, width - 3);
|
||||||
if (length == 0) return false;
|
if (length == 0) return;
|
||||||
|
|
||||||
const nx = if (label.text_in_center) (label.x + (label.visible_length - env.environment.name.len) / 2) else (label.x + 2);
|
const x_offset = if (label.text_in_center) (width - length) / 2 else 0;
|
||||||
label.first_char_x = nx + env.environment.name.len;
|
|
||||||
|
|
||||||
label.buffer.drawLabel(env.environment.specifier, x, y);
|
label.item_width = length + x_offset;
|
||||||
label.buffer.drawLabel(env.environment.name, nx, label.y);
|
label.buffer.drawLabel(env.environment.name, x + x_offset, y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
const termbox = TerminalBuffer.termbox;
|
const termbox = TerminalBuffer.termbox;
|
||||||
|
|
||||||
const DynamicString = std.ArrayListUnmanaged(u8);
|
const DynamicString = std.ArrayListUnmanaged(u8);
|
||||||
@@ -14,13 +15,19 @@ text: DynamicString,
|
|||||||
end: usize,
|
end: usize,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
visible_start: usize,
|
visible_start: usize,
|
||||||
visible_length: usize,
|
width: usize,
|
||||||
x: usize,
|
component_pos: Position,
|
||||||
y: usize,
|
children_pos: Position,
|
||||||
masked: bool,
|
masked: bool,
|
||||||
maybe_mask: ?u32,
|
maybe_mask: ?u32,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u32) Text {
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
masked: bool,
|
||||||
|
maybe_mask: ?u32,
|
||||||
|
width: usize,
|
||||||
|
) Text {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.buffer = buffer,
|
.buffer = buffer,
|
||||||
@@ -28,9 +35,9 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_m
|
|||||||
.end = 0,
|
.end = 0,
|
||||||
.cursor = 0,
|
.cursor = 0,
|
||||||
.visible_start = 0,
|
.visible_start = 0,
|
||||||
.visible_length = 0,
|
.width = width,
|
||||||
.x = 0,
|
.component_pos = TerminalBuffer.START_POSITION,
|
||||||
.y = 0,
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
.masked = masked,
|
.masked = masked,
|
||||||
.maybe_mask = maybe_mask,
|
.maybe_mask = maybe_mask,
|
||||||
};
|
};
|
||||||
@@ -40,10 +47,26 @@ pub fn deinit(self: *Text) void {
|
|||||||
self.text.deinit(self.allocator);
|
self.text.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
|
pub fn positionX(self: *Text, original_pos: Position) void {
|
||||||
self.x = x;
|
self.component_pos = original_pos;
|
||||||
self.y = y;
|
self.children_pos = original_pos.addX(self.width);
|
||||||
self.visible_length = visible_length;
|
}
|
||||||
|
|
||||||
|
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 handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void {
|
pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void {
|
||||||
@@ -79,36 +102,44 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.masked and self.maybe_mask == null) {
|
if (self.masked and self.maybe_mask == null) {
|
||||||
_ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
|
_ = termbox.tb_set_cursor(@intCast(self.component_pos.x), @intCast(self.component_pos.y));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
|
_ = termbox.tb_set_cursor(
|
||||||
|
@intCast(self.component_pos.x + (self.cursor - self.visible_start)),
|
||||||
|
@intCast(self.component_pos.y),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(self: Text) void {
|
pub fn draw(self: Text) void {
|
||||||
if (self.masked) {
|
if (self.masked) {
|
||||||
if (self.maybe_mask) |mask| {
|
if (self.maybe_mask) |mask| {
|
||||||
const length = @min(self.text.items.len, self.visible_length - 1);
|
const length = @min(self.text.items.len, self.width - 1);
|
||||||
if (length == 0) return;
|
if (length == 0) return;
|
||||||
|
|
||||||
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
|
self.buffer.drawCharMultiple(
|
||||||
|
mask,
|
||||||
|
self.component_pos.x,
|
||||||
|
self.component_pos.y,
|
||||||
|
length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const length = @min(self.text.items.len, self.visible_length);
|
const length = @min(self.text.items.len, self.width);
|
||||||
if (length == 0) return;
|
if (length == 0) return;
|
||||||
|
|
||||||
const visible_slice = vs: {
|
const visible_slice = vs: {
|
||||||
if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) {
|
if (self.text.items.len > self.width and self.cursor < self.text.items.len) {
|
||||||
break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)];
|
break :vs self.text.items[self.visible_start..(self.width + self.visible_start)];
|
||||||
} else {
|
} else {
|
||||||
break :vs self.text.items[self.visible_start..];
|
break :vs self.text.items[self.visible_start..];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.buffer.drawLabel(visible_slice, self.x, self.y);
|
self.buffer.drawLabel(visible_slice, self.component_pos.x, self.component_pos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(self: *Text) void {
|
pub fn clear(self: *Text) void {
|
||||||
@@ -127,7 +158,7 @@ fn goLeft(self: *Text) void {
|
|||||||
|
|
||||||
fn goRight(self: *Text) void {
|
fn goRight(self: *Text) void {
|
||||||
if (self.cursor >= self.end) return;
|
if (self.cursor >= self.end) return;
|
||||||
if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1;
|
if (self.cursor - self.visible_start == self.width - 1) self.visible_start += 1;
|
||||||
|
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,25 @@ const UserList = @This();
|
|||||||
|
|
||||||
label: UserLabel,
|
label: UserLabel,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, usernames: StringList, saved_users: *SavedUsers, session: *Session) !UserList {
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
usernames: StringList,
|
||||||
|
saved_users: *SavedUsers,
|
||||||
|
session: *Session,
|
||||||
|
width: usize,
|
||||||
|
text_in_center: bool,
|
||||||
|
) !UserList {
|
||||||
var userList = UserList{
|
var userList = UserList{
|
||||||
.label = UserLabel.init(allocator, buffer, drawItem, usernameChanged, session),
|
.label = UserLabel.init(
|
||||||
|
allocator,
|
||||||
|
buffer,
|
||||||
|
drawItem,
|
||||||
|
usernameChanged,
|
||||||
|
session,
|
||||||
|
width,
|
||||||
|
text_in_center,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (usernames.items) |username| {
|
for (usernames.items) |username| {
|
||||||
@@ -75,13 +91,12 @@ fn usernameChanged(user: User, maybe_session: ?*Session) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawItem(label: *UserLabel, user: User, _: usize, _: usize) bool {
|
fn drawItem(label: *UserLabel, user: User, x: usize, y: usize, width: usize) void {
|
||||||
const length = @min(user.name.len, label.visible_length - 3);
|
const length = @min(user.name.len, width - 3);
|
||||||
if (length == 0) return false;
|
if (length == 0) return;
|
||||||
|
|
||||||
const x = if (label.text_in_center) (label.x + (label.visible_length - user.name.len) / 2) else (label.x + 2);
|
const x_offset = if (label.text_in_center) (width - length) / 2 else 0;
|
||||||
label.first_char_x = x + user.name.len;
|
|
||||||
|
|
||||||
label.buffer.drawLabel(user.name, x, label.y);
|
label.item_width = length + x_offset;
|
||||||
return true;
|
label.buffer.drawLabel(user.name, x + x_offset, y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
|
||||||
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
|
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ItemList = std.ArrayListUnmanaged(ItemType);
|
const ItemList = std.ArrayListUnmanaged(ItemType);
|
||||||
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
|
const DrawItemFn = *const fn (*Self, ItemType, usize, usize, usize) void;
|
||||||
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
|
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
|
||||||
|
|
||||||
const termbox = TerminalBuffer.termbox;
|
const termbox = TerminalBuffer.termbox;
|
||||||
@@ -17,26 +18,34 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
|
|||||||
buffer: *TerminalBuffer,
|
buffer: *TerminalBuffer,
|
||||||
list: ItemList,
|
list: ItemList,
|
||||||
current: usize,
|
current: usize,
|
||||||
visible_length: usize,
|
width: usize,
|
||||||
x: usize,
|
component_pos: Position,
|
||||||
y: usize,
|
children_pos: Position,
|
||||||
first_char_x: usize,
|
|
||||||
text_in_center: bool,
|
text_in_center: bool,
|
||||||
|
item_width: usize,
|
||||||
draw_item_fn: DrawItemFn,
|
draw_item_fn: DrawItemFn,
|
||||||
change_item_fn: ?ChangeItemFn,
|
change_item_fn: ?ChangeItemFn,
|
||||||
change_item_arg: ?ChangeItemType,
|
change_item_arg: ?ChangeItemType,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType) Self {
|
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,
|
||||||
|
) Self {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.buffer = buffer,
|
.buffer = buffer,
|
||||||
.list = .empty,
|
.list = .empty,
|
||||||
.current = 0,
|
.current = 0,
|
||||||
.visible_length = 0,
|
.width = width,
|
||||||
.x = 0,
|
.component_pos = TerminalBuffer.START_POSITION,
|
||||||
.y = 0,
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
.first_char_x = 0,
|
.text_in_center = text_in_center,
|
||||||
.text_in_center = false,
|
.item_width = 0,
|
||||||
.draw_item_fn = draw_item_fn,
|
.draw_item_fn = draw_item_fn,
|
||||||
.change_item_fn = change_item_fn,
|
.change_item_fn = change_item_fn,
|
||||||
.change_item_arg = change_item_arg,
|
.change_item_arg = change_item_arg,
|
||||||
@@ -47,14 +56,29 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
|
|||||||
self.list.deinit(self.allocator);
|
self.list.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position(self: *Self, x: usize, y: usize, visible_length: usize, text_in_center: ?bool) void {
|
pub fn positionX(self: *Self, original_pos: Position) void {
|
||||||
self.x = x;
|
self.component_pos = original_pos;
|
||||||
self.y = y;
|
self.item_width = self.component_pos.x + 2;
|
||||||
self.visible_length = visible_length;
|
self.children_pos = original_pos.addX(self.width);
|
||||||
self.first_char_x = x + 2;
|
}
|
||||||
if (text_in_center) |value| {
|
|
||||||
self.text_in_center = value;
|
pub fn positionY(self: *Self, original_pos: Position) void {
|
||||||
}
|
self.component_pos = original_pos;
|
||||||
|
self.item_width = 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.item_width = 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 {
|
pub fn addItem(self: *Self, item: ItemType) !void {
|
||||||
@@ -81,28 +105,51 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = termbox.tb_set_cursor(@intCast(self.first_char_x), @intCast(self.y));
|
_ = termbox.tb_set_cursor(
|
||||||
|
@intCast(self.component_pos.x + self.item_width + 2),
|
||||||
|
@intCast(self.component_pos.y),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(self: *Self) void {
|
pub fn draw(self: *Self) void {
|
||||||
if (self.list.items.len == 0) return;
|
if (self.list.items.len == 0) return;
|
||||||
|
|
||||||
|
_ = termbox.tb_set_cell(
|
||||||
|
@intCast(self.component_pos.x),
|
||||||
|
@intCast(self.component_pos.y),
|
||||||
|
'<',
|
||||||
|
self.buffer.fg,
|
||||||
|
self.buffer.bg,
|
||||||
|
);
|
||||||
|
_ = termbox.tb_set_cell(
|
||||||
|
@intCast(self.component_pos.x + self.width - 1),
|
||||||
|
@intCast(self.component_pos.y),
|
||||||
|
'>',
|
||||||
|
self.buffer.fg,
|
||||||
|
self.buffer.bg,
|
||||||
|
);
|
||||||
|
|
||||||
const current_item = self.list.items[self.current];
|
const current_item = self.list.items[self.current];
|
||||||
const x = self.buffer.box_x + self.buffer.margin_box_h;
|
const x = self.component_pos.x + 2;
|
||||||
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
|
const y = self.component_pos.y;
|
||||||
|
const width = self.width - 2;
|
||||||
|
|
||||||
const continue_drawing = @call(.auto, self.draw_item_fn, .{ self, current_item, x, y });
|
@call(
|
||||||
if (!continue_drawing) return;
|
.auto,
|
||||||
|
self.draw_item_fn,
|
||||||
_ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
|
.{ self, current_item, x, y, width },
|
||||||
_ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn goLeft(self: *Self) void {
|
fn goLeft(self: *Self) void {
|
||||||
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
|
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
|
||||||
|
|
||||||
if (self.change_item_fn) |change_item_fn| {
|
if (self.change_item_fn) |change_item_fn| {
|
||||||
@call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg });
|
@call(
|
||||||
|
.auto,
|
||||||
|
change_item_fn,
|
||||||
|
.{ self.list.items[self.current], self.change_item_arg },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +157,11 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
|
|||||||
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
|
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
|
||||||
|
|
||||||
if (self.change_item_fn) |change_item_fn| {
|
if (self.change_item_fn) |change_item_fn| {
|
||||||
@call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg });
|
@call(
|
||||||
|
.auto,
|
||||||
|
change_item_fn,
|
||||||
|
.{ self.list.items[self.current], self.change_item_arg },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user