README: add build instructions
[rofl0r-df-libgraphics.git] / g_src / renderer_curses.cpp
blobe49312732f92433a1cf03d06227e3f7718f840d2
1 #ifdef CURSES
2 static bool curses_initialized = false;
4 static void endwin_void() {
5 if (curses_initialized) {
6 endwin();
7 curses_initialized = false;
11 class renderer_curses : public renderer {
12 std::map<std::pair<int,int>,int> color_pairs;
14 // Map from DF color to ncurses color
15 static int ncurses_map_color(int color) {
16 if (color < 0) abort();
17 switch (color) {
18 case 0: return 0;
19 case 1: return 4;
20 case 2: return 2;
21 case 3: return 6;
22 case 4: return 1;
23 case 5: return 5;
24 case 6: return 3;
25 case 7: return 7;
26 default: return ncurses_map_color(color - 7);
30 // Look up, or create, a curses color pair
31 int lookup_pair(pair<int,int> color) {
32 map<pair<int,int>,int>::iterator it = color_pairs.find(color);
33 if (it != color_pairs.end()) return it->second;
34 // We don't already have it. Make sure it's in range.
35 if (color.first < 0 || color.first > 7 || color.second < 0 || color.second > 7) return 0;
36 // We don't already have it. Generate a new pair if possible.
37 if (color_pairs.size() < COLOR_PAIRS - 1) {
38 const short pair = color_pairs.size() + 1;
39 init_pair(pair, ncurses_map_color(color.first), ncurses_map_color(color.second));
40 color_pairs[color] = pair;
41 return pair;
43 // We don't have it, and there's no space for more. Find the closest equivalent.
44 int score = 999, pair = 0;
45 int rfg = color.first % 16, rbg = color.second % 16;
46 for (auto it = color_pairs.cbegin(); it != color_pairs.cend(); ++it) {
47 int fg = it->first.first;
48 int bg = it->first.second;
49 int candidate = it->second;
50 int candidate_score = 0; // Lower is better.
51 if (rbg != bg) {
52 if (rbg == 0 || rbg == 15)
53 candidate_score += 3; // We would like to keep the background black/white.
54 if ((rbg == 7 || rbg == 8)) {
55 if (bg == 7 || bg == 8)
56 candidate_score += 1; // Well, it's still grey.
57 else
58 candidate_score += 2;
61 if (rfg != fg) {
62 if (rfg == 0 || rfg == 15)
63 candidate_score += 5; // Keep the foreground black/white if at all possible.
64 if (rfg == 7 || rfg == 8) {
65 if (fg == 7 || fg == 8)
66 candidate_score += 1; // Still grey. Meh.
67 else
68 candidate_score += 3;
71 if (candidate_score < score) {
72 score = candidate_score;
73 pair = candidate;
76 color_pairs[color] = pair;
77 return pair;
80 public:
82 void update_tile(int x, int y) {
83 const int ch = gps.screen[x*gps.dimy*4 + y*4 + 0];
84 const int fg = gps.screen[x*gps.dimy*4 + y*4 + 1];
85 const int bg = gps.screen[x*gps.dimy*4 + y*4 + 2];
86 const int bold = gps.screen[x*gps.dimy*4 + y*4 + 3];
88 const int pair = lookup_pair(make_pair(fg,bg));
90 if (ch == 219 && !bold) {
91 // It's █, which is used for borders and digging designations.
92 // A_REVERSE space looks better if it isn't completely tall.
93 // Which is most of the time, for me at least.
94 // █ <-- Do you see gaps?
95 // █
96 // The color can't be bold.
97 wattrset(*stdscr_p, COLOR_PAIR(pair) | A_REVERSE);
98 mvwaddstr(*stdscr_p, y, x, " ");
99 } else {
100 wattrset(*stdscr_p, COLOR_PAIR(pair) | (bold ? A_BOLD : 0));
101 #ifdef HAVE_NCURSESW
102 wchar_t chs[2] = {charmap[ch],0};
103 mvwaddwstr(*stdscr_p, y, x, chs);
104 #else
105 mvwaddch(*stdscr_p, y, x, ch);
106 #endif
110 void update_all() {
111 for (int x = 0; x < init.display.grid_x; x++)
112 for (int y = 0; y < init.display.grid_y; y++)
113 update_tile(x, y);
116 void render() {
117 refresh();
120 void resize(int w, int h) {
121 if (enabler.overridden_grid_sizes.size() == 0)
122 gps_allocate(w, h);
123 erase();
124 // Force a full display cycle
125 gps.force_full_display_count = 1;
126 enabler.flag |= ENABLERFLAG_RENDER;
129 void grid_resize(int w, int h) {
130 gps_allocate(w, h);
133 renderer_curses() {
134 init_curses();
137 bool get_mouse_coords(int &x, int &y) {
138 return false;
142 // Reads from getch, collapsing utf-8 encoding to the actual unicode
143 // character. Ncurses symbols (left arrow, etc.) are returned as
144 // positive values, unicode as negative. Error returns 0.
145 static int getch_utf8() {
146 int byte = wgetch(*stdscr_p);
147 if (byte == ERR) return 0;
148 if (byte > 0xff) return byte;
149 int len = decode_utf8_predict_length(byte);
150 if (!len) return 0;
151 string input(len,0); input[0] = byte;
152 for (int i = 1; i < len; i++) input[i] = wgetch(*stdscr_p);
153 return -decode_utf8(input);
156 void enablerst::eventLoop_ncurses() {
157 int x, y, oldx = 0, oldy = 0;
158 renderer_curses *renderer = static_cast<renderer_curses*>(this->renderer);
160 while (loopvar) {
161 // Check for terminal resize
162 getmaxyx(*stdscr_p, y, x);
163 if (y != oldy || x != oldx) {
164 pause_async_loop();
165 renderer->resize(x, y);
166 unpause_async_loop();
167 oldx = x; oldy = y;
170 // Deal with input
171 Uint32 now = SDL_GetTicks();
172 // Read keyboard input, if any, and transform to artificial SDL
173 // events for enabler_input.
174 int key;
175 bool paused_loop = false;
176 while ((key = getch_utf8())) {
177 if (!paused_loop) {
178 pause_async_loop();
179 paused_loop = true;
181 bool esc = false;
182 if (key == KEY_MOUSE) {
183 MEVENT ev;
184 if (getmouse(&ev) == OK) {
185 // TODO: Deal with curses mouse input. And turn it on above.
187 } else if (key == -27) { // esc
188 int second = getch_utf8();
189 if (second) { // That was an escape sequence
190 esc = true;
191 key = second;
194 add_input_ncurses(key, now, esc);
197 if (paused_loop)
198 unpause_async_loop();
200 // Run the common logic
201 do_frame();
206 //// libncursesw stub ////
208 extern "C" {
209 static void *handle;
210 WINDOW **stdscr_p;
212 int COLOR_PAIRS;
213 static int (*_erase)(void);
214 static int (*_wmove)(WINDOW *w, int y, int x);
215 static int (*_waddnstr)(WINDOW *w, const char *s, int n);
216 static int (*_nodelay)(WINDOW *w, bool b);
217 static int (*_refresh)(void);
218 static int (*_wgetch)(WINDOW *w);
219 static int (*_endwin)(void);
220 static WINDOW *(*_initscr)(void);
221 static int (*_raw)(void);
222 static int (*_keypad)(WINDOW *w, bool b);
223 static int (*_noecho)(void);
224 static int (*_set_escdelay)(int delay);
225 static int (*_curs_set)(int s);
226 static int (*_start_color)(void);
227 static int (*_init_pair)(short p, short fg, short bg);
228 static int (*_getmouse)(MEVENT *m);
229 static int (*_waddnwstr)(WINDOW *w, const wchar_t *s, int i);
231 static void *dlsym_orexit(const char *symbol, bool actually_exit = true) {
232 void *sym = dlsym(handle, symbol);
233 if (!sym) {
234 printf("Symbol not found: %s\n", symbol);
235 if (actually_exit)
236 exit(EXIT_FAILURE);
238 return sym;
241 int erase(void) {
242 return _erase();
244 int wmove(WINDOW *w, int y, int x) {
245 return _wmove(w, y, x);
247 int waddnstr(WINDOW *w, const char *s, int n) {
248 return _waddnstr(w, s, n);
250 int nodelay(WINDOW *w, bool b) {
251 return _nodelay(w, b);
253 int refresh(void) {
254 return _refresh();
256 int wgetch(WINDOW *w) {
257 return _wgetch(w);
259 int endwin(void) {
260 return _endwin();
262 WINDOW *initscr(void) {
263 return _initscr();
265 int raw(void) {
266 return _raw();
268 int keypad(WINDOW *w, bool b) {
269 return _keypad(w, b);
271 int noecho(void) {
272 return _noecho();
274 int set_escdelay(int delay) {
275 if (_set_escdelay)
276 return _set_escdelay(delay);
277 else
278 return 0;
280 int curs_set(int s) {
281 return _curs_set(s);
283 int start_color(void) {
284 return _start_color();
286 int init_pair(short p, short fg, short bg) {
287 return _init_pair(p, fg, bg);
289 int getmouse(MEVENT *m) {
290 return _getmouse(m);
292 int waddnwstr(WINDOW *w, const wchar_t *s, int n) {
293 return _waddnwstr(w, s, n);
296 void init_curses() {
297 static bool stub_initialized = false;
298 // Initialize the stub
299 if (!stub_initialized) {
300 stub_initialized = true;
301 // We prefer libncursesw, but we'll accept libncurses if we have to
302 handle = dlopen("libncursesw.so.5", RTLD_LAZY);
303 if (handle) goto opened;
304 handle = dlopen("libncursesw.so", RTLD_LAZY);
305 if (handle) goto opened;
306 puts("Didn't find any flavor of libncursesw, attempting libncurses");
307 sleep(5);
308 handle = dlopen("libncurses.so.5", RTLD_LAZY);
309 if (handle) goto opened;
310 handle = dlopen("libncurses.so", RTLD_LAZY);
311 if (handle) goto opened;
313 opened:
314 if (!handle) {
315 puts("Unable to open any flavor of libncurses!");
316 exit(EXIT_FAILURE);
318 // Okay, look up our symbols
319 int *pairs = (int*)dlsym_orexit("COLOR_PAIRS");
320 COLOR_PAIRS = *pairs;
321 stdscr_p = (WINDOW**)dlsym_orexit("stdscr");
322 _erase = (int (*)(void))dlsym_orexit("erase");
323 _wmove = (int (*)(WINDOW *w, int y, int x))dlsym_orexit("wmove");
324 _waddnstr = (int (*)(WINDOW *w, const char *s, int n))dlsym_orexit("waddnstr");
325 _nodelay = (int (*)(WINDOW *w, bool b))dlsym_orexit("nodelay");
326 _refresh = (int (*)(void))dlsym_orexit("refresh");
327 _wgetch = (int (*)(WINDOW *w))dlsym_orexit("wgetch");
328 _endwin = (int (*)(void))dlsym_orexit("endwin");
329 _initscr = (WINDOW *(*)(void))dlsym_orexit("initscr");
330 _raw = (int (*)(void))dlsym_orexit("raw");
331 _keypad = (int (*)(WINDOW *w, bool b))dlsym_orexit("keypad");
332 _noecho = (int (*)(void))dlsym_orexit("noecho");
333 _set_escdelay = (int (*)(int delay))dlsym_orexit("set_escdelay", false);
334 _curs_set = (int (*)(int s))dlsym_orexit("curs_set");
335 _start_color = (int (*)(void))dlsym_orexit("start_color");
336 _init_pair = (int (*)(short p, short fg, short bg))dlsym_orexit("init_pair");
337 _getmouse = (int (*)(MEVENT *m))dlsym_orexit("getmouse");
338 _waddnwstr = (int (*)(WINDOW *w, const wchar_t *s, int i))dlsym_orexit("waddnwstr");
341 // Initialize curses
342 if (!curses_initialized) {
343 curses_initialized = true;
344 WINDOW *new_window = initscr();
345 if (!new_window) {
346 puts("unable to create ncurses window - initscr failed!");
347 exit(EXIT_FAILURE);
349 // in some versions of curses, initscr does not update stdscr!
350 if (!*stdscr_p) *stdscr_p = new_window;
351 raw();
352 noecho();
353 keypad(*stdscr_p, true);
354 nodelay(*stdscr_p, true);
355 set_escdelay(25); // Possible bug
356 curs_set(0);
357 mmask_t dummy;
358 // mousemask(ALL_MOUSE_EVENTS, &dummy);
359 start_color();
360 init_pair(1, COLOR_WHITE, COLOR_BLACK);
362 atexit(endwin_void);
367 #endif