display.c (11218B)
1 /** 2 * @file display.c 3 * @author Dimitrije Dobrota 4 * @date 12 June 2022 5 * @brief This file handles ncurses library and UI 6 * 7 * This file is the main link with ncurses interface. It is responsible for 8 * starting and stopping ncurses library as well as managing it's resources and 9 * handling terminal resize by rebuilding window_T binary tree. It exports 10 * window_T MAIN_w as the entry point for other windows and display calls. It 11 * handles the display of menus and user input. 12 */ 13 #include <curses.h> 14 #include <stdlib.h> 15 #include <string.h> 16 17 #include "display.h" 18 #include "logic.h" 19 #include "pattern.h" 20 #include "utils.h" 21 #include "window.h" 22 23 window_T MAIN_w = NULL; 24 mmask_t mbitmask; 25 26 /** 27 * @brief Offset the cursor for ncurses WINDOW* 28 * 29 * @param oy: real number representing offset along y axis 30 * @param ox: real number representing offset along x axis 31 */ 32 void cursor_offset(WINDOW *win, int oy, int ox) { 33 int y, x; 34 getyx(win, y, x); 35 wmove(win, y + oy, x + ox); 36 } 37 38 /** 39 * @brief Print pattern_T 40 * 41 * @param y: pointer to int representing y cord where the pattern 42 * should be displayed. Will be inc read by the number of lines 43 * printed 44 * @param x: pointer to int representing x cord where the pattern 45 * should be displayed 46 */ 47 void print_pattern(WINDOW *win, pattern_T pattern, int *y, int x) { 48 (*y)++; 49 wmove(win, (*y)++, x); 50 for (char *c = pattern->cells; *c != '\0'; c++) { 51 if (*c == ' ') { 52 wmove(win, (*y)++, x); 53 continue; 54 } 55 int val = *c - '0'; 56 wattrset(win, COLOR_PAIR(val + 2)); 57 print_cell(win, " "); 58 } 59 } 60 61 /** 62 * @brief Get user input of different type up to the specified size 63 * 64 * @param buffer: buffer where the input will be stored 65 * @param size: maximum number of characters in the input 66 * @param crit: function that checks the validity of the character 67 */ 68 int input(WINDOW *win, char *buffer, int size, input_f crit) { 69 int CLINES = LINES, CCOLS = COLS; 70 int ch, read = strlen(buffer); 71 72 while ((ch = getch()) != '\n') { 73 switch (ch) { 74 case 27: 75 flushinp(); 76 buffer[read] = '\0'; 77 return 100; 78 case KEY_BACKSPACE: 79 case KEY_LEFT: 80 if (read > 0) { 81 cursor_offset(win, 0, -1); 82 wprintw(win, " "); 83 cursor_offset(win, 0, -1); 84 read--; 85 } 86 break; 87 case KEY_UP: 88 buffer[read] = '\0'; 89 return -1; 90 case KEY_DOWN: 91 buffer[read] = '\0'; 92 return +1; 93 default: 94 if (read < size && crit && crit(ch)) { 95 wprintw(win, "%c", ch); 96 buffer[read++] = ch; 97 } 98 break; 99 } 100 if (is_term_resized(CLINES, CCOLS)) { 101 buffer[read] = '\0'; 102 return -100; 103 } 104 wrefresh(win); 105 } 106 buffer[read] = '\0'; 107 return 0; 108 } 109 110 /** 111 * @brief Given a array of struct imenu_T, display a menu where user will enter 112 * all of the information required from the array 113 */ 114 int display_imenu(window_T wind, struct imenu_T *items, int size) { 115 WINDOW *win; 116 int y_offset; 117 int current = 0; 118 119 for (int i = 0; i < size; i++) 120 if (!items[i].buffer) 121 items[i].buffer = calloc(5, sizeof(char)); 122 123 int maxi = 0, len = 0; 124 for (int i = 0; i < size; i++) 125 if ((len = strlen(items[i].message)) > maxi) 126 maxi = len; 127 128 curs_set(1); 129 window_clear_noRefresh(wind); 130 redraw:; 131 win = window_win(wind); 132 y_offset = wcenter_vertical(wind, size * 2 - 1); 133 134 for (int i = 0; i < size; i++) { 135 wcenter_horizontal(wind, y_offset + 2 * i, 20); 136 wprintw(win, "%*s: %s", -maxi, items[i].message, items[i].buffer); 137 } 138 wrefresh(win); 139 140 while (TRUE) { 141 for (int i = 0; i < size; i++) { 142 if (current != i) 143 continue; 144 145 wcenter_horizontal(wind, y_offset + 2 * i, 20); 146 wprintw(win, "%*s: %s", -maxi, items[i].message, items[i].buffer); 147 switch (input(win, items[i].buffer, items[i].size, items[i].crit)) { 148 case -1: 149 current--; 150 break; 151 case 0: 152 if (++current == size) { 153 curs_set(0); 154 return 1; 155 } 156 break; 157 case 1: 158 current++; 159 break; 160 case 100: 161 curs_set(0); 162 return 0; 163 case -100: 164 HANDLE_RESIZE; 165 goto redraw; 166 } 167 CLAMP(current, 0, size - 1); 168 } 169 } 170 171 curs_set(0); 172 } 173 174 /** 175 * @brief Display the title of the game in the center of the screen if it can 176 * fit 177 */ 178 void display_title(window_T wind, int y) { 179 WINDOW *win = window_win(wind); 180 title.height = (!title.height) ? pattern_height(&title) : title.height; 181 title.width = (!title.width) ? pattern_width(&title) : title.width; 182 183 int max_w = window_width(wind); 184 if (title.width * 2 < max_w) 185 print_pattern(win, &title, &y, (max_w - title.width * 2) / 2); 186 wrefresh(win); 187 } 188 189 /** 190 * @brief Given a array of struct menu_T, display all menu items and allow user 191 * to chose one of them after which appropriate callback function will be called 192 * 193 * @param title: 1 if title should be displayed 194 */ 195 void display_menu(window_T wind, char *name, struct menu_T *items, int size, 196 int title) { 197 WINDOW *win; 198 int current = 0; 199 200 char sep[] = ">--------<"; 201 int sep_len = strlen(sep); 202 203 int maxi = 0, len = 0; 204 for (int i = 0; i < size; i++) 205 if ((len = strlen(items[i].name)) > maxi) 206 maxi = len; 207 208 window_set_title(wind, name); 209 window_clear_noRefresh(wind); 210 211 int d_start, d_size, y_offset; 212 213 d_start = 0; 214 redraw:; 215 int CLINES = LINES, CCOLS = COLS; 216 win = window_win(wind); 217 218 d_size = window_height(wind) / 2 - 2; 219 220 CLAMP(d_size, 0, size); 221 y_offset = wcenter_vertical(wind, d_size * 2) + 1; 222 223 while (TRUE) { 224 window_clear(wind); 225 if (title) 226 display_title(wind, title); 227 228 if (current == -1) 229 d_start--; 230 231 if (current == d_size) 232 d_start++; 233 234 CLAMP(d_start, 0, size - d_size); 235 CLAMP(current, 0, d_size - 1); 236 237 if (!size) { 238 char *message = "NOTHING TO DISPLAY HERE!"; 239 wcenter_horizontal(wind, y_offset, strlen(message)); 240 waddstr(win, message); 241 wrefresh(win); 242 nodelay(stdscr, 0); 243 getch(); 244 nodelay(stdscr, 1); 245 return; 246 } 247 248 if (d_size < size && d_start == 0) { 249 wattrset(win, COLOR_PAIR(0)); 250 wcenter_horizontal(wind, y_offset - 1, sep_len); 251 wprintw(win, "%s", sep); 252 } 253 254 int index = d_start; 255 for (int i = 0; i < d_size; i++, index++) { 256 wattrset(win, COLOR_PAIR(i == current ? 1 : 0)); 257 wcenter_horizontal(wind, y_offset + i * 2, maxi); 258 wprintw(win, "%s", items[index].name); 259 } 260 261 if (d_size < size && d_start == size - d_size) { 262 wattrset(win, COLOR_PAIR(0)); 263 wcenter_horizontal(wind, y_offset + d_size * 2 - 1, sep_len); 264 wprintw(win, "%s", sep); 265 } 266 267 wrefresh(win); 268 269 while (TRUE) { 270 int c = getch(); 271 if (c == 'k' || c == 'w' || c == KEY_UP) { 272 current--; 273 break; 274 } else if (c == 'j' || c == 's' || c == KEY_DOWN) { 275 current++; 276 break; 277 } else if (c == '\n') { 278 int index = d_start + current; 279 wattrset(win, COLOR_PAIR(0)); 280 items[index].callback(items[index].name, index); 281 return; 282 } else if (c == 27) { 283 flushinp(); 284 return; 285 } 286 if (is_term_resized(CLINES, CCOLS)) { 287 HANDLE_RESIZE; 288 goto redraw; 289 } 290 } 291 } 292 } 293 294 /** 295 * @brief Initialize ncurses library and set colors based on display mode 296 * selected while compiling 297 */ 298 void curses_start(void) { 299 if (initscr() == NULL) 300 err("Fatal Error: initscr()"); 301 302 window_settings(stdscr); 303 304 if (!has_colors() || start_color() != OK) 305 err("Fatal Error: Terminal does not support colors"); 306 307 use_default_colors(); 308 309 curs_set(0); 310 noecho(); 311 nodelay(stdscr, 1); 312 313 init_pair(0, COLOR_WHITE, -1); 314 init_pair(1, COLOR_RED, -1); 315 316 #ifndef NO_UNICODE 317 init_pair(2, COLOR_WHITE, -1); 318 init_pair(3, COLOR_WHITE, -1); 319 init_pair(4, COLOR_RED, -1); 320 321 init_pair(5, COLOR_WHITE, COLOR_BLUE); 322 init_pair(6, COLOR_WHITE, COLOR_BLUE); 323 init_pair(7, COLOR_RED, COLOR_BLUE); 324 325 init_pair(8, COLOR_WHITE, COLOR_YELLOW); 326 init_pair(9, COLOR_WHITE, COLOR_YELLOW); 327 init_pair(10, COLOR_RED, COLOR_YELLOW); 328 #else 329 init_pair(2, COLOR_WHITE, -1); 330 init_pair(3, COLOR_WHITE, COLOR_WHITE); 331 init_pair(4, COLOR_RED, COLOR_RED); 332 333 init_pair(5, COLOR_BLUE, -1); 334 init_pair(6, COLOR_BLUE, COLOR_WHITE); 335 init_pair(7, COLOR_BLUE, COLOR_RED); 336 337 init_pair(8, COLOR_WHITE, COLOR_YELLOW); 338 init_pair(9, COLOR_WHITE, COLOR_BLUE); 339 init_pair(10, COLOR_RED, COLOR_BLACK); 340 #endif 341 342 #if defined NCURSES_MOUSE_VERSION && !defined NO_MOUSE 343 mbitmask = mousemask(BUTTON1_CLICKED, NULL); 344 #endif 345 } 346 347 /** 348 * @brief Handle the reboiling of window_T binary tree after terminal resize 349 * 350 * This function MUST be called after a resize has been detected by any function 351 */ 352 void handle_winch(int sig) { 353 endwin(); 354 refresh(); 355 clear(); 356 357 window_init(MAIN_w); 358 window_update_children(MAIN_w); 359 } 360 361 /** 362 * @brief Start ncurses display and export MAIN_w 363 */ 364 void display_start(void) { 365 curses_start(); 366 MAIN_w = window_init(window_new()); 367 } 368 369 /** 370 * @brief Stop ncurses display and cleanup 371 */ 372 void display_stop(void) { 373 window_free(MAIN_w); 374 endwin(); 375 } 376 377 /** 378 * @brief Display help menu using all the patterns from all the groups in 379 * pattern_groups array 380 */ 381 void display_patterns(window_T wind) { 382 int y_start = 2, x_start = 2; 383 int indent = 2; 384 385 window_set_title(wind, "Help"); 386 window_clear_noRefresh(wind); 387 redraw:; 388 int CLINES = LINES, CCOLS = COLS; 389 WINDOW *win = window_win(wind); 390 int ph = window_height(wind), pw = window_width(wind); 391 int y, x, maxi, max_x; 392 393 x = x_start; 394 for (int i = 0; i < pattern_groups_s; i++) { 395 wattrset(win, 0); 396 pattern_group_T group = pattern_groups[i]; 397 y = y_start; 398 399 if ((maxi = strlen(group.name)) + x >= pw) 400 continue; 401 mvwprintw(win, y++, x, "%s:", group.name); 402 403 y++; 404 for (int j = 0; j < group.size; j++) { 405 struct pattern_T p = group.pattern[j]; 406 p.height = (!p.height) ? pattern_height(&p) : p.height; 407 p.width = (!p.width) ? pattern_width(&p) : p.width; 408 409 max_x = MAX(p.width * 2, strlen(p.name) + 3); 410 411 if (y + p.height + 4 >= ph) { 412 y = 3; 413 x += (maxi + 2 + indent * 1); 414 maxi = max_x; 415 } 416 417 if (x + max_x + 5 + indent >= pw) { 418 break; 419 } 420 421 wattrset(win, 0); 422 if (p.height != 0) { 423 mvwprintw(win, y++, x, "- %s: ", p.name); 424 print_pattern(win, &p, &y, x + indent); 425 y += 2; 426 } else { 427 if (*p.name) { 428 if (*p.cells != '\0') { 429 mvwprintw(win, y, x, " %s: ", p.name); 430 max_x += 2; 431 } else 432 mvwprintw(win, y, x, "%s: ", p.name); 433 wprintw(win, "%s", p.cells); 434 } 435 y++; 436 } 437 438 maxi = MAX(maxi, max_x); 439 } 440 x += (maxi + 2 + indent * 2); 441 442 wattrset(win, 0); 443 for (int i = 1; i <= ph; i++) 444 mvwprintw(win, i, x, "|"); 445 446 x += 2 * indent; 447 if (x >= pw) 448 break; 449 } 450 451 wrefresh(win); 452 while (true) { 453 switch (getch()) { 454 case 27: 455 case 'q': 456 case 'Q': 457 case '\n': 458 flushinp(); 459 goto end; 460 } 461 if (is_term_resized(CLINES, CCOLS)) { 462 HANDLE_RESIZE; 463 goto redraw; 464 } 465 } 466 end:; 467 }