diff --git a/.gitignore b/.gitignore index 1dfcbd2..3389c86 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .zig-cache/ +zig-out/ diff --git a/src/main.zig b/src/main.zig index 0c1a6c8..317d824 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,7 @@ const borders = @import("borders.zig"); const pane = @import("pane.zig"); const color = @import("colors.zig"); const dim = @import("dimensions.zig"); +const menu = @import("menu.zig"); var term_io: term.TermIO = undefined; @@ -36,7 +37,7 @@ pub fn main() !void { var child = pane.Pane{ .parent = &top, - .children = null, + .children = std.ArrayList(*pane.Pane).init(allocator), .dimensions = .{ .anchor = .Center, .size_type = .Relative, @@ -47,13 +48,17 @@ pub fn main() !void { .foreground = color.RGB(255, 125, 10), }; + var items = [_]menu.MenuItem{ .{ .name = "Item 1", .value = 1 }, .{ .name = "Item 2", .value = 2 } }; + var m = menu.Menu.init(&child, dim.Fill, "Test", &items); + pane.top_pane = ⊤ try top.children.?.append(&child); + try child.children.?.append(&m.pane); const childWriter = child.writer(&term_io); var key = term_io.getKey(false); while (key.value != 113 and !pane.should_exit) { - try pane.tick(&term_io); + try pane.tick(&term_io, key); if (key.type == .ASCII and key.value == 111) { try std.fmt.format(childWriter, "\x1b[2J", .{}); try term_io.flush(); @@ -66,8 +71,13 @@ pub fn main() !void { try std.fmt.format(childWriter, "Hello\n", .{}); try term_io.flush(); } + if (key.type == .ASCII and key.value == 108) { + m.pane.focus(&term_io); + try term_io.flush(); + } key = term_io.getKey(false); } top.children.?.deinit(); + child.children.?.deinit(); } diff --git a/src/menu.zig b/src/menu.zig new file mode 100644 index 0000000..0bc8de8 --- /dev/null +++ b/src/menu.zig @@ -0,0 +1,173 @@ +const std = @import("std"); +const dim = @import("dimensions.zig"); +const pane = @import("pane.zig"); +const term = @import("term.zig"); +const Pane = pane.Pane; +const TermIO = term.TermIO; + +pub const MenuItem = struct { + name: []const u8, + value: usize, + disabled: bool = false, +}; + +pub const Menu = struct { + pane: Pane, + title: []const u8, + items: []MenuItem, + selected_item: usize = 0, + selected_value: usize = 0, + + pub fn init(parent: ?*Pane, dimensions: dim.Dimensions, title: []const u8, items: []MenuItem) Menu { + for (items) |item| { + std.debug.assert(item.value != 0); + } + + var menu = Menu{ + .pane = .{ + .parent = parent, + .children = null, + .dimensions = dimensions, + .vtable = .{ + .draw = draw, + .focus = focus, + .update = update, + }, + }, + .title = title, + .items = items, + }; + + menu.reset(); + + return menu; + } + + pub fn reset(self: *Menu) void { + self.selected_item = self.firstEnabledItem() catch self.items.len; + self.selected_value = 0; + } + + fn draw(pane_ptr: *Pane, term_io: *TermIO) !void { + var self: *Menu = @fieldParentPtr("pane", pane_ptr); + self.pane.cursor = .{ .x = 0, .y = 0 }; + self.pane.moveCursor(term_io); + + var writer = self.pane.writer(term_io); + try writer.print("{s}", .{self.title}); + for (0..self.items.len) |i| { + try self.printItem(term_io, i); + } + + try term_io.flush(); + } + + fn update(pane_ptr: *Pane, term_io: *TermIO, key: term.Key) !void { + var self: *Menu = @fieldParentPtr("pane", pane_ptr); + if (self.selected_item == self.items.len or key.type == .NO_KEY) { + return; + } + + if (key.type == term.KeyType.SEQUENCE) { + const key_enum: term.SequenceKey = @enumFromInt(key.value); + switch (key_enum) { + term.SequenceKey.DOWN => try self.nextItem(term_io), + term.SequenceKey.UP => try self.prevItem(term_io), + else => {}, + } + } else { + switch (key.value) { + 13 => self.pane.should_draw = false, // Enter + '0' => { + self.selectItem(9, term_io) catch |err| switch (err) { + error.MenuItemDisabled => {}, + else => return err, + }; + }, + '1'...'9' => { + self.selectItem(key.value - '1', term_io) catch |err| switch (err) { + error.MenuItemDisabled => {}, + else => return err, + }; + }, + 0x1b, 'q' => self.selected_value = 0, + 'j' => try self.nextItem(term_io), + 'k' => try self.prevItem(term_io), + else => {}, + } + } + + try term_io.flush(); + } + + fn focus(pane_ptr: *Pane, term_io: *TermIO, focused: bool) !void { + if (true) return; + _ = focused; + var self: *Menu = @fieldParentPtr("pane", pane_ptr); + try self.printItem(term_io, self.selected_item); + try term_io.flush(); + } + + fn printItem(self: *Menu, term_io: *TermIO, item: usize) !void { + var writer = self.pane.writer(term_io); + + term_io.enableFormats(.{ .highlight = item == self.selected_item, .dim = self.items[item].disabled or pane.focused_pane != &self.pane }); + + if (item < 10) { + self.pane.cursor = .{ .x = 0, .y = item + 1 }; + self.pane.moveCursor(term_io); + try writer.print("{d}: {s}", .{ item + 1 % 10, self.items[item].name }); + } else { + self.pane.cursor = .{ .x = 0, .y = item + 1 }; + self.pane.moveCursor(term_io); + try writer.print(" : {s}", .{self.items[item].name}); + } + + term_io.disableFormats(.{ .highlight = true, .dim = true }); + } + + fn firstEnabledItem(self: *Menu) !usize { + for (0..self.items.len) |i| { + if (!self.items[i].disabled) { + return i; + } + } + + return error.AllMenuItemsDisabled; + } + + fn nextItem(self: *Menu, term_io: *TermIO) !void { + var i: usize = self.selected_item; + while (i < self.items.len - 1 and (self.items[i].disabled or i == self.selected_item)) { + i += 1; + } + if (!self.items[i].disabled) { + try self.selectItem(i, term_io); + } + } + + fn prevItem(self: *Menu, term_io: *TermIO) !void { + var i: usize = self.selected_item; + while (i > 0 and (self.items[i].disabled or i == self.selected_item)) { + i -= 1; + } + if (!self.items[i].disabled) { + try self.selectItem(i, term_io); + } + } + + fn selectItem(self: *Menu, item: usize, term_io: *TermIO) !void { + if (self.items[item].disabled) { + return error.MenuItemDisabled; + } + + const previousSelection = self.selected_item; + self.selected_item = item; + self.selected_value = self.items[self.selected_item].value; + + if (previousSelection != self.selected_item) { + try self.printItem(term_io, previousSelection); + } + try self.printItem(term_io, self.selected_item); + } +}; diff --git a/src/pane.zig b/src/pane.zig index ffdff09..f55cf01 100644 --- a/src/pane.zig +++ b/src/pane.zig @@ -38,20 +38,20 @@ pub fn init(term_io: *TermIO) !void { try std.posix.sigaction(std.posix.SIG.INT, &exit_handler, null); term_io.enterRawMode(); - term_io.saveScreen() catch {}; + term_io.saveScreen(); term_io.hideCursor(); try term_io.flush(); } pub fn cleanup(term_io: *TermIO) void { term_io.showCursor(); - term_io.restoreScreen() catch {}; + term_io.restoreScreen(); term_io.exitRawMode(); term_io.flush() catch {}; term_io.close(); } -pub fn tick(term_io: *TermIO) !void { +pub fn tick(term_io: *TermIO, key: term.Key) !void { if (needs_redraw) { needs_redraw = false; const size = term.getTermSize(term_io); @@ -60,9 +60,15 @@ pub fn tick(term_io: *TermIO) !void { try top_pane.ReDraw(term_io, size); redraw_count += 1; - try std.fmt.format(top_pane.writer(term_io), "Resized {} times", .{redraw_count}); + // try std.fmt.format(top_pane.writer(term_io), "Resized {} times", .{redraw_count}); try term_io.flush(); } + + if (focused_pane) |pane| { + try pane.update(term_io, key); + } + + try term_io.flush(); } pub const Overflow = struct { @@ -85,19 +91,35 @@ pub const Pane = struct { calcDims: dim.CalculatedDimensions = undefined, background: color.Color = color.Default, foreground: color.Color = color.Default, + should_draw: bool = true, - pub const WriterContext = struct { pane: *Pane, term_io: *const TermIO }; + vtable: PaneVtable = .{}, + + fn defaultDraw(_: *Pane, _: *TermIO) !void {} + fn defaultFocus(_: *Pane, _: *TermIO, _: bool) !void {} + fn defaultUpdate(_: *Pane, _: *TermIO, _: term.Key) !void {} + + const PaneVtable = struct { + draw: *const fn (*Pane, *TermIO) anyerror!void = defaultDraw, + focus: *const fn (*Pane, *TermIO, bool) anyerror!void = defaultFocus, + update: *const fn (*Pane, *TermIO, term.Key) anyerror!void = defaultUpdate, + }; + + pub const WriterContext = struct { pane: *Pane, term_io: *TermIO }; pub const Writer = std.io.Writer(WriterContext, std.fs.File.WriteError, write); pub fn ReDraw(self: *Pane, term_io: *TermIO, parentDims: dim.CalculatedDimensions) !void { - self.calcDims = .{ .pos = parentDims.pos, .size = self.dimensions.size }; - if (self.dimensions.size_type == .Relative) { - std.debug.assert(self.dimensions.size.width <= 100 and self.dimensions.size.height <= 100); - self.calcDims.size.width = (parentDims.size.width * self.dimensions.size.width) / 100; - self.calcDims.size.height = (parentDims.size.height * self.dimensions.size.height) / 100; + if (!self.should_draw) { + return; } - try term_io.setColor(self.background, self.foreground); + const parentBorderWidth: u2 = if (self.parent) |parent| (if (parent.border != null) 1 else 0) else 0; + self.calcDims = .{ .pos = .{ .x = parentDims.pos.x + parentBorderWidth, .y = parentDims.pos.y + parentBorderWidth }, .size = self.dimensions.size }; + if (self.dimensions.size_type == .Relative) { + std.debug.assert(self.dimensions.size.width <= 100 and self.dimensions.size.height <= 100); + self.calcDims.size.width = ((parentDims.size.width - parentBorderWidth * 2) * self.dimensions.size.width) / 100; + self.calcDims.size.height = ((parentDims.size.height - parentBorderWidth * 2) * self.dimensions.size.height) / 100; + } switch (self.dimensions.anchor) { .TopLeft => {}, @@ -105,7 +127,7 @@ pub const Pane = struct { self.calcDims.pos.x = parentDims.pos.x + (parentDims.size.width - self.calcDims.size.width) / 2; }, .TopRight => { - self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width; + self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width - parentBorderWidth; }, .CenterLeft => { self.calcDims.pos.y = parentDims.pos.y + (parentDims.size.height - self.calcDims.size.height) / 2; @@ -115,49 +137,62 @@ pub const Pane = struct { self.calcDims.pos.y = parentDims.pos.y + (parentDims.size.height - self.calcDims.size.height) / 2; }, .CenterRight => { - self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width; + self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width - parentBorderWidth; self.calcDims.pos.y = parentDims.pos.y + (parentDims.size.height - self.calcDims.size.height) / 2; }, .BottomLeft => { - self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height; + self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height - parentBorderWidth; }, .BottomCenter => { - self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height; + self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height - parentBorderWidth; self.calcDims.pos.x = parentDims.pos.x + (parentDims.size.width - self.calcDims.size.width) / 2; }, .BottomRight => { - self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height; - self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width; + self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height - parentBorderWidth; + self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width - parentBorderWidth; }, } + var fill = false; + if (self.parent) |parent| { + term_io.setColor(self.background, self.foreground); + fill = !self.background.equal(parent.background); + } else { + term_io.setColor(self.background, self.foreground); + fill = !self.background.equal(color.Default); + } + if (self.border) |border| { - var fill = false; - if (self.parent) |parent| { - fill = !self.background.equal(parent.background); - } else { - fill = !self.background.equal(color.Default); - } try term_io.drawBox(self.calcDims, border, fill); + } else if (fill) { + term_io.fillBox(self.calcDims); } try term_io.flush(); + + try self.vtable.draw(self, term_io); //_ = term_io.getKey(true); if (self.children) |children| { for (children.items) |child| { + child.focus(term_io); try child.ReDraw(term_io, self.calcDims); - try child.focus(term_io); } } } - pub fn focus(self: *Pane, term_io: *const TermIO) !void { + pub fn update(self: *Pane, term_io: *TermIO, key: term.Key) !void { + try self.vtable.update(self, term_io, key); + } + + pub fn focus(self: *Pane, term_io: *TermIO) void { focused_pane = self; self.cursor = .{ .x = 0, .y = 0 }; self.moveCursor(term_io); - try term_io.setColor(self.background, self.foreground); + term_io.setColor(self.background, self.foreground); + + self.vtable.focus(self, term_io, true) catch {}; } fn nextLine(self: *Pane, term_io: *const TermIO) void { @@ -169,7 +204,7 @@ pub const Pane = struct { pub fn moveCursor(self: *Pane, term_io: *const TermIO) void { const borderWidth: u1 = if (self.border != null) 1 else 0; - term_io.moveCursor(self.calcDims.pos.x + self.cursor.x + borderWidth, self.calcDims.pos.y + self.cursor.y + borderWidth) catch {}; + term_io.moveCursor(self.calcDims.pos.x + self.cursor.x + borderWidth, self.calcDims.pos.y + self.cursor.y + borderWidth); } pub fn print(self: *Pane, term_io: *const TermIO, string: []const u8) !void { @@ -178,19 +213,19 @@ pub const Pane = struct { var i: usize = 0; while (string.len - i >= space) { - try term_io.print("{s}", .{string[i .. i + space]}, .{}); + term_io.print("{s}", .{string[i .. i + space]}, .{}); self.nextLine(term_io); i += space; space = self.calcDims.size.width - (2 * borderWidth); } if (i < string.len) { - try term_io.print("{s}", .{string[i..]}, .{}); + term_io.print("{s}", .{string[i..]}, .{}); self.cursor.x += string.len - i; } } - pub fn writer(self: *Pane, term_io: *const TermIO) Writer { + pub fn writer(self: *Pane, term_io: *TermIO) Writer { return .{ .context = .{ .pane = self, .term_io = term_io }, }; @@ -198,7 +233,7 @@ pub const Pane = struct { pub fn write(self: WriterContext, bytes: []const u8) std.fs.File.WriteError!usize { if (self.pane != focused_pane) { - try self.pane.focus(self.term_io); + self.pane.focus(self.term_io); } const borderWidth: u2 = if (self.pane.border != null) 1 else 0; diff --git a/src/term.zig b/src/term.zig index bf41b44..f442c4e 100644 --- a/src/term.zig +++ b/src/term.zig @@ -10,6 +10,7 @@ var orig_termios: posix.termios = undefined; pub const MenuError = error{ForgottenMenuItem}; pub const KeyType = enum { + NO_KEY, ASCII, SEQUENCE, }; @@ -42,12 +43,6 @@ pub const SelectMenuItem = struct { selected: bool = false, }; -pub const MenuItem = struct { - name: []const u8, - value: usize, - disabled: bool = false, -}; - pub const SelectMenu = struct { title: []const u8, items: []SelectMenuItem, @@ -66,7 +61,7 @@ pub const SelectMenu = struct { pub fn refresh(self: *SelectMenu, term_io: *TermIO) !void { term_io.clear(); - try term_io.print("{s}", .{self.title}, .{ .underline = true }); + try term_io.print("{s}", .{self.title}); for (self.items, 0..) |_, i| { try self.printItem(i, term_io); } @@ -138,122 +133,6 @@ pub const SelectMenu = struct { } }; -pub const Menu = struct { - title: []const u8, - items: []MenuItem, - selected_item: usize = 0, - selected_value: usize = 0, - - pub fn init(title: []const u8, items: []MenuItem) Menu { - for (items) |item| { - std.debug.assert(item.value != 0); - } - - return .{ .title = title, .items = items }; - } - - pub fn reset(self: *Menu) void { - self.selected_item = 0; - self.selected_value = 0; - } - - pub fn refresh(self: *Menu, term_io: *TermIO) !void { - term_io.clear(); - - try term_io.print("{s}", .{self.title}, .{ .underline = true }); - for (self.items, 0..) |_, i| { - try self.printItem(i, term_io); - } - } - - pub fn show(self: *Menu, term_io: *TermIO) !void { - if (self.items[self.selected_item].disabled) { - try self.nextItem(term_io); - } - if (self.items[self.selected_item].disabled) { - self.selected_item = 0; - return; - } - - try self.refresh(term_io); - - while (true) { - const key: Key = term_io.getKey(true); - if (key.type == KeyType.SEQUENCE) { - const key_enum: SequenceKey = @enumFromInt(key.value); - switch (key_enum) { - SequenceKey.DOWN => try self.nextItem(term_io), - SequenceKey.UP => try self.prevItem(term_io), - else => continue, - } - } - - switch (key.value) { - 13 => break, // Enter - '0' => { - if (!self.items[9].disabled) { - try self.selectItem(9, term_io); - break; - } - }, - '1'...'9' => { - if (!self.items[key.value - '1'].disabled) { - try self.selectItem(key.value - '1', term_io); - break; - } - }, - 0x1b, 'q' => { - self.selected_value = 0; - return; - }, - 'j' => try self.nextItem(term_io), - 'k' => try self.prevItem(term_io), - else => continue, - } - } - - self.selected_value = self.items[self.selected_item].value; - } - - fn printItem(self: *Menu, item: usize, term_io: *TermIO) !void { - if (item < 10) { - try term_io.output(1, item + 2, "{d}: {s}", .{ item + 1 % 10, self.items[item].name }, .{ .dim = self.items[item].disabled, .highlight = item == self.selected_item }); - } else { - try term_io.output(1, item + 2, " : {s}", .{self.items[item].name}, .{ .dim = self.items[item].disabled, .highlight = item == self.selected_item }); - } - } - - fn nextItem(self: *Menu, term_io: *TermIO) !void { - var i: usize = self.selected_item; - while (i < self.items.len and (self.items[i].disabled or i == self.selected_item)) { - i += 1; - } - if (!self.items[i].disabled) { - try self.selectItem(i, term_io); - } - } - - fn prevItem(self: *Menu, term_io: *TermIO) !void { - var i: usize = self.selected_item; - while (i > 0 and (self.items[i].disabled or i == self.selected_item)) { - i -= 1; - } - if (!self.items[i].disabled) { - try self.selectItem(i, term_io); - } - } - - fn selectItem(self: *Menu, item: usize, term_io: *TermIO) !void { - const previousSelection = self.selected_item; - self.selected_item = item; - - if (previousSelection != self.selected_item) { - try self.printItem(previousSelection, term_io); - } - try self.printItem(self.selected_item, term_io); - } -}; - pub const Input = struct { pos_x: usize, pos_y: usize, @@ -336,10 +215,21 @@ pub const Input = struct { } }; +pub const TermFormat = packed struct { + bold: bool = false, + dim: bool = false, + italic: bool = false, + underline: bool = false, + highlight: bool = false, + invisible: bool = false, + strikethrough: bool = false, +}; + pub const TermIO = struct { stdin: fs.File.Reader, stdout: std.io.BufferedWriter(4096, fs.File.Writer).Writer, tty_file: fs.File, + current_format: TermFormat = .{}, current_background: color.Color = color.Default, current_foreground: color.Color = color.Default, escape_sequence_queued: bool = false, @@ -381,107 +271,197 @@ pub const TermIO = struct { posix.tcsetattr(term_io.tty_file.handle, posix.TCSA.FLUSH, orig_termios) catch unreachable; } - pub fn saveScreen(term_io: *const TermIO) !void { - try term_io.stdout.print("\x1b[s", .{}); - try term_io.stdout.print("\x1b[?47h", .{}); - try term_io.stdout.print("\x1b[?1049h", .{}); + pub fn enableFormats(term_io: *TermIO, formats: TermFormat) void { + if (formats.bold and !term_io.current_format.bold) { + term_io.current_format.bold = true; + term_io.print("\x1b[1m", .{}); + } + + if (formats.dim and !term_io.current_format.dim) { + term_io.current_format.dim = true; + term_io.print("\x1b[2m", .{}); + } + + if (formats.italic and !term_io.current_format.italic) { + term_io.current_format.italic = true; + term_io.print("\x1b[3m", .{}); + } + + if (formats.underline and !term_io.current_format.underline) { + term_io.current_format.underline = true; + term_io.print("\x1b[4m", .{}); + } + + if (formats.highlight and !term_io.current_format.highlight) { + term_io.current_format.highlight = true; + term_io.print("\x1b[7m", .{}); + } + + if (formats.invisible and !term_io.current_format.invisible) { + term_io.current_format.invisible = true; + term_io.print("\x1b[8m", .{}); + } + + if (formats.strikethrough and !term_io.current_format.strikethrough) { + term_io.current_format.strikethrough = true; + term_io.print("\x1b[9m", .{}); + } } - pub fn restoreScreen(term_io: *const TermIO) !void { - try term_io.stdout.print("\x1b[?1049l", .{}); - try term_io.stdout.print("\x1b[?47l", .{}); - try term_io.stdout.print("\x1b[u", .{}); + pub fn disableFormats(term_io: *TermIO, formats: TermFormat) void { + if ((!term_io.current_format.bold or formats.bold) and (!term_io.current_format.dim or formats.dim) and + (!term_io.current_format.italic or formats.italic) and (!term_io.current_format.underline or formats.underline) and + (!term_io.current_format.highlight or formats.highlight) and (!term_io.current_format.invisible or formats.invisible) and + (!term_io.current_format.strikethrough or formats.strikethrough)) + { + term_io.current_format = .{}; + term_io.print("\x1b[0m", .{}); + } + + if (formats.dim and !term_io.current_format.dim) { + term_io.current_format.dim = false; + term_io.print("\x1b[22m", .{}); + } + + if (formats.italic and !term_io.current_format.italic) { + term_io.current_format.italic = false; + term_io.print("\x1b[23m", .{}); + } + + if (formats.underline and !term_io.current_format.underline) { + term_io.current_format.underline = false; + term_io.print("\x1b[24m", .{}); + } + + if (formats.highlight and !term_io.current_format.highlight) { + term_io.current_format.highlight = false; + term_io.print("\x1b[27m", .{}); + } + + if (formats.invisible and !term_io.current_format.invisible) { + term_io.current_format.invisible = false; + term_io.print("\x1b[28m", .{}); + } + + if (formats.strikethrough and !term_io.current_format.strikethrough) { + term_io.current_format.strikethrough = false; + term_io.print("\x1b[29m", .{}); + } + + if (formats.bold and !term_io.current_format.bold) { + term_io.current_format.bold = false; + term_io.print("\x1b[21m", .{}); + } + } + + pub fn saveScreen(term_io: *const TermIO) void { + term_io.print("\x1b[s", .{}); + term_io.print("\x1b[?47h", .{}); + term_io.print("\x1b[?1049h", .{}); + } + + pub fn restoreScreen(term_io: *const TermIO) void { + term_io.print("\x1b[?1049l", .{}); + term_io.print("\x1b[?47l", .{}); + term_io.print("\x1b[u", .{}); } pub fn clear(term_io: *const TermIO) void { - term_io.stdout.print("\x1b[2J\x1b[H", .{}) catch unreachable; + term_io.print("\x1b[2J\x1b[H", .{}); } - pub fn setColor(term_io: *const TermIO, background: color.Color, foreground: color.Color) !void { - try term_io.setBackgroundColor(background); - try term_io.setForegroundColor(foreground); + pub fn setColor(term_io: *TermIO, background: color.Color, foreground: color.Color) void { + term_io.setBackgroundColor(background); + term_io.setForegroundColor(foreground); } - pub fn setBackgroundColor(term_io: *const TermIO, background: color.Color) !void { + pub fn setBackgroundColor(term_io: *TermIO, background: color.Color) void { if (!background.equal(term_io.current_background)) { switch (background.type) { - .Default => try term_io.print("\x1b[49m", .{}, .{}), - .RGB => try term_io.print("\x1b[48;2;{};{};{}m", .{ background.red, background.green, background.blue }, .{}), + .Default => term_io.print("\x1b[49m", .{}), + .RGB => term_io.print("\x1b[48;2;{};{};{}m", .{ background.red, background.green, background.blue }), } } + + term_io.current_background = background; } - pub fn setForegroundColor(term_io: *const TermIO, foreground: color.Color) !void { + pub fn setForegroundColor(term_io: *TermIO, foreground: color.Color) void { if (!foreground.equal(term_io.current_foreground)) { switch (foreground.type) { - .Default => try term_io.print("\x1b[39m", .{}, .{}), - .RGB => try term_io.print("\x1b[38;2;{};{};{}m", .{ foreground.red, foreground.green, foreground.blue }, .{}), + .Default => term_io.print("\x1b[39m", .{}), + .RGB => term_io.print("\x1b[38;2;{};{};{}m", .{ foreground.red, foreground.green, foreground.blue }), } } + + term_io.current_foreground = foreground; } pub fn hideCursor(term_io: *const TermIO) void { - term_io.stdout.print("\x1b[?25l", .{}) catch unreachable; + term_io.print("\x1b[?25l", .{}); } pub fn showCursor(term_io: *const TermIO) void { - term_io.stdout.print("\x1b[?25h", .{}) catch unreachable; + term_io.print("\x1b[?25h", .{}); } - pub fn moveCursor(term_io: *const TermIO, x: usize, y: usize) !void { - try term_io.stdout.print("\x1b[{d};{d}H", .{ y, x }); + pub fn moveCursor(term_io: *const TermIO, x: usize, y: usize) void { + term_io.print("\x1b[{d};{d}H", .{ y, x }); } - pub fn saveTitle(term_io: *const TermIO) !void { - try term_io.stdout.print("\x1b[22;2t", .{}); + pub fn saveTitle(term_io: *const TermIO) void { + term_io.print("\x1b[22;2t", .{}); } - pub fn restoreTitle(term_io: *const TermIO) !void { - try term_io.stdout.print("\x1b[23;2t", .{}); + pub fn restoreTitle(term_io: *const TermIO) void { + term_io.print("\x1b[23;2t", .{}); } - pub fn setTitle(term_io: *const TermIO, title: []const u8) !void { - try term_io.stdout.print("\x1b]0;{s}\x1b\\", .{title}); + pub fn setTitle(term_io: *const TermIO, title: []const u8) void { + term_io.print("\x1b]0;{s}\x1b\\", .{title}); } - pub fn print(term_io: *const TermIO, comptime format: []const u8, args: anytype, flags: PrintFlags) !void { - // try term_io.stdout.print("{s}{s}{s}{s}{s}" ++ format ++ "\x1b[0m", .{ if (flags.bold) "\x1b[1m" else "", if (flags.dim) "\x1b[2m" else "", if (flags.italic) "\x1b[3m" else "", if (flags.underline) "\x1b[4m" else "", if (flags.highlight) "\x1b[7m" else "" } ++ args); - _ = flags; - try term_io.stdout.print(format, args); + pub fn print(term_io: *const TermIO, comptime format: []const u8, args: anytype) void { + term_io.stdout.print(format, args) catch @panic("Failed to print!!"); } - pub fn output(term_io: *const TermIO, x: usize, y: usize, comptime format: []const u8, args: anytype, flags: PrintFlags) !void { - // try term_io.stdout.print("\x1b[{d};{d}H{s}{s}{s}{s}{s}" ++ format ++ "\x1b[0m", .{ y, x, if (flags.bold) "\x1b[1m" else "", if (flags.dim) "\x1b[2m" else "", if (flags.italic) "\x1b[3m" else "", if (flags.underline) "\x1b[4m" else "", if (flags.highlight) "\x1b[7m" else "" } ++ args); - _ = flags; - try term_io.stdout.print("\x1b[{d};{d}H" ++ format, .{ y, x } ++ args); + pub fn output(term_io: *const TermIO, x: usize, y: usize, comptime format: []const u8, args: anytype) void { + term_io.stdout.print("\x1b[{d};{d}H" ++ format, .{ y, x } ++ args) catch @panic("Failed to print!!"); + } + + pub fn fillBox(term_io: *const TermIO, dims: dim.CalculatedDimensions) void { + for (0..dims.size.height) |i| { + term_io.moveCursor(dims.pos.x, dims.pos.y + i); + term_io.print("\x1b[{d}X", .{dims.size.width}); + } } pub fn drawBox(term_io: *TermIO, dims: dim.CalculatedDimensions, border: borders.Border, fill: bool) !void { if (dims.size.width < 2 or dims.size.height < 2) { return; } - try term_io.output(dims.pos.x, dims.pos.y, "{u}", .{border.top_left}, .{}); + term_io.output(dims.pos.x, dims.pos.y, "{u}", .{border.top_left}); for (0..dims.size.width - 2) |_| { - try term_io.print("{u}", .{border.top}, .{}); + term_io.print("{u}", .{border.top}); } - try term_io.print("{u}", .{border.top_right}, .{}); + term_io.print("{u}", .{border.top_right}); for (1..dims.size.height - 1) |h| { - try term_io.output(dims.pos.x, dims.pos.y + h, "{u}", .{border.left}, .{}); + term_io.output(dims.pos.x, dims.pos.y + h, "{u}", .{border.left}); if (fill) { - try term_io.print("\x1b[{d}X", .{dims.size.width - 2}, .{}); + term_io.print("\x1b[{d}X", .{dims.size.width - 2}); } - try term_io.output(dims.pos.x + dims.size.width - 1, dims.pos.y + h, "{u}", .{border.right}, .{}); + term_io.output(dims.pos.x + dims.size.width - 1, dims.pos.y + h, "{u}", .{border.right}); if (term_io.stdout.context.buf.len - term_io.stdout.context.end < dims.size.width * 3) { try term_io.flush(); _ = term_io.getKey(true); } } - try term_io.output(dims.pos.x, dims.pos.y + dims.size.height - 1, "{u}", .{border.bottom_left}, .{}); + term_io.output(dims.pos.x, dims.pos.y + dims.size.height - 1, "{u}", .{border.bottom_left}); for (0..dims.size.width - 2) |_| { - try term_io.print("{u}", .{border.bottom}, .{}); + term_io.print("{u}", .{border.bottom}); } - try term_io.print("{u}", .{border.bottom_right}, .{}); + term_io.print("{u}", .{border.bottom_right}); } pub fn getKey(term_io: *TermIO, block: bool) Key { @@ -493,10 +473,9 @@ pub const TermIO = struct { if (!term_io.escape_sequence_queued) { const c = term_io.stdin.readByte() catch |err| { switch (err) { - error.WouldBlock => if (!block) return .{ .type = KeyType.ASCII, .value = 0 }, - error.EndOfStream => if (!block) return .{ .type = KeyType.ASCII, .value = 0 }, + error.WouldBlock, error.EndOfStream => if (!block) return .{ .type = KeyType.NO_KEY, .value = 0 }, else => { - std.debug.print("Error: {}\n", .{err}); + std.debug.print("Error: {any}\n", .{err}); unreachable; }, }