I can never remember what that `TRUE' means in the game structure
[sgt/puzzles] / midend.c
index a28f3db..30ad32b 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -32,6 +32,8 @@ struct midend_data {
     float anim_time, anim_pos;
     float flash_time, flash_pos;
     int dir;
+
+    int pressed_mouse_button;
 };
 
 #define ensure(me) do { \
@@ -66,6 +68,7 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
     me->flash_time = me->flash_pos = 0.0F;
     me->dir = 0;
     me->ui = NULL;
+    me->pressed_mouse_button = 0;
 
     sfree(randseed);
 
@@ -114,6 +117,7 @@ void midend_new_game(midend_data *me)
     if (me->ui)
         me->ourgame->free_ui(me->ui);
     me->ui = me->ourgame->new_ui(me->states[0]);
+    me->pressed_mouse_button = 0;
 }
 
 void midend_restart_game(midend_data *me)
@@ -180,7 +184,7 @@ static void midend_stop_anim(midend_data *me)
     }
 }
 
-int midend_process_key(midend_data *me, int x, int y, int button)
+static int midend_really_process_key(midend_data *me, int x, int y, int button)
 {
     game_state *oldstate = me->ourgame->dup_game(me->states[me->statepos - 1]);
     float anim_time;
@@ -255,6 +259,104 @@ int midend_process_key(midend_data *me, int x, int y, int button)
     return 1;
 }
 
+int midend_process_key(midend_data *me, int x, int y, int button)
+{
+    int ret = 1;
+
+    /*
+     * Harmonise mouse drag and release messages.
+     * 
+     * Some front ends might accidentally switch from sending, say,
+     * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
+     * drag. (This can happen on the Mac, for example, since
+     * RIGHT_DRAG is usually done using Command+drag, and if the
+     * user accidentally releases Command half way through the drag
+     * then there will be trouble.)
+     * 
+     * It would be an O(number of front ends) annoyance to fix this
+     * in the front ends, but an O(number of back ends) annoyance
+     * to have each game capable of dealing with it. Therefore, we
+     * fix it _here_ in the common midend code so that it only has
+     * to be done once.
+     * 
+     * The possible ways in which things can go screwy in the front
+     * end are:
+     * 
+     *  - in a system containing multiple physical buttons button
+     *    presses can inadvertently overlap. We can see ABab (caps
+     *    meaning button-down and lowercase meaning button-up) when
+     *    the user had semantically intended AaBb.
+     * 
+     *  - in a system where one button is simulated by means of a
+     *    modifier key and another button, buttons can mutate
+     *    between press and release (possibly during drag). So we
+     *    can see Ab instead of Aa.
+     * 
+     * Definite requirements are:
+     * 
+     *  - button _presses_ must never be invented or destroyed. If
+     *    the user presses two buttons in succession, the button
+     *    presses must be transferred to the backend unchanged. So
+     *    if we see AaBb , that's fine; if we see ABab (the button
+     *    presses inadvertently overlapped) we must somehow
+     *    `correct' it to AaBb.
+     * 
+     *  - every mouse action must end up looking like a press, zero
+     *    or more drags, then a release. This allows back ends to
+     *    make the _assumption_ that incoming mouse data will be
+     *    sane in this regard, and not worry about the details.
+     * 
+     * So my policy will be:
+     * 
+     *  - treat any button-up as a button-up for the currently
+     *    pressed button, or ignore it if there is no currently
+     *    pressed button.
+     * 
+     *  - treat any drag as a drag for the currently pressed
+     *    button, or ignore it if there is no currently pressed
+     *    button.
+     * 
+     *  - if we see a button-down while another button is currently
+     *    pressed, invent a button-up for the first one and then
+     *    pass the button-down through as before.
+     * 
+     */
+    if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
+        if (me->pressed_mouse_button) {
+            if (IS_MOUSE_DRAG(button)) {
+                button = me->pressed_mouse_button +
+                    (LEFT_DRAG - LEFT_BUTTON);
+            } else {
+                button = me->pressed_mouse_button +
+                    (LEFT_RELEASE - LEFT_BUTTON);
+            }
+        } else
+            return ret;                /* ignore it */
+    } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
+        /*
+         * Fabricate a button-up for the previously pressed button.
+         */
+        ret = ret && midend_really_process_key
+            (me, x, y, (me->pressed_mouse_button +
+                        (LEFT_RELEASE - LEFT_BUTTON)));
+    }
+
+    /*
+     * Now send on the event we originally received.
+     */
+    ret = ret && midend_really_process_key(me, x, y, button);
+
+    /*
+     * And update the currently pressed button.
+     */
+    if (IS_MOUSE_RELEASE(button))
+        me->pressed_mouse_button = 0;
+    else if (IS_MOUSE_DOWN(button))
+        me->pressed_mouse_button = button;
+
+    return ret;
+}
+
 void midend_redraw(midend_data *me)
 {
     if (me->statepos > 0 && me->drawstate) {