const std = @import("std"); const os = std.os; const io = std.io; const fs = std.fs; const mem = std.mem; const heap = std.heap; const fmt = std.fmt; pub fn main() !void { var gpa = heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var term = try Terminal.init(allocator); defer term.deinit(); try term.clearScreen(); var box = Terminal.Box.init(allocator); defer box.deinit(); box.content = "hi"; box.border = true; try term.box.addChild(&box); term.box.border = true; try term.draw(); // var box = Terminal.Box{ // .x = 0, // .y = 0, // .width = 13, // .height = 1, // .content = "hello world", // .border = true, // }; // try box.draw(&term); // try term.clearScreen(); // try term.print("fooboo", .{}); // try term.cursorLeft(); // try term.cursorLeft(); // try term.print("ar", .{}); // // try term.cursorSet(12, 3); // try term.blinkOn(); // try term.boldOn(); // try term.underlineOn(); // try term.italicsOn(); // try term.print("ABCDEFGHIJKLMNOPQRSTUVWXYZ", .{}); // try term.blinkOff(); // try term.boldOff(); // try term.underlineOff(); // try term.italicsOff(); // try term.print("eeheeveehee\n", .{}); } pub const Terminal = struct { tty: fs.File, original_termios: os.linux.termios, info: Info, allocator: mem.Allocator, box: Box, pub fn init(allocator: mem.Allocator) !Terminal { var term = Terminal{ .tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }), .original_termios = undefined, .info = try Info.init(allocator), .allocator = allocator, .box = Box.init(allocator), }; errdefer term.tty.close(); errdefer term.info.deinit(); term.uncook(); return term; } pub fn deinit(self: *Terminal) void { self.box.deinit(); self.cook(); self.tty.close(); self.info.deinit(); } // pub fn poll(self: *Terminal) void {} fn uncook(self: *Terminal) void { _ = os.linux.tcgetattr(self.tty.handle, &self.original_termios); var raw = self.original_termios; raw.lflag.ECHO = false; raw.lflag.ICANON = false; raw.lflag.ISIG = false; raw.lflag.IEXTEN = false; raw.iflag.IXON = false; raw.iflag.ICRNL = false; raw.iflag.BRKINT = false; raw.iflag.INPCK = false; raw.iflag.ISTRIP = false; raw.oflag.OPOST = false; raw.cc[@intFromEnum(os.linux.V.TIME)] = 0; raw.cc[@intFromEnum(os.linux.V.MIN)] = 1; _ = os.linux.tcsetattr(self.tty.handle, .FLUSH, &raw); } fn cook(self: *Terminal) void { _ = os.linux.tcsetattr(self.tty.handle, .FLUSH, &self.original_termios); } pub fn draw(self: *Terminal) !void { try self.box.draw(self); } pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void { const formatted = try fmt.allocPrint(self.allocator, format, args); defer self.allocator.free(formatted); try self.tty.writeAll(formatted); } pub fn cursorUp(self: *Terminal) !void { try self.info.writeString(.cursor_up, self.tty.writer(), &[_]u32{}); } pub fn cursorDown(self: *Terminal) !void { try self.info.writeString(.cursor_down, self.tty.writer(), &[_]u32{}); } pub fn cursorLeft(self: *Terminal) !void { try self.info.writeString(.cursor_left, self.tty.writer(), &[_]u32{}); } pub fn cursorRight(self: *Terminal) !void { try self.info.writeString(.cursor_right, self.tty.writer(), &[_]u32{}); } pub fn cursorSet(self: *Terminal, x: u32, y: u32) !void { try self.info.writeString(.cursor_address, self.tty.writer(), &[_]u32{ y, x }); } pub fn blinkOn(self: *Terminal) !void { try self.info.writeString(.enter_blink_mode, self.tty.writer(), &[_]u32{}); } pub fn blinkOff(self: *Terminal) !void { try self.info.writeString(.exit_blink_mode, self.tty.writer(), &[_]u32{}); } pub fn boldOn(self: *Terminal) !void { try self.info.writeString(.enter_bold_mode, self.tty.writer(), &[_]u32{}); } pub fn boldOff(self: *Terminal) !void { try self.info.writeString(.exit_bold_mode, self.tty.writer(), &[_]u32{}); } pub fn italicsOn(self: *Terminal) !void { try self.info.writeString(.enter_italics_mode, self.tty.writer(), &[_]u32{}); } pub fn italicsOff(self: *Terminal) !void { try self.info.writeString(.exit_italics_mode, self.tty.writer(), &[_]u32{}); } pub fn underlineOn(self: *Terminal) !void { try self.info.writeString(.enter_underline_mode, self.tty.writer(), &[_]u32{}); } pub fn underlineOff(self: *Terminal) !void { try self.info.writeString(.exit_underline_mode, self.tty.writer(), &[_]u32{}); } pub fn clearScreen(self: *Terminal) !void { try self.info.writeString(.clear_screen, self.tty.writer(), &[_]u32{}); } pub const Info = @import("terminfo.zig"); pub const SpecialKey = enum(u16) { home, end, page_up, page_down, delete, backspace, arrow_left, arrow_right, arrow_up, arrow_down, }; pub const Box = struct { parent: ?*Box, children: std.ArrayList(*Box), // x: u32, // y: u32, // width: u32, // height: u32, left: u32, right: u32, top: u32, bottom: u32, border_type: enum { line, bg } = .line, border_char: u8 = ' ', border_bg: i16 = -1, border_fg: i16 = -1, content: []const u8 = "", border: bool = false, pub fn init(allocator: mem.Allocator) Box { return .{ .parent = null, .children = std.ArrayList(*Box).init(allocator), .x = 0, .y = 0, .width = 20, .height = 8, }; } pub fn deinit(self: *Box) void { for (self.children.items) |child| child.deinit(); self.children.deinit(); } pub fn draw(self: *Box, term: *Terminal) !void { var x: u32 = 0; var y: u32 = 0; try term.cursorSet(self.x, self.y); switch (self.border_type) { .line => { try term.print("", .{}); while (x < self.width) : (x += 1) try term.print("", .{}); try term.print("", .{}); y += 1; while (y <= self.height) : (y += 1) { try term.cursorSet(self.x, self.y + y); try term.print("", .{}); try term.cursorSet(self.x + self.width + 1, self.y + y); try term.print("", .{}); } try term.cursorSet(self.x, self.y + y); }, .bg => {}, } // if (self.border) { // try term.print("┌", .{}); // // x += 1; // while (x < self.width) : (x += 1) try term.print("─", .{}); // try term.print("┐", .{}); // // x += 1; // y += 1; // while (y <= self.height) : (y += 1) { // try term.cursorSet(self.x, self.y + y); // try term.print("│", .{}); // try term.cursorSet(self.x + self.width + 1, self.y + y); // try term.print("│", .{}); // } // x = 0; // try term.cursorSet(self.x + x, self.y + y); // try term.print("└", .{}); // while (x < self.width) : (x += 1) try term.print("─", .{}); // try term.print("┘", .{}); // } // x = self.x + 2; // y = self.y + 1; // try term.cursorSet(x, y); // if (self.content.len < self.width) { // try term.print("{s}", .{self.content}); // x += @intCast(self.content.len); // } else { // const space = mem.indexOfScalar(u8, self.content, ' '); // if (space) |sp| { // try term.print("{s}", .{self.content[0..sp]}); // y += 1; // try term.cursorSet(x, y); // try term.print("{s}", .{self.content[sp + 1 ..]}); // } // } for (self.children.items) |child| try child.draw(term); try term.cursorSet(self.x + self.width + 2, self.y + self.height + 1); } pub fn addChild(self: *Box, child: *Box) !void { std.debug.assert(child.parent == null); child.parent = self; child.x = self.x + 2; child.y = self.y + 2; child.width = self.width - 4; child.height = self.height - 4; try self.children.append(child); } }; };