gol

Implementation of Conway's Game of Life writen in C
git clone git://git.dimitrijedobrota.com/gol.git
Log | Files | Refs | README

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 }