From ceb2df0fe41852ba3f5637dcd04217113b4259ba Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Fri, 30 Dec 2022 12:16:13 -0500 Subject: [PATCH] motif: Avoid constantly recalculating tile size. Instead of calculating tile size every time any drawing is needed, we can just store the value calculated during window layout. This involves some code reorganization to give the ResizeGameArea action implementation direct access to the state structure. Moreover, avoid dealing with width/height separately in the drawing functions, as by design the game/goal areas (and therefore the tiles) are always square. --- src/motif.c | 56 +++++++++++++++++++++++++++++++++-------- src/motif.h | 6 +++-- src/motif_ui.c | 68 +++++++++----------------------------------------- src/x11.c | 48 ++++++++++++++++------------------- 4 files changed, 83 insertions(+), 95 deletions(-) diff --git a/src/motif.c b/src/motif.c index fe03079..c4ea62d 100644 --- a/src/motif.c +++ b/src/motif.c @@ -33,6 +33,11 @@ #define TIMER_UPDATE_MS 33 +#define SPLIT_NUMERATOR 75 +#define SPLIT_DENOMINATOR 100 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + #define PROGNAME "rrace" static const char *progname = PROGNAME; static const struct option lopts[] = { LOPTS_INITIALIZER, {0} }; @@ -182,7 +187,6 @@ static void game_input(Widget w, void *data, void *cb_data) XmDrawingAreaCallbackStruct *cbs = cb_data; XButtonEvent *b = &cbs->event->xbutton; struct app_state *state = data; - Dimension width, height; switch (cbs->event->type) { case ButtonPress: @@ -191,12 +195,8 @@ static void game_input(Widget w, void *data, void *cb_data) if (b->state & Button3Mask) break; - XtVaGetValues(w, XmNwidth, &width, - XmNheight, &height, - (char *)NULL); - - do_input_move(state, b->x / (width / 5), - b->y / (height / 5)); + do_input_move(state, b->x / state->game_tile_sz, + b->y / state->game_tile_sz); break; case Button3: set_view_goal(state, 1); @@ -250,10 +250,44 @@ static void proc_about(Widget w, XEvent *e, String *argv, Cardinal *argc) ui_show_about(&state, get_shell(w)); } -static const XtActionsRec menu_actions[] = { +static void proc_resize(Widget form, XEvent *e, String *argv, Cardinal *argc) +{ + Widget game = XtParent(state.game); + Widget goal = XtParent(state.goal); + Dimension w, h, gamesz, gameborder, goalsz, goalborder; + int x, y, gap; + + XtVaGetValues(form, XmNwidth, &w, XmNheight, &h, (char *)NULL); + XtVaGetValues(game, XmNshadowThickness, &gameborder, (char *)NULL); + XtVaGetValues(goal, XmNshadowThickness, &goalborder, + XmNleftOffset, &gap, + (char *)NULL); + + gamesz = MIN(h, w * SPLIT_NUMERATOR / SPLIT_DENOMINATOR); + state.game_tile_sz = (gamesz - 2*gameborder) / 5; + gamesz = 5*state.game_tile_sz + 2*gameborder; + + goalsz = MIN(gamesz*3/5, w - gamesz - gap); + state.goal_tile_sz = (goalsz - 2*goalborder) / 3; + goalsz = 3*state.goal_tile_sz + 2*goalborder; + + x = (w - gamesz - goalsz - gap) / 2; + if (x < 2) x = 0; + + y = (h - gamesz) / 2; + if (y < 3) y = 0; + + XtVaSetValues(game, XmNleftOffset, x, XmNtopOffset, y, (char *)NULL); + XtVaSetValues(game, XmNwidth, gamesz, XmNheight, gamesz, (char *)NULL); + XtVaSetValues(goal, XmNwidth, goalsz, XmNheight, goalsz, (char *)NULL); +} + +static const XtActionsRec app_actions[] = { { "gameNew", proc_new_game }, { "gameExit", proc_exit }, - { "helpAbout", proc_about } + { "helpAbout", proc_about }, + + { "ResizeGameArea", proc_resize } }; static XtAppContext app_initialize(int argc, char **argv) @@ -266,11 +300,11 @@ static XtAppContext app_initialize(int argc, char **argv) progname = argv[0]; shell = early_setup(&app, argc, argv); - XtAppAddActions(app, (void *)menu_actions, XtNumber(menu_actions)); + XtAppAddActions(app, (void *)app_actions, XtNumber(app_actions)); ui_initialize(&state, shell); x11_initialize(&state, shell); - XtAddCallback(state.game, XmNinputCallback, game_input, &state); + XtAddCallback(state.game, XmNinputCallback, game_input, &state); /* Begin with the game in winning state */ game_reset(&state.board); diff --git a/src/motif.h b/src/motif.h index cb07ec3..a008306 100644 --- a/src/motif.h +++ b/src/motif.h @@ -37,8 +37,10 @@ struct app_state { XtIntervalId render_tick; XtWorkProcId render_proc; - uint_fast32_t render_game_mask; - uint_fast16_t render_goal_mask; + + uint_least32_t render_game_mask; + uint_least16_t render_goal_mask; + uint_least16_t game_tile_sz, goal_tile_sz; /* If true, the goal will be displayed over the main play area. */ int view_goal_on_game; diff --git a/src/motif_ui.c b/src/motif_ui.c index 6abdbc9..5941834 100644 --- a/src/motif_ui.c +++ b/src/motif_ui.c @@ -26,11 +26,6 @@ #include "xcounter.h" #include "version.h" -#define SPLIT_NUMERATOR 75 -#define SPLIT_DENOMINATOR 100 - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - /* XXX generate this list? */ enum { widgetMainWindow, @@ -69,37 +64,6 @@ static const struct ui_menuitem { uint_least16_t label; } mainmenu[] = { MAINMENU_INITIALIZER }; -static void -ResizeGameArea(Widget form, XEvent *e, String *args, Cardinal *num_args) -{ - Widget game = XtNameToWidget(form, &tree_strtab[gameArea]); - Widget goal = XtNameToWidget(form, &tree_strtab[goalArea]); - Dimension w, h, gamesz, gameborder, goalsz, goalborder; - int x, y, gap; - - XtVaGetValues(form, XmNwidth, &w, XmNheight, &h, (char *)NULL); - XtVaGetValues(game, XmNshadowThickness, &gameborder, (char *)NULL); - XtVaGetValues(goal, XmNshadowThickness, &goalborder, - XmNleftOffset, &gap, - (char *)NULL); - - gamesz = MIN(h, w * SPLIT_NUMERATOR / SPLIT_DENOMINATOR); - gamesz = 5*( (gamesz - 2*gameborder)/5 ) + 2*gameborder; - - goalsz = MIN(gamesz*3/5, w - gamesz - gap); - goalsz = 3*( (goalsz - 2*goalborder)/3 ) + 2*goalborder; - - x = (w - gamesz - goalsz - gap) / 2; - if (x < 2) x = 0; - - y = (h - gamesz) / 2; - if (y < 3) y = 0; - - XtVaSetValues(game, XmNleftOffset, x, XmNtopOffset, y, (char *)NULL); - XtVaSetValues(game, XmNwidth, gamesz, XmNheight, gamesz, (char *)NULL); - XtVaSetValues(goal, XmNwidth, goalsz, XmNheight, goalsz, (char *)NULL); -} - static char *timer_text(int_fast32_t ms, char *buf) { unsigned min, sec; @@ -132,7 +96,6 @@ static void configure_mainwin(struct app_state *state, Widget form) char xc_template[100]; assert(gamearea && goalarea && timer); - XtVaSetValues(form, XmNfractionBase, SPLIT_DENOMINATOR, (char *)NULL); state->game = XtNameToWidget(gamearea, &tree_strtab[gameCanvas]); XtVaSetValues(gamearea, XmNleftAttachment, XmATTACH_FORM, @@ -155,9 +118,6 @@ static void configure_mainwin(struct app_state *state, Widget form) state->timer = xcounter_simple_init(timer, timer_text(0, xc_template)); ui_timer_update(state, -1); - resize_rec.string = "ResizeGameArea"; - resize_rec.proc = ResizeGameArea; - XtAppAddActions(XtWidgetToApplicationContext(form), &resize_rec, 1); XtOverrideTranslations(form, XtParseTranslationTable( ": ResizeGameArea()\n" ": ResizeGameArea()\n" @@ -165,9 +125,9 @@ static void configure_mainwin(struct app_state *state, Widget form) /* * Performing the initial update of the layout seems to avoid - * some weird problems on Motif 2.1 + * some weird initial sizing problems on Motif 2.1 */ - ResizeGameArea(form, 0, 0, 0); + XtCallActionProc(form, "ResizeGameArea", 0, 0, 0); } static Widget create_widget(const struct ui_widget *item, Widget parent, @@ -257,12 +217,12 @@ construct_menu(const struct ui_menuitem *root, Widget parent, unsigned i) } /* Figure out which tiles intersect a rectangle. */ -static uint_fast32_t x11_expose_mask(XExposeEvent *e, int tile_w, int tile_h) +static uint_fast32_t x11_expose_mask(XExposeEvent *e, int tile_sz) { - return board_rect( e->x/tile_w, - e->y/tile_h, - (e->x+e->width-1)/tile_w, - (e->y+e->height-1)/tile_h ); + return board_rect( e->x/tile_sz, + e->y/tile_sz, + (e->x+e->width-1)/tile_sz, + (e->y+e->height-1)/tile_sz ); } static void resize(Widget w, void *data, void *cb_data) @@ -277,31 +237,27 @@ static void expose(Widget w, void *data, void *cb_data) XmDrawingAreaCallbackStruct *cbs = cb_data; XExposeEvent *e = &cbs->event->xexpose; struct app_state *state = data; - Dimension tile_w, tile_h; uint_fast32_t mask; + Dimension tile_sz; - XtVaGetValues(w, XmNwidth, &tile_w, XmNheight, &tile_h, (char *)NULL); if (w == state->game) { uint_least32_t *gp = state->board.game; - if (!(tile_w /= 5) || !(tile_h /= 5)) - return; - /* * Only draw exposed nonempty tiles; exposed areas are filled * with the background automatically and thus exposed empty * spaces don't need to be drawn again. */ + tile_sz = state->game_tile_sz; mask = gp[0] | gp[1] | gp[2]; } else { - if (!(tile_w /= 3) || !(tile_h /= 3)) - return; - /* Goal area never has empty tiles. */ + tile_sz = state->goal_tile_sz; mask = -1; } - mask &= x11_expose_mask(e, tile_w, tile_h); + if (tile_sz) + mask &= x11_expose_mask(e, tile_sz); x11_queue_render(state, mask, 1<<(w == state->game)); } diff --git a/src/x11.c b/src/x11.c index b15e7d6..6d09a7d 100644 --- a/src/x11.c +++ b/src/x11.c @@ -140,14 +140,19 @@ static void draw_tile(struct app_state *state, Display *display, Drawable d, XFillRectangle(display, d, state->tile_gc, tx+2, ty+2, tw-4, th-4); } -static void clear_rect(struct app_state *state, Display *display, Drawable d, - int x, int y, int w, int h) +static void +clear_rect(Display *display, Drawable d, int x, int y, int w, int h) { #if X11_RENDER_DEBUG XRectangle r = { x, y, w, h }; + XGCValues gcv; + GC gc; + + gcv.foreground = 0xff0000; + gc = XCreateGC(display, d, GCForeground, &gcv); + XFillRectangles(display, d, gc, &r, 1); + XFreeGC(display, gc); - XSetForeground(display, state->tile_gc, 0xff0000); - XFillRectangles(display, d, state->tile_gc, &r, 1); XFlush(display); usleep(70000); #endif @@ -160,23 +165,20 @@ static void clear_rect(struct app_state *state, Display *display, Drawable d, * which tiles need clearing, but for the border clear it is safe to wipe an * entire row or column of the border using a single XClearArea. */ -static void clear_border(struct app_state *state, Display *display, Drawable d, - Dimension w, Dimension h, uint_fast32_t mask) +static void clear_border(Display *display, Drawable d, unsigned sz) { - if (!(mask &= ~GOAL_MASK & 0x1ffffff)) - return; - - clear_rect(state, display, d, 0, 0, 5*w, h); - clear_rect(state, display, d, 0, 4*h, 5*w, h); - clear_rect(state, display, d, 0, 0, w, 5*h); - clear_rect(state, display, d, 4*w, 0, w, 5*h); + clear_rect(display, d, 0, 0, -1, sz); + clear_rect(display, d, 0, 4*sz, -1, sz); + clear_rect(display, d, 0, 0, sz, -1); + clear_rect(display, d, 4*sz, 0, sz, -1); } static int redraw_tile(struct app_state *state, Display *display, Drawable d, - uint_least32_t *gp, int x, int y, Dimension w, Dimension h) + uint_least32_t *gp, int x, int y, unsigned sz) { unsigned tile = board_tile(gp, 5*y+x); + unsigned w = sz, h = sz; #if X11_RENDER_DEBUG if (d == XtWindow(state->game) || d == XtWindow(state->goal)) { @@ -235,8 +237,8 @@ x11_redraw_goal(struct app_state *state, uint_fast32_t mask, Widget icon_shell) { Display *display = XtDisplay(state->goal); Window goal = XtWindow(state->goal); + unsigned sz = state->goal_tile_sz; unsigned long ewmhseq[9]; - Dimension w, h; int i; uint_least32_t gp[3] = { @@ -245,9 +247,6 @@ x11_redraw_goal(struct app_state *state, uint_fast32_t mask, Widget icon_shell) state->board.goal[2] }; - XtVaGetValues(state->goal, XtNwidth, &w, XtNheight, &h, (char *)NULL); - w /= 3; h /= 3; - for (i = 0; i < 9; i++) { int x = i%3, y = i/3; @@ -255,12 +254,12 @@ x11_redraw_goal(struct app_state *state, uint_fast32_t mask, Widget icon_shell) unsigned tile; tile = redraw_tile(state, display, state->icon_pixmap, - gp, x, y, ICON_SIZE/3, ICON_SIZE/3); + gp, x, y, ICON_SIZE/3); ewmhseq[i] = COLOUR_MAX*0x10101 * (tile-1) + 0x20100; } if (mask & 1) - redraw_tile(state, display, goal, gp, x, y, w, h); + redraw_tile(state, display, goal, gp, x, y, sz); /* * Goal bitmaps have a gap of 2 tiles between each row. @@ -278,12 +277,9 @@ void x11_redraw_game(struct app_state *state, uint_fast32_t mask) Display *display = XtDisplay(state->goal); Window game = XtWindow(state->game); uint_least32_t buf[3], *gp = state->board.game; - Dimension w, h; + unsigned sz = state->game_tile_sz; int i; - XtVaGetValues(state->game, XtNwidth, &w, XtNheight, &h, (char *)NULL); - w /= 5; h /= 5; - if (state->view_goal_on_game) { for (i = 0; i < 3; i++) { buf[i] = state->board.goal[i]; @@ -294,13 +290,13 @@ void x11_redraw_game(struct app_state *state, uint_fast32_t mask) /* Optimize the game end case where the outer frame is cleared */ if (((gp[0] | gp[1] | gp[2]) & ~GOAL_MASK) == 0) { - clear_border(state, display, game, w, h, mask); + clear_border(display, game, sz); mask &= GOAL_MASK; } for (i = 0; i < 25; i++) { if (mask & 1) - redraw_tile(state, display, game, gp, i%5, i/5, w, h); + redraw_tile(state, display, game, gp, i%5, i/5, sz); mask >>= 1; } } -- 2.43.2