From: Nick Bowler Date: Sat, 18 Jun 2022 01:53:25 +0000 (-0400) Subject: Use gen-tree.awk to produce curses menu labels. X-Git-Url: http://git.draconx.ca/gitweb/rrace.git/commitdiff_plain/2fe01fae44c88046139ddc51c8521d5962ed9ac7 Use gen-tree.awk to produce curses menu labels. Instead of hardcoding the labels, we can use gen-tree.awk to produce a compact description. --- diff --git a/Makefile.am b/Makefile.am index e5efa40..d662b18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,9 +43,8 @@ rrace_motif_LDADD = $(libmotifmain_a_OBJECTS) $(libmotifui_a_OBJECTS) \ $(rrace_motif_OBJECTS): $(gnulib_headers) EXTRA_LIBRARIES += libcursesmain.a -libcursesmain_a_SOURCES = src/curses.c -$(libcursesmain_a_OBJECTS): $(gnulib_headers) -$(libcursesmain_a_OBJECTS): src/cursesopt.h +libcursesmain_a_SOURCES = src/curses.c src/cursmenu.c +$(libcursesmain_a_OBJECTS): $(gnulib_headers) src/cursesopt.h src/cursmenu.h EXTRA_LIBRARIES += libmotifmain.a libmotifmain_a_SOURCES = src/motif.c @@ -71,7 +70,7 @@ $(OPTFILES:.opt=.h): $(DX_BASEDIR)/scripts/gen-options.awk DISTCLEANFILES += $(OPTFILES:.opt=.h) EXTRA_DIST += $(DX_BASEDIR)/scripts/gen-options.awk $(OPTFILES) -TREEFILES = src/motifgui.dat +TREEFILES = src/cursmenu.dat src/motifgui.dat .dat.h: $(AM_V_GEN) :; { \ $(AWK) -f $(DX_BASEDIR)/scripts/gen-tree.awk $< && \ diff --git a/src/.gitignore b/src/.gitignore index 977700c..8200e95 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,4 +1,5 @@ /cursesopt.h +/cursmenu.h /motifgui.h /motifopt.h /motifstr.h diff --git a/src/curses.c b/src/curses.c index 7497395..cdaf396 100644 --- a/src/curses.c +++ b/src/curses.c @@ -25,11 +25,9 @@ #include "help.h" #include "version.h" -#include "cursesopt.h" -#include "game.h" -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#include "cursesui.h" +#include "cursesopt.h" enum { GAME_YPOS = 1 // top row of game and goal areas. @@ -38,40 +36,7 @@ enum { static const char *progname = "rrace"; static const struct option lopts[] = { LOPTS_INITIALIZER, {0} }; -enum { - WINDOW_TILEBORDER, - WINDOW_TILEFILL, - WINDOW_AREA, - WINDOW_MAX, -}; - -/* Colour pair enumeration */ -enum { - /* Pairs 1-6 correspond to tile colours */ - RR_COLOUR_CURSOR = TILE_MAX, // black on black, for the cursor - RR_COLOUR_TOOLBAR, // cyan on black (use reverse video) - RR_COLOUR_MAX -}; - -static struct app_state { - struct board board; - - WINDOW *gamewin[WINDOW_MAX], *goalwin[WINDOW_MAX]; - WINDOW *toolbar, *timer; - int last_input; - - /* Most recently displayed timer value, for screen redraw. */ - uint_least32_t timer_ms; - - /* Location of the keyboard cursor */ - int_least8_t cursor; - - /* If true, the goal will be displayed over the main play area. */ - uint_least8_t view_goal_on_game; - - /* Clicked toolbar item */ - uint_least8_t toolbar_click; -} state; +static struct app_state state; static void print_version(void) { @@ -110,7 +75,7 @@ static void print_help(void) static void draw_tile(WINDOW **win, unsigned colour, unsigned selected, unsigned x, unsigned y, unsigned start_column) { - WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL]; + WINDOW *border = win[PLAYWIN_TILEBORDER], *fill = win[PLAYWIN_TILEFILL]; int w, h, attr, ch, bc = selected ? '#' : 0; assert(colour < TILE_MAX); @@ -123,7 +88,7 @@ static void draw_tile(WINDOW **win, unsigned colour, unsigned selected, case TILE_BLUE: ch = 'o'; attr |= A_BOLD; break; case TILE_WHITE: ch = '.'; attr |= A_BOLD; break; - case TILE_EMPTY: attr = A_BOLD|COLOR_PAIR(RR_COLOUR_CURSOR); + case TILE_EMPTY: attr = A_BOLD|COLOR_PAIR(RR_COLOUR_SHADOW); } getmaxyx(border, h, w); @@ -168,7 +133,7 @@ redraw_tile(WINDOW **win, unsigned x, unsigned y, unsigned start_column, 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]; + WINDOW *area = win[PLAYWIN_AREA]; getmaxyx(stdscr, h, w); @@ -220,10 +185,10 @@ static void curs_redraw_goal(struct app_state *state, uint_fast32_t mask) uint_least16_t *gp = state->board.goal; int i, x, y; - if (!state->goalwin[WINDOW_AREA]) + if (!state->goalwin[PLAYWIN_AREA]) return; - getbegyx(state->goalwin[WINDOW_AREA], y, x); + getbegyx(state->goalwin[PLAYWIN_AREA], y, x); if (mask == -1) redraw_area_border(state->goalwin, x, 3); @@ -257,7 +222,7 @@ static WINDOW *realloc_area(WINDOW **orig, int h, int w, int y, int x) static void realloc_tiles(WINDOW **win, int h) { - WINDOW *border = win[WINDOW_TILEBORDER], *fill = win[WINDOW_TILEFILL]; + WINDOW *border = win[PLAYWIN_TILEBORDER], *fill = win[PLAYWIN_TILEFILL]; int w = 2*h - 1; if (fill && border) { @@ -281,60 +246,8 @@ static void realloc_tiles(WINDOW **win, int h) if (border) delwin(border); - win[WINDOW_TILEBORDER] = border = newwin(h, w, 0, 0); - win[WINDOW_TILEFILL] = derwin(border, h-2, w-2, 1, 1); -} - -/* - * Given the toolbar function number (between 1 and 10, inclusive), and the - * total width of the screen, return the character position for the start of - * its label display. - * - * The intention is to divide the width of the screen into 10 roughly - * equally-sized areas, spreading out the remainder so that the width of - * each label is a monotone increasing function of the total width. - * - * The minimum size of a label is 6 characters (2 of which are used for the - * number indicator) - */ -static int toolbar_xpos(int i, int total_width) -{ - int button_width = MAX(6, total_width/10); - int rem = total_width - 10*button_width; - int pos = (i-1)*button_width; - - switch (rem) { - case 9: pos += i > 6; - case 8: pos += i > 2; - case 7: pos += i > 7; - case 6: pos += i > 3; - case 5: pos += i > 8; - case 4: pos += i > 4; - case 3: pos += i > 9; - case 2: pos += i > 5; - } - - return pos; -} - -static void draw_toolbar(struct app_state *state) -{ - WINDOW *toolbar = state->toolbar; - int i, w, lw; - - getmaxyx(toolbar, i, w); - werase(toolbar); - - lw = MAX(6, w/10); - mvwprintw(toolbar, 0, toolbar_xpos( 1, w)+2, "%.*s", lw, "Help"); - mvwprintw(toolbar, 0, toolbar_xpos( 2, w)+2, "%.*s", lw, "NewGame"); - mvwprintw(toolbar, 0, toolbar_xpos(10, w)+2, "%.*s", lw, "Exit"); - - mvwchgat(toolbar, 0, 0, -1, A_REVERSE, RR_COLOUR_TOOLBAR, 0); - for (i = 1; i <= 10; i++) - mvwprintw(toolbar, 0, toolbar_xpos(i, w), "%2d", i); - - wnoutrefresh(state->toolbar); + win[PLAYWIN_TILEBORDER] = border = newwin(h, w, 0, 0); + win[PLAYWIN_TILEFILL] = derwin(border, h-2, w-2, 1, 1); } static void setup_mainwin(struct app_state *state) @@ -363,12 +276,12 @@ static void setup_mainwin(struct app_state *state) /* Frame for game area */ w = MIN(scr_w-2, 3+10*gamesz); h = MIN(scr_h-GAME_YPOS, 2+5*gamesz); - realloc_area(&state->gamewin[WINDOW_AREA], h, w, GAME_YPOS, 2); + realloc_area(&state->gamewin[PLAYWIN_AREA], h, w, GAME_YPOS, 2); /* Frame for goal area */ w = MIN(scr_w-split, 3+6*goalsz); h = MIN(scr_h-GAME_YPOS, 2+3*goalsz); - realloc_area(&state->goalwin[WINDOW_AREA], h, w, GAME_YPOS, split); + realloc_area(&state->goalwin[PLAYWIN_AREA], h, w, GAME_YPOS, split); /* Status area */ w = MAX(0, scr_w-split-1); @@ -376,7 +289,7 @@ static void setup_mainwin(struct app_state *state) /* Toolbar */ realloc_area(&state->toolbar, 1, scr_w, scr_h-1, 0); - draw_toolbar(state); + curs_draw_toolbar(state); } static void app_initialize(int argc, char **argv) @@ -457,7 +370,7 @@ static void app_initialize(int argc, char **argv) init_pair(TILE_GREEN, COLOR_GREEN, COLOR_BLACK); init_pair(TILE_BLUE, COLOR_BLUE, COLOR_BLACK); init_pair(TILE_WHITE, COLOR_WHITE, COLOR_BLACK); - init_pair(RR_COLOUR_CURSOR, COLOR_BLACK, COLOR_BLACK); + init_pair(RR_COLOUR_SHADOW, COLOR_BLACK, COLOR_BLACK); init_pair(RR_COLOUR_TOOLBAR, COLOR_CYAN, COLOR_BLACK); setup_mainwin(&state); @@ -474,7 +387,7 @@ static void update_timer(struct app_state *state, uint_fast32_t ms) mvwprintw(state->timer, 0, 0, "Time: %u:%.2u.%.3u", min, sec, (unsigned)ms); wclrtoeol(state->timer); - wrefresh(state->timer); + wnoutrefresh(state->timer); } static uint_fast32_t do_move(struct app_state *state, int x, int y) @@ -509,7 +422,7 @@ static void do_reset_cursor(struct app_state *state) state->cursor = 5*state->board.y + state->board.x; } -static void do_new_game(struct app_state *state) +void curs_new_game(struct app_state *state) { game_reset(&state->board); @@ -523,42 +436,7 @@ static void do_new_game(struct app_state *state) game_begin(&state->board); } -static void do_function(struct app_state *state, unsigned func) -{ - switch (func) { - case 2: - do_new_game(state); - break; - case 10: - endwin(); - exit(0); - } -} - #if HAVE_CURSES_MOUSE_SUPPORT -/* - * Returns the toolbar function (1 through 10) under the given x, y screen - * coordinates, or 0 if the coordinates are outside of the toolbar. - */ -static int mouse_toolbar_function(struct app_state *state, int x, int y) -{ - int toolbar_x, toolbar_y, toolbar_w, toolbar_h, i; - - getbegyx(state->toolbar, toolbar_y, toolbar_x); - getmaxyx(state->toolbar, toolbar_h, toolbar_w); - - if ((void)toolbar_x, y != toolbar_y) - return 0; - - /* OK, selected a button, determine which one */ - for (i = 10; i > 1; i--) { - if ((void)toolbar_h, x >= toolbar_xpos(i, toolbar_w)) - break; - } - - return i; -} - /* * Given x, y as screen coordinates, record which (if any) toolbar function * label is at that position, to be performed later. @@ -567,19 +445,21 @@ static int mouse_toolbar_function(struct app_state *state, int x, int y) */ static int press_toolbar(struct app_state *state, int x, int y) { - return state->toolbar_click = mouse_toolbar_function(state, x, y); + return state->toolbar_click = curs_toolbar_mouse_func(state, x, y); } /* + * Given x, y as screen coordinates, perform the toolbar action. + * * Perform the action previously recorded by press_toolbar, if and only if * the x, y screen coordinates correspond to the same function. */ static void release_toolbar(struct app_state *state, int x, int y) { - int func = mouse_toolbar_function(state, x, y); + int func = curs_toolbar_mouse_func(state, x, y); if (func && state->toolbar_click == func) { - do_function(state, func); + curs_execute_function(state, func); } state->toolbar_click = 0; @@ -598,8 +478,8 @@ static int press_tile(struct app_state *state, int x, int y) int game_x, game_y, tile_w, tile_h; uint_fast32_t cursor_mask, move_mask; - getbegyx(state->gamewin[WINDOW_AREA], game_y, game_x); - getmaxyx(state->gamewin[WINDOW_TILEBORDER], tile_h, tile_w); + getbegyx(state->gamewin[PLAYWIN_AREA], game_y, game_x); + getmaxyx(state->gamewin[PLAYWIN_TILEBORDER], tile_h, tile_w); tile_w += tile_w & 1; /* special case the left spacer column */ @@ -746,12 +626,12 @@ static void do_keystroke(struct app_state *state, int c) /* ESC+# keys */ if (last_input == '\33' && c >= '0' && c <= '9') { - do_function(state, (c -= '0') == 0 ? 10 : c); + curs_execute_function(state, (c -= '0') == 0 ? 10 : c); } /* F# keys */ if (c >= KEY_F(1) && c <= KEY_F(10)) { - do_function(state, c - KEY_F0); + curs_execute_function(state, c - KEY_F0); } } @@ -768,8 +648,9 @@ void do_mainloop(struct app_state *state) refresh(); curs_redraw_game(state, -1); curs_redraw_goal(state, -1); - draw_toolbar(state); + curs_draw_toolbar(state); update_timer(state, state->timer_ms); + doupdate(); break; #endif #if HAVE_CURSES_MOUSE_SUPPORT @@ -782,8 +663,10 @@ void do_mainloop(struct app_state *state) case ERR:; } - if (state->board.x <= 4) + if (state->board.x <= 4) { update_timer(state, game_elapsed(&state->board)); + doupdate(); + } } int main(int argc, char **argv) @@ -791,7 +674,7 @@ int main(int argc, char **argv) setlocale(LC_ALL, ""); app_initialize(argc, argv); - do_new_game(&state); + curs_new_game(&state); while (1) do_mainloop(&state); abort(); diff --git a/src/cursesui.h b/src/cursesui.h new file mode 100644 index 0000000..50d30fe --- /dev/null +++ b/src/cursesui.h @@ -0,0 +1,85 @@ +/* + * Curses UI for slide puzzle game + * Copyright © 2022 Nick Bowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RRACE_CURSESUI_H_ +#define RRACE_CURSESUI_H_ + +#include "game.h" + +/* Array indices for gamewin and goalwin window sets. */ +enum { + PLAYWIN_TILEBORDER, + PLAYWIN_TILEFILL, + PLAYWIN_AREA, + PLAYWIN_MAX +}; + +/* Colour pair enumeration */ +enum { + /* Pairs 1-6 correspond to tile colours; use TILE_xxx enumerations. */ + RR_COLOUR_SHADOW = TILE_MAX, /* black on black (use bold) */ + RR_COLOUR_TOOLBAR, /* cyan on black (use reverse video) */ + RR_COLOUR_MAX +}; + +struct app_state { + struct board board; + + WINDOW *gamewin[PLAYWIN_MAX], *goalwin[PLAYWIN_MAX]; + WINDOW *toolbar, *timer; + + /* Previous input returned from getch, for 2-character sequences */ + int last_input; + + /* Most recently displayed timer value, for screen redraw. */ + uint_least32_t timer_ms; + + /* Location of the keyboard cursor */ + int_least8_t cursor; + + /* If true, the goal will be displayed over the main play area. */ + uint_least8_t view_goal_on_game; + + /* Clicked toolbar item */ + uint_least8_t toolbar_click; + + /* Current state of the toolbar menu */ + uint_least8_t toolbar_state; +}; + +void curs_new_game(struct app_state *state); + +void curs_reenable_dialog(struct app_state *state, const char *heading); + +void curs_draw_toolbar(struct app_state *state); +void curs_execute_function(struct app_state *state, int function); + +#if HAVE_CURSES_MOUSE_SUPPORT +int curs_toolbar_mouse_func(struct app_state *state, int x, int y); +#else +static inline int +curs_toolbar_mouse_func(struct app_state *state, int x, int y) +{ + return 0; +} +#endif + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#endif diff --git a/src/cursmenu.c b/src/cursmenu.c new file mode 100644 index 0000000..e665a32 --- /dev/null +++ b/src/cursmenu.c @@ -0,0 +1,156 @@ +/* + * Curses UI for slide puzzle game + * Copyright © 2022 Nick Bowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "cursesui.h" +#include "cursmenu.h" + +enum { + ACTION_EXIT = 0x80, + ACTION_ABOUT, + ACTION_NEWGAME, + ACTION_MAX +}; + +static const struct menuitem { + uint_least8_t label, position, action; +} menu[] = { FUNC_INITIALIZER }; + +static void do_function(struct app_state *state, unsigned action) +{ + if (action < sizeof menu / sizeof menu[0]) { + state->toolbar_state = action; + curs_draw_toolbar(state); + doupdate(); + } + + switch (action) { + case ACTION_NEWGAME: + curs_new_game(state); + break; + case ACTION_EXIT: + endwin(); + exit(0); + }; +} + +void curs_execute_function(struct app_state *state, int func) +{ + int i; + + for (i = state->toolbar_state; menu[i].position; i++) { + if (func == menu[i].position) { + do_function(state, menu[i].action); + return; + } + } +} + +/* + * Return the width of the toolbar and the target width of toolbar labels. + */ +static int toolbar_width(WINDOW *toolbar, int *label_width) +{ + int w, h; + + getmaxyx(toolbar, h, w); + *label_width = MAX(6, w/10); + return (void)h, w; +} + +/* + * Given the toolbar function number (between 1 and 10, inclusive), and the + * total width of the screen, return the character position for the start of + * its label display. + * + * The intention is to divide the width of the screen into 10 roughly + * equally-sized areas, spreading out the remainder so that the width of + * each label is a monotone increasing function of the total width. + * + * The minimum size of a label is 6 characters (2 of which are used for the + * number indicator) + */ +static int toolbar_xpos(int i, int total_width, int label_width) +{ + int rem = total_width - 10*label_width; + int pos = (i-1)*label_width; + + switch (rem) { + case 9: pos += i > 6; + case 8: pos += i > 2; + case 7: pos += i > 7; + case 6: pos += i > 3; + case 5: pos += i > 8; + case 4: pos += i > 4; + case 3: pos += i > 9; + case 2: pos += i > 5; + } + + return pos; +} + +/* + * Redraw the current set of toolbar labels. + */ +void curs_draw_toolbar(struct app_state *state) +{ + WINDOW *toolbar = state->toolbar; + int i, w, lw; + + werase(toolbar); + + w = toolbar_width(toolbar, &lw); + for (i = state->toolbar_state; menu[i].position; i++) { + int pos = toolbar_xpos(menu[i].position, w, lw); + mvwprintw(toolbar, 0, pos+2, "%.*s", lw, strtab+menu[i].label); + } + + mvwchgat(toolbar, 0, 0, -1, A_REVERSE, RR_COLOUR_TOOLBAR, 0); + for (i = 1; i <= 10; i++) { + mvwprintw(toolbar, 0, toolbar_xpos(i, w, lw), "%2d", i); + } + + wnoutrefresh(state->toolbar); +} + +#if HAVE_CURSES_MOUSE_SUPPORT +/* + * Returns the toolbar function (1 through 10) under the given x, y screen + * coordinates, or 0 if the coordinates are outside of the toolbar. + */ +int curs_toolbar_mouse_func(struct app_state *state, int x, int y) +{ + int toolbar_x, toolbar_y, i, w, lw; + + getbegyx(state->toolbar, toolbar_y, toolbar_x); + if ((void)toolbar_x, y != toolbar_y) + return 0; + + /* OK, selected a button, determine which one */ + w = toolbar_width(state->toolbar, &lw); + for (i = 10; i > 1; i--) { + if (x >= toolbar_xpos(i, w, lw)) + break; + } + + return i; +} +#endif diff --git a/src/cursmenu.dat b/src/cursmenu.dat new file mode 100644 index 0000000..73f0561 --- /dev/null +++ b/src/cursmenu.dat @@ -0,0 +1,14 @@ +@nostrtab + +#&func_help Help +#&func_about About +#&func_back Back +#&func_newgame NewGame +#&func_exit Exit + +FUNC + func_help, 1, func_help_OFFSET + func_about, 1, ACTION_ABOUT + func_back, 10 + func_newgame, 2, ACTION_NEWGAME + func_exit, 10, ACTION_EXIT