1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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();
}
};
|