aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/ncurses.zig
diff options
context:
space:
mode:
authorjjanzen <jjanzen@jjanzen.ca>2025-01-20 17:30:19 -0600
committerjjanzen <jjanzen@jjanzen.ca>2025-01-20 17:30:19 -0600
commit706dfc3b9fc2aef2427fda5e40c2d5dae9e894b1 (patch)
tree6ca7ad16acb82344ce80d88dfb948f6dc536032b /src/frontend/ncurses.zig
parent3cd1ec2279e98f9569587b38008ad0011f97e602 (diff)
start migration to zig
Diffstat (limited to 'src/frontend/ncurses.zig')
-rw-r--r--src/frontend/ncurses.zig186
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();
+ }
+};