From 89952d7fa03d1bffb2a4e32dcb7a0693a085eda3 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 7 Jun 2022 21:38:13 -0400 Subject: [PATCH] Curses UI improvements. 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 | 2 +- configure.ac | 4 + src/curses.c | 261 +++++++++++++++++++++++++++++++++++++--------- src/cursesopt.opt | 6 ++ 4 files changed, 223 insertions(+), 50 deletions(-) diff --git a/common b/common index 18c520e..4d796da 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 18c520e1c6d44a8c68328f3ffa4d64434fb454b8 +Subproject commit 4d796dabafdee54a7b726013416730efaafa905e diff --git a/configure.ac b/configure.ac index 2c359a7..96dc8f2 100644 --- a/configure.ac +++ b/configure.ac @@ -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"], diff --git a/src/curses.c b/src/curses.c index bf97caf..eecf3a1 100644 --- a/src/curses.c +++ b/src/curses.c @@ -28,13 +28,23 @@ #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 } } } diff --git a/src/cursesopt.opt b/src/cursesopt.opt index fe0f942..fdbc74e 100644 --- a/src/cursesopt.opt +++ b/src/cursesopt.opt @@ -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. -- 2.43.2