]> git.draconx.ca Git - rrace.git/commitdiff
Curses UI improvements.
authorNick Bowler <nbowler@draconx.ca>
Wed, 8 Jun 2022 01:38:13 +0000 (21:38 -0400)
committerNick Bowler <nbowler@draconx.ca>
Wed, 8 Jun 2022 02:32:17 +0000 (22:32 -0400)
Add the goal area to the curses UI, and also leverage additional
configure tests to allow building against a wider variety of
curses implementations.

common
configure.ac
src/curses.c
src/cursesopt.opt

diff --git a/common b/common
index 18c520e1c6d44a8c68328f3ffa4d64434fb454b8..4d796dabafdee54a7b726013416730efaafa905e 160000 (submodule)
--- a/common
+++ b/common
@@ -1 +1 @@
-Subproject commit 18c520e1c6d44a8c68328f3ffa4d64434fb454b8
+Subproject commit 4d796dabafdee54a7b726013416730efaafa905e
index 2c359a723b16504e4cc772ae8e376acdac981741..96dc8f22fd3c1e30649a0a3cc22dcbef36204673 100644 (file)
@@ -39,6 +39,10 @@ AS_IF([test x"$with_curses" = x"yes" && x"$have_curses" != x"yes"],
   [AC_MSG_FAILURE([--with-curses requested but curses was not found])])
 AM_CONDITIONAL([HAVE_CURSES], [test x"$have_curses" = x"yes"])
 
+AM_COND_IF([HAVE_CURSES],
+[DX_CHECK_CURSES_FUNC([wresize], [0,0,0])
+DX_CHECK_CURSES_MOUSE_SUPPORT])
+
 # Checks for X11
 AC_PATH_XTRA
 AS_IF([test x"$no_x" != x"yes"],
index bf97caf5ab951a9bc213c96e1ffeea5534a6bfd9..eecf3a19e736a6f9e13552eea465fe130a9fb5dc 100644 (file)
 #include "cursesopt.h"
 #include "game.h"
 
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
 static const char *progname = "rrace";
 static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
 
+enum {
+       WINDOW_TILEBORDER,
+       WINDOW_TILEFILL,
+       WINDOW_AREA,
+       WINDOW_MAX,
+};
+
 static struct app_state {
        struct board board;
 
-       WINDOW *tile_border, *tile_fill;
+       WINDOW *gamewin[WINDOW_MAX], *goalwin[WINDOW_MAX];
 } state;
 
 static void print_version(void)
@@ -71,9 +81,10 @@ static void print_help(void)
        printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
 }
 
-static void
-draw_tile(struct app_state *state, unsigned colour, unsigned x, unsigned y)
+static void draw_tile(WINDOW **win, unsigned colour,
+                      unsigned x, unsigned y, unsigned start_column)
 {
+       WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL];
        int attr, ch;
        int w, h;
 
@@ -88,83 +99,190 @@ draw_tile(struct app_state *state, unsigned colour, unsigned x, unsigned y)
        case TILE_WHITE:  ch = '.'; attr |= A_BOLD; break;
        }
 
-       getmaxyx(state->tile_border, h, w);
+       getmaxyx(border, h, w);
        w = 2*(w+1)/2;
 
-       if (mvwin(state->tile_border, 2+h*y, 4+w*x) == ERR)
+       if (mvwin(border, 2+h*y, start_column+w*x) == ERR)
                return;
 
        if (colour != TILE_EMPTY) {
-               wattrset(state->tile_border, attr);
-               box(state->tile_border, 0, 0);
+               wattrset(border, attr);
+               box(border, 0, 0);
 
-               mvderwin(state->tile_fill, 1, 1);
-               wbkgdset(state->tile_fill, A_REVERSE|attr|ch);
-               werase(state->tile_fill);
+               mvderwin(fill, 1, 1);
+               wbkgdset(fill, A_REVERSE|attr|ch);
+               werase(fill);
        } else {
-               werase(state->tile_border);
+               werase(border);
        }
 
-       wnoutrefresh(state->tile_border);
+       wnoutrefresh(border);
 }
 
-static int curs_redraw_tile(struct app_state *state, unsigned x, unsigned y)
+static int
+redraw_tile(WINDOW **win, unsigned x, unsigned y, unsigned start_column,
+           uint_fast32_t bit0, uint_fast32_t bit1, uint_fast32_t bit2)
 {
        uint_fast32_t pos = board_position(x, y);
        unsigned char tile = 0;
 
-       if (state->board.game[0] & pos) tile |= 1;
-       if (state->board.game[1] & pos) tile |= 2;
-       if (state->board.game[2] & pos) tile |= 4;
+       if (bit0 & pos) tile |= 1;
+       if (bit1 & pos) tile |= 2;
+       if (bit2 & pos) tile |= 4;
        assert(tile < TILE_MAX);
 
-       draw_tile(state, tile, x, y);
+       draw_tile(win, tile, x, y, start_column);
        return tile;
 }
 
+static void redraw_area_border(WINDOW **win, unsigned x, unsigned sz)
+{
+       int w, h, tr = 0, rs = 0, br = 0, bs = 0, bl = 0;
+       WINDOW *area = win[WINDOW_AREA];
+
+       getmaxyx(stdscr, h, w);
+
+       if (h <= 2) {
+               bl = ACS_ULCORNER, br = ACS_URCORNER;
+       } else if (h <= 3*sz+2) {
+               bl = br = ACS_VLINE, bs = ' ';
+       }
+
+       if (w <= x+1) {
+               tr = ACS_ULCORNER, br = ACS_LLCORNER;
+       } else if (w <= 6*sz+x+2) {
+               tr = ACS_HLINE, rs = ' ';
+               br = br ? ' ' : ACS_HLINE;
+       }
+
+       wborder(area, 0, rs, 0, bs, 0, tr, bl, br);
+       wnoutrefresh(area);
+}
+
 static void curs_redraw_game(struct app_state *state, uint_fast32_t mask)
 {
+       uint_least32_t *gp = state->board.game;
        int i;
 
+       if (mask == -1)
+               redraw_area_border(state->gamewin, 2, 5);
+
        for (i = 0; i < 25; i++) {
                if (mask & 1) {
-                       curs_redraw_tile(state, i%5, i/5);
+                       redraw_tile(state->gamewin, i%5, i/5,
+                                   4, gp[0], gp[1], gp[2]);
                }
                mask >>= 1;
        }
 }
 
-static void curs_alloc_tiles(struct app_state *state)
+static void curs_redraw_goal(struct app_state *state, uint_fast32_t mask)
 {
-       int w, h, tilesz;
-       WINDOW *tile;
+       uint_least16_t *gp = state->board.goal;
+       int i, x, y;
 
-       getmaxyx(stdscr, h, w);
-       tilesz = (h - 4) / 5;
-       if (tilesz < 3)
-               tilesz = 3;
+       if (!state->goalwin[WINDOW_AREA])
+               return;
+
+       getbegyx(state->goalwin[WINDOW_AREA], y, x);
+       if (mask == -1)
+               redraw_area_border(state->goalwin, x, 3);
 
-       h = -1;
-       if (state->tile_border) {
-               getmaxyx(state->tile_border, h, w);
+       for (i = 0; i < 9; i++) {
+               if (mask & 1) {
+                       redraw_tile(state->goalwin, i%3, i/3,
+                                   x+2, gp[0], gp[1], gp[2]);
+               }
+               mask >>= 1;
        }
+}
 
-       if (h == tilesz) {
-               /* Nothing to do. */
-               return;
+static WINDOW *realloc_area(WINDOW **orig, int h, int w, int y, int x)
+{
+       WINDOW *win = *orig;
+
+       if (win) {
+#if HAVE_CURSES_WRESIZE
+               if (wresize(win, h, w) != ERR) {
+                       mvwin(win, y, x);
+                       return win;
+               }
+#endif
+               delwin(win);
+       }
+
+       if (w > 0 && h > 0)
+               return *orig = subwin(stdscr, h, w, y, x);
+       return *orig = NULL;
+}
+
+static void realloc_tiles(WINDOW **win, int h)
+{
+       WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL];
+       int w = 2*h - 1;
+
+       if (fill && border) {
+               int old_w, old_h;
+
+#if HAVE_CURSES_WRESIZE
+               if (wresize(fill, h-2, w-2) != ERR
+                   && wresize(border, h, w) != ERR)
+               {
+                       return;
+               }
+#endif
+
+               getmaxyx(border, old_h, old_w);
+               if (old_h == h)
+                       return;
        }
 
-       if (state->tile_border) {
-               delwin(state->tile_fill);
-               delwin(state->tile_border);
+       if (fill)
+               delwin(fill);
+       if (border)
+               delwin(border);
+
+       win[WINDOW_TILEBORDER] = border = newwin(h, w, 0, 0);
+       win[WINDOW_TILEFILL] = derwin(border, h-2, w-2, 1, 1);
+}
+
+static void setup_mainwin(struct app_state *state)
+{
+       int w, h, gamesz, goalsz, scr_w, scr_h, split;
+
+       getmaxyx(stdscr, scr_h, scr_w);
+
+       /* First try to fit the game tiles based on window height. */
+       gamesz = MAX(3, (scr_h - 4) / 5);
+
+       /* Adjust downward until we can fit smallest possible goal area. */
+       for (; split = 5+10*gamesz, gamesz > 3; gamesz--) {
+               if (split + 20 < scr_w)
+                       break;
        }
 
-       state->tile_border = tile = newwin(tilesz, 2*tilesz-1, 0, 0);
-       state->tile_fill = derwin(tile, tilesz-2, 2*tilesz-3, 1, 1);
+       /* Pick a goal size that will fit in the remaining area */
+       goalsz = MAX(3, (scr_w - split - 4) / 6);
+       if (goalsz >= gamesz)
+               goalsz = MAX(3, gamesz - 1);
+
+       realloc_tiles(state->gamewin, gamesz);
+       realloc_tiles(state->goalwin, goalsz);
+
+       /* Frame for game area */
+       w = MIN(scr_w-2, 3+10*gamesz);
+       h = MIN(scr_h-1, 2+5*gamesz);
+       realloc_area(&state->gamewin[WINDOW_AREA], h, w, 1, 2);
+
+       /* Frame for goal area */
+       w = MIN(scr_w-split, 3+6*goalsz);
+       h = MIN(scr_h-1, 2+3*goalsz);
+       realloc_area(&state->goalwin[WINDOW_AREA], h, w, 1, split);
 }
 
 static void app_initialize(int argc, char **argv)
 {
+       int enable_mouse = 1;
        int opt;
 
        if (argc > 0)
@@ -172,6 +290,12 @@ static void app_initialize(int argc, char **argv)
 
        while ((opt = getopt_long(argc, argv, SOPT_STRING, lopts, 0)) != -1) {
                switch (opt) {
+               case LOPT_MOUSE:
+                       enable_mouse = 2;
+                       break;
+               case LOPT_NO_MOUSE:
+                       enable_mouse = 0;
+                       break;
                case LOPT_VERSION:
                        print_version();
                        exit(EXIT_SUCCESS);
@@ -193,8 +317,17 @@ static void app_initialize(int argc, char **argv)
 
        cbreak();
        keypad(stdscr, TRUE);
-       mousemask(BUTTON1_PRESSED, NULL);
-       mouseinterval(0);
+       if (enable_mouse) {
+#if HAVE_CURSES_MOUSE_SET
+               mouse_set(BUTTON1_PRESSED);
+#elif HAVE_CURSES_MOUSEMASK
+               mousemask(BUTTON1_PRESSED, NULL);
+#endif
+#if HAVE_CURSES_MOUSEINTERVAL
+               mouseinterval(0);
+#endif
+       }
+
        noecho();
 
        init_pair(TILE_RED, COLOR_RED, COLOR_BLACK);
@@ -204,7 +337,7 @@ static void app_initialize(int argc, char **argv)
        init_pair(TILE_BLUE, COLOR_BLUE, COLOR_BLACK);
        init_pair(TILE_WHITE, COLOR_WHITE, COLOR_BLACK);
 
-       curs_alloc_tiles(&state);
+       setup_mainwin(&state);
        refresh();
 }
 
@@ -218,21 +351,47 @@ static void do_move(struct app_state *state, int x, int y)
        }
 }
 
-static void do_mouse(struct app_state *state, MEVENT *mev)
+#if HAVE_CURSES_MOUSE_SUPPORT
+static void do_mouse(struct app_state *state)
 {
-       if (mev->bstate == BUTTON1_PRESSED) {
-               int w, h, x, y;
+       unsigned long bstate;
+       int x, y;
+
+#if HAVE_CURSES_GETMOUSE_NCURSES
+       MEVENT mev;
+
+       if (getmouse(&mev) == ERR)
+               return;
+
+       x = mev.x, y = mev.y;
+       bstate = mev.bstate;
+#elif HAVE_CURSES_REQUEST_MOUSE_POS
+       request_mouse_pos();
+       x = MOUSE_X_POS;
+       y = MOUSE_Y_POS;
+
+       bstate = 0;
+       if (BUTTON_CHANGED(1)) {
+               switch (BUTTON_STATUS(1)) {
+               case BUTTON_RELEASED: bstate |= BUTTON1_RELEASED;
+               case BUTTON_PRESSED:  bstate |= BUTTON1_PRESSED;
+               }
+       }
+#endif
+       if (bstate == BUTTON1_PRESSED) {
+               int w, h;
 
                /* Determine size of the game area */
-               getmaxyx(state->tile_border, h, w);
+               getmaxyx(state->gamewin[WINDOW_TILEBORDER], h, w);
                w = 2*(w+1)/2;
 
-               if (mev->x < 4 || (x = mev->x - 4)/5 >= w) return;
-               if (mev->y < 2 || (y = mev->y - 2)/5 >= h) return;
+               if (x < 4 || (x -= 4)/5 >= w) return;
+               if (y < 2 || (y -= 2)/5 >= h) return;
 
                do_move(state, x/w, y/h);
        }
 }
+#endif
 
 int main(int argc, char **argv)
 {
@@ -240,24 +399,28 @@ int main(int argc, char **argv)
        app_initialize(argc, argv);
 
        curs_redraw_game(&state, -1);
+       curs_redraw_goal(&state, -1);
        refresh();
 
        while (1) {
                int c = getch();
-               MEVENT mev;
 
                switch (c) {
+#ifdef KEY_RESIZE
                case KEY_RESIZE:
-                       curs_alloc_tiles(&state);
+                       setup_mainwin(&state);
                        clear();
                        refresh();
                        curs_redraw_game(&state, -1);
+                       curs_redraw_goal(&state, -1);
                        refresh();
                        break;
+#endif
+#if HAVE_CURSES_MOUSE_SUPPORT
                case KEY_MOUSE:
-                       if (getmouse(&mev) != ERR)
-                               do_mouse(&state, &mev);
+                       do_mouse(&state);
                        break;
+#endif
                }
        }
 }
index fe0f942e180a98794895f22020a141cf9e58b232..fdbc74e7ced87efa822642f551523275d0793698 100644 (file)
@@ -1,3 +1,9 @@
+--mouse
+Attempt to enable mouse support in curses (default).
+
+--no-mouse
+Do not use the mouse, even if supported.
+
 --version
 Print a version message and then exit.