]> git.draconx.ca Git - rrace.git/commitdiff
x11: Fix redraw on window resize.
authorNick Bowler <nbowler@draconx.ca>
Sun, 27 Nov 2022 16:43:01 +0000 (11:43 -0500)
committerNick Bowler <nbowler@draconx.ca>
Sun, 27 Nov 2022 17:25:21 +0000 (12:25 -0500)
It turns out that relying on expose events to trigger redraw of certain
tiles when enlarging is not actually a good idea.  No expose events will
be generated if the new portion of the window is obscured while resizing,
which means tiles along the bottom or right edge of the game/goal areas
may not get redrawn when they should be.

We can avoid the redundant drawing on resize another way.  Instead of
redrawing immediately in response to resize or expose events, we just
mark those tiles as needing update and only actually redraw after a
short delay, which is hopefully enough to catch the expose events
directly generated by the resize.

src/motif.h
src/motif_ui.c
src/x11.c

index 9064788691c61185150bd13a411af56ae4a5fa5c..28e1b768c1baecc87c62685c89d0f10426489124 100644 (file)
@@ -34,6 +34,11 @@ struct app_state {
 
        XtIntervalId timer_tick;
 
+       XtIntervalId render_tick;
+       XtWorkProcId render_proc;
+       uint_fast32_t render_game_mask;
+       uint_fast16_t render_goal_mask;
+
        /* If true, the goal will be displayed over the main play area. */
        int view_goal_on_game;
 
@@ -53,5 +58,7 @@ void x11_initialize(struct app_state *state, Widget shell);
 void x11_redraw_icon(struct app_state *state, Widget shell);
 void x11_redraw_goal(struct app_state *state, uint_fast32_t mask);
 void x11_redraw_game(struct app_state *state, uint_fast32_t mask);
+void x11_queue_render(struct app_state *state, uint_fast32_t game_mask,
+                                               uint_fast16_t goal_mask);
 
 #endif
index fefcfedef7505a6509074f76e5ffe1d2e9d57ecb..718375ce411bb4dd416e3482e2dc199743486133 100644 (file)
@@ -268,55 +268,14 @@ expose_mask(int rect_x, int rect_y, int rect_w, int rect_h,
             & board_left((rect_x + rect_w - 1) / tile_w);
 }
 
-/*
- * Calculate which tiles need to be redrawn after resizing.  This is the
- * tiles which (at the new size) are fully contained in the previous area.
- *
- * When shrinking, this is all tiles, but when enlarging, the generated
- * expose events will trigger drawing of the new area.
- */
-static uint_fast32_t resize_helper(Widget w, Dimension *oldsz, int gridsz)
-{
-       Dimension new_w, new_h, common_w, common_h;
-
-       if (!XtIsRealized(w))
-               return 0;
-
-       XtVaGetValues(w, XmNwidth, &new_w, XmNheight, &new_h, (char *)NULL);
-       common_w = MIN(new_w, oldsz[0]);
-       common_h = MIN(new_h, oldsz[1]);
-       oldsz[0] = new_w;
-       oldsz[1] = new_h;
-       new_w /= gridsz;
-       new_h /= gridsz;
-
-       /* Round down to multiple of tile dimensions */
-       common_w = common_w / new_w * new_w;
-       common_h = common_h / new_h * new_h;
-
-       if (new_w > 0 && new_h > 0)
-               return expose_mask(0, 0, common_w, common_h, new_w, new_h);
-       return 0;
-}
-
 static void game_resize(Widget w, void *data, void *cb_data)
 {
-       struct app_state *state = data;
-       uint_fast32_t mask;
-
-       mask = resize_helper(w, state->game_sz, 5);
-       if (mask)
-               x11_redraw_game(data, mask);
+       x11_queue_render(data, -1, 0);
 }
 
 static void goal_resize(Widget w, void *data, void *cb_data)
 {
-       struct app_state *state = data;
-       uint_fast32_t mask;
-
-       mask = resize_helper(w, state->goal_sz, 3);
-       if (mask)
-               x11_redraw_goal(data, mask);
+       x11_queue_render(data, 0, -1);
 }
 
 static void game_expose(Widget w, void *data, void *cb_data)
@@ -339,8 +298,7 @@ static void game_expose(Widget w, void *data, void *cb_data)
         */
        mask = gp[0] | gp[1] | gp[2];
        mask &= expose_mask(e->x, e->y, e->width, e->height, width, height);
-
-       x11_redraw_game(state, mask);
+       x11_queue_render(state, mask, 0);
 }
 
 static void goal_expose(Widget w, void *data, void *cb_data)
@@ -355,7 +313,7 @@ static void goal_expose(Widget w, void *data, void *cb_data)
                return;
 
        mask = expose_mask(e->x, e->y, e->width, e->height, width, height);
-       x11_redraw_goal(data, mask);
+       x11_queue_render(data, 0, mask);
 }
 
 void ui_initialize(struct app_state *state, Widget shell)
index 874bfdc57399c4cf6ecf5b7ae2c2d5f2981501ef..6954f06a15a5a30345ce43be670b5227d411bc0f 100644 (file)
--- a/src/x11.c
+++ b/src/x11.c
@@ -361,3 +361,56 @@ void x11_redraw_game(struct app_state *state, uint_fast32_t mask)
                mask >>= 1;
        }
 }
+
+/*
+ * Deferred redraw of tiles after resize/expose to avoid redundant drawing.
+ *
+ * Record any tiles that need to be redrawn due to resizes or expose events,
+ * then, after a short delay, perform all the accumulated redraws at once.
+ *
+ * This is implemented using both a work proc and a timeout, because it seems
+ * that rendering directly inside the timeout callback gives poor results:
+ * possibly redrawing outdated intermediate positions during "fast" resizes
+ * long after the resizing has stopped.  This does not happen when drawing
+ * from a work proc.
+ */
+static Boolean do_render(void *data)
+{
+       struct app_state *state = data;
+
+       x11_redraw_goal(state, state->render_goal_mask);
+       x11_redraw_game(state, state->render_game_mask);
+
+       state->render_goal_mask = state->render_game_mask = 0;
+       state->render_proc = 0;
+       state->render_tick = 0;
+       return True;
+}
+
+static void start_render(void *data, XtIntervalId *id)
+{
+       struct app_state *state = data;
+       XtAppContext app;
+
+       if (state->render_proc)
+               return;
+
+       app = XtWidgetToApplicationContext(state->game);
+       state->render_proc = XtAppAddWorkProc(app, do_render, state);
+}
+
+void x11_queue_render(struct app_state *state, uint_fast32_t game_mask,
+                                               uint_fast16_t goal_mask)
+{
+       uint_fast32_t changed = 0;
+       XtAppContext app;
+
+       changed |= state->render_game_mask |= game_mask;
+       changed |= state->render_goal_mask |= goal_mask;
+
+       if (state->render_tick || !changed)
+               return;
+
+       app = XtWidgetToApplicationContext(state->game);
+       state->render_tick = XtAppAddTimeOut(app, 3, start_render, state);
+}