diff options
author | jjanzen <jjanzen@jjanzen.ca> | 2025-01-20 17:30:19 -0600 |
---|---|---|
committer | jjanzen <jjanzen@jjanzen.ca> | 2025-01-20 17:30:19 -0600 |
commit | 706dfc3b9fc2aef2427fda5e40c2d5dae9e894b1 (patch) | |
tree | 6ca7ad16acb82344ce80d88dfb948f6dc536032b /src/frontend/ncurses.zig | |
parent | 3cd1ec2279e98f9569587b38008ad0011f97e602 (diff) |
start migration to zig
Diffstat (limited to 'src/frontend/ncurses.zig')
-rw-r--r-- | src/frontend/ncurses.zig | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/src/frontend/ncurses.zig b/src/frontend/ncurses.zig new file mode 100644 index 0000000..1586cf0 --- /dev/null +++ b/src/frontend/ncurses.zig @@ -0,0 +1,186 @@ +const std = @import("std"); +const Entity = @import("../ecs/entity.zig").Entity; +const Action = @import("../actions.zig").Action; +const ncurses = @cImport({ + @cInclude("ncurses.h"); +}); +const locale = @cImport({ + @cInclude("locale.h"); +}); + +const MAIN_PANEL_WIDTH = 100; +const MAIN_PANEL_HEIGHT = 41; +const INSTRUCTION_PANEL_WIDTH = 32; +const INSTRUCTION_PANEL_HEIGHT = 39; +const MESSAGE_PANEL_WIDTH = 100; +const MESSAGE_PANEL_HEIGHT = 3; +const STATUS_PANEL_WIDTH = 32; +const STATUS_PANEL_HEIGHT = 5; + +const KEY_MOVE_UP: i32 = 'k'; +const KEY_MOVE_DOWN: i32 = 'j'; +const KEY_MOVE_LEFT: i32 = 'h'; +const KEY_MOVE_RIGHT: i32 = 'l'; +const KEY_STAIR_UP: i32 = '<'; +const KEY_STAIR_DOWN: i32 = '>'; +const KEY_QUIT: i32 = ncurses.KEY_F(1); + +pub const Display = struct { + main: ?*ncurses.WINDOW, + inst: ?*ncurses.WINDOW, + msgs: ?*ncurses.WINDOW, + stat: ?*ncurses.WINDOW, + allocator: std.mem.Allocator, + + fn validTermSize() bool { + return ncurses.LINES >= MAIN_PANEL_HEIGHT + MESSAGE_PANEL_HEIGHT and + ncurses.COLS >= MAIN_PANEL_WIDTH + INSTRUCTION_PANEL_WIDTH; + } + + fn colorSupport() bool { + return ncurses.has_colors(); + } + + fn createNewWin(height: i32, width: i32, y: i32, x: i32) ?*ncurses.WINDOW { + const local_win = ncurses.newwin(height, width, y, x); + _ = ncurses.box(local_win, 0, 0); + _ = ncurses.wrefresh(local_win); + return local_win; + } + + fn formatInstruction(self: *Display, pos: i32, key: i32, description: [:0]const u8) void { + const c_description: ?[*:0]const u8 = description.ptr; + + if (ncurses.KEY_F0 <= key and key <= ncurses.KEY_F(64)) { + _ = ncurses.mvwprintw(self.inst, pos, 2, "F%d - %s", key - ncurses.KEY_F0, c_description); + } else if (key < 128) { + _ = ncurses.mvwprintw(self.inst, pos, 2, "%c - %s", key, c_description); + } else { + self.displayMessage("Invalid key name: {d}", .{key}); + } + } + + pub fn displayInstructions(self: *Display) void { + self.formatInstruction(1, KEY_MOVE_LEFT, "move left"); + self.formatInstruction(2, KEY_MOVE_DOWN, "move down"); + self.formatInstruction(3, KEY_MOVE_UP, "move up"); + self.formatInstruction(4, KEY_MOVE_RIGHT, "move right"); + self.formatInstruction(5, KEY_STAIR_DOWN, "move down staircase"); + self.formatInstruction(6, KEY_STAIR_UP, "move up staircase"); + self.formatInstruction(7, KEY_QUIT, "quit"); + _ = ncurses.wrefresh(self.inst); + } + + pub fn displayMessage(self: *Display, comptime fmt: []const u8, args: anytype) void { + for (1..MESSAGE_PANEL_WIDTH - 1) |i| { + const i_32 = @as(i32, @intCast(i)); + _ = ncurses.mvwaddch(self.msgs, 1, i_32, ' '); + } + + var buf: [MESSAGE_PANEL_WIDTH:0]u8 = undefined; + const data = std.fmt.bufPrint(&buf, fmt, args) catch { + self.displayMessage("Message too long", .{}); + return; + }; + buf[data.len] = 0; + + const msg: [:0]u8 = &buf; + + const c_msg: ?[*:0]const u8 = msg.ptr; + _ = ncurses.mvwprintw(self.msgs, 1, 1, c_msg); + _ = ncurses.wrefresh(self.msgs); + } + + pub fn displayStatus(self: *Display, entity: *const Entity) void { + for (1..STATUS_PANEL_HEIGHT - 1) |i| { + for (1..STATUS_PANEL_WIDTH - 1) |j| { + const i_32 = @as(i32, @intCast(i)); + const j_32 = @as(i32, @intCast(j)); + _ = ncurses.mvwaddch(self.stat, i_32, j_32, ' '); + } + } + + _ = entity; + _ = ncurses.wrefresh(self.stat); + } + + pub fn processInput(self: *Display) Action { + _ = self; + + const ch = ncurses.getch(); + return switch (ch) { + KEY_MOVE_UP => Action.move_up, + KEY_MOVE_DOWN => Action.move_down, + KEY_MOVE_LEFT => Action.move_left, + KEY_MOVE_RIGHT => Action.move_right, + KEY_STAIR_DOWN => Action.down_stair, + KEY_STAIR_UP => Action.up_stair, + KEY_QUIT => Action.exit, + else => Action.illegal, + }; + } + + pub fn init() !Display { + _ = locale.setlocale(locale.LC_ALL, ""); + if (ncurses.initscr() == null) { + return error.CursesInitFail; + } + + if (!validTermSize()) { + _ = ncurses.endwin(); + return error.InvalidTermSize; + } + + if (!colorSupport()) { + _ = ncurses.endwin(); + return error.NoColorSupport; + } + + _ = ncurses.raw(); + _ = ncurses.keypad(ncurses.stdscr, true); + _ = ncurses.noecho(); + _ = ncurses.curs_set(0); + _ = ncurses.start_color(); + + _ = ncurses.init_pair(1, ncurses.COLOR_WHITE, ncurses.COLOR_BLACK); + _ = ncurses.init_pair(2, ncurses.COLOR_BLACK, ncurses.COLOR_RED); + _ = ncurses.wattron(ncurses.stdscr, ncurses.COLOR_PAIR(1)); + _ = ncurses.refresh(); + + return Display{ + .inst = createNewWin( + INSTRUCTION_PANEL_HEIGHT, + INSTRUCTION_PANEL_WIDTH, + @divTrunc(ncurses.LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT, 2) + 1, + @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2) + MAIN_PANEL_WIDTH - 1, + ), + .msgs = createNewWin( + MESSAGE_PANEL_HEIGHT, + MESSAGE_PANEL_WIDTH, + @divTrunc(ncurses.LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT, 2) + MAIN_PANEL_HEIGHT, + @divTrunc(ncurses.COLS - MESSAGE_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2), + ), + .stat = createNewWin( + STATUS_PANEL_HEIGHT, + STATUS_PANEL_WIDTH, + @divTrunc(ncurses.LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT, 2) + INSTRUCTION_PANEL_HEIGHT, + @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2) + MAIN_PANEL_WIDTH - 1, + ), + .main = createNewWin( + MAIN_PANEL_HEIGHT, + MAIN_PANEL_WIDTH, + @divTrunc(ncurses.LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT, 2) + 1, + @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2), + ), + .allocator = undefined, + }; + } + + pub fn deinit(self: *Display) void { + _ = ncurses.delwin(self.main); + _ = ncurses.delwin(self.inst); + _ = ncurses.delwin(self.msgs); + _ = ncurses.delwin(self.stat); + _ = ncurses.endwin(); + } +}; |