Patch from Jonas Koelker to add keyboard control support to Pearl.
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sun, 19 Feb 2012 10:15:59 +0000 (10:15 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sun, 19 Feb 2012 10:15:59 +0000 (10:15 +0000)
git-svn-id: svn://svn.tartarus.org/sgt/puzzles@9411 cda61777-01e9-0310-a592-d414129be87e

pearl.c
puzzles.but

diff --git a/pearl.c b/pearl.c
index 21cca29..a06ecc2 100644 (file)
--- a/pearl.c
+++ b/pearl.c
@@ -5,10 +5,17 @@
 /*
  * TODO:
  *
- *  - Keyboard-control cursor. (Would probably have to address both
- *    square centres, for laying multiple edges at a time in a
- *    drag-like style, and grid edges for marking particular line
- *    segments as no-go.)
+ *  - The current keyboard cursor mechanism works well on ordinary PC
+ *    keyboards, but for platforms with only arrow keys and a select
+ *    button or two, we may at some point need a simpler one which can
+ *    handle 'x' markings without needing shift keys. For instance, a
+ *    cursor with twice the grid resolution, so that it can range
+ *    across face centres, edge centres and vertices; 'clicks' on face
+ *    centres begin a drag as currently, clicks on edges toggle
+ *    markings, and clicks on vertices are ignored (but it would be
+ *    too confusing not to let the cursor rest on them). But I'm
+ *    pretty sure that would be less pleasant to play on a full
+ *    keyboard, so probably a #ifdef would be the thing.
  *
  *  - Generation is still pretty slow, due to difficulty coming up in
  *    the first place with a loop that makes a soluble puzzle even
@@ -83,6 +90,7 @@
 
 enum {
     COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_CURSOR_BACKGROUND = COL_LOWLIGHT,
     COL_BLACK, COL_WHITE,
     COL_ERROR, COL_GRID, COL_FLASH,
     COL_DRAGON, COL_DRAGOFF,
@@ -1720,6 +1728,9 @@ struct game_ui {
     int ndragcoords;       /* number of entries in dragcoords.
                             * 0 = click but no drag yet. -1 = no drag at all */
     int clickx, clicky;    /* pixel position of initial click */
+
+    int curx, cury;        /* grid position of keyboard cursor */
+    int cursor_active;     /* TRUE iff cursor is shown */
 };
 
 static game_ui *new_ui(game_state *state)
@@ -1729,6 +1740,8 @@ static game_ui *new_ui(game_state *state)
 
     ui->ndragcoords = -1;
     ui->dragcoords = snewn(sz, int);
+    ui->cursor_active = FALSE;
+    ui->curx = ui->cury = 0;
 
     return ui;
 }
@@ -1762,6 +1775,7 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
 #define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
 
 #define COORD(x) ( (x) * TILE_SIZE + BORDER )
+#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 )
 #define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) )
 
 #define DS_ESHIFT 4     /* R/U/L/D shift, for error flags */
@@ -1770,6 +1784,7 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
 
 #define DS_ERROR_CLUE (1 << 20)
 #define DS_FLASH (1 << 21)
+#define DS_CURSOR (1 << 22)
 
 enum { GUI_MASYU, GUI_LOOPY };
 
@@ -1912,14 +1927,44 @@ static void interpret_ui_drag(game_state *state, game_ui *ui, int *clearing,
     }
 }
 
+static char *mark_in_direction(game_state *state, int x, int y, int dir,
+                              int ismark, char *buf)
+{
+    int w = state->shared->w /*, h = state->shared->h, sz = state->shared->sz */;
+    int x2 = x + DX(dir);
+    int y2 = y + DY(dir);
+    int dir2 = F(dir);
+    char ch = ismark ? 'M' : 'F';
+
+    if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return "";
+    /* disallow laying a mark over a line, or vice versa. */
+    if (ismark) {
+       if ((state->lines[y*w+x] & dir) || (state->lines[y2*w+x2] & dir2))
+           return "";
+    } else {
+       if ((state->marks[y*w+x] & dir) || (state->marks[y2*w+x2] & dir2))
+           return "";
+    }
+    
+    sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2);
+    return dupstr(buf);
+}
+
+#define KEY_DIRECTION(btn) (\
+    (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\
+    (btn) == CURSOR_LEFT ? L : R)
+
 static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
                            int x, int y, int button)
 {
-    int w = state->shared->w /*, h = state->shared->h, sz = state->shared->sz */;
+    int w = state->shared->w, h = state->shared->h /*, sz = state->shared->sz */;
     int gx = FROMCOORD(x), gy = FROMCOORD(y), i;
+    int release = FALSE;
     char tmpbuf[80];
 
     if (IS_MOUSE_DOWN(button)) {
+       ui->cursor_active = FALSE;
+
         if (!INGRID(state, gx, gy)) {
             ui->ndragcoords = -1;
             return NULL;
@@ -1937,7 +1982,44 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
         return "";
     }
 
-    if (IS_MOUSE_RELEASE(button)) {
+    if (IS_MOUSE_RELEASE(button)) release = TRUE;
+
+    if (IS_CURSOR_MOVE(button & ~MOD_MASK)) {
+       if (!ui->cursor_active) {
+           ui->cursor_active = TRUE;
+       } else if (button & (MOD_SHFT | MOD_CTRL)) {
+           if (ui->ndragcoords > 0) return NULL;
+           ui->ndragcoords = -1;
+           return mark_in_direction(state, ui->curx, ui->cury,
+                                    KEY_DIRECTION(button & ~MOD_MASK),
+                                    (button & MOD_SHFT), tmpbuf);
+       } else {
+           move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE);
+           if (ui->ndragcoords >= 0)
+               update_ui_drag(state, ui, ui->curx, ui->cury);
+       }
+       return "";
+    }
+
+    if (IS_CURSOR_SELECT(button & ~MOD_MASK)) {
+       if (!ui->cursor_active) {
+           ui->cursor_active = TRUE;
+           return "";
+       } else if (button == CURSOR_SELECT) {
+           if (ui->ndragcoords == -1) {
+               ui->ndragcoords = 0;
+               ui->dragcoords[0] = ui->cury * w + ui->curx;
+               ui->clickx = CENTERED_COORD(ui->curx);
+               ui->clicky = CENTERED_COORD(ui->cury);
+               return "";
+           } else release = TRUE;
+       } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) {
+           ui->ndragcoords = -1;
+           return "";
+       }
+    }
+
+    if (release) {
         if (ui->ndragcoords > 0) {
             /* End of a drag: process the cached line data. */
             int buflen = 0, bufsize = 256, tmplen;
@@ -1971,8 +2053,6 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
             /* Click (or tiny drag). Work out which edge we were
              * closest to. */
             int cx, cy;
-            int gx2, gy2, l1, l2, ismark = (button == RIGHT_RELEASE);
-            char movec = ismark ? 'M' : 'F';
 
             ui->ndragcoords = -1;
 
@@ -1986,8 +2066,8 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
 
             gx = FROMCOORD(x);
             gy = FROMCOORD(y);
-            cx = COORD(gx) + TILE_SIZE/2;
-            cy = COORD(gy) + TILE_SIZE/2;
+            cx = CENTERED_COORD(gx);
+            cy = CENTERED_COORD(gy);
 
             if (!INGRID(state, gx, gy)) return "";
 
@@ -1996,30 +2076,16 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
 
                 return "";
             } else {
+               int direction;
                 if (abs(x-cx) < abs(y-cy)) {
                     /* Closest to top/bottom edge. */
-                    l1 = (y < cy) ? U : D;
+                    direction = (y < cy) ? U : D;
                 } else {
                     /* Closest to left/right edge. */
-                    l1 = (x < cx) ? L : R;
-                }
-                gx2 = gx + DX(l1); gy2 = gy + DY(l1);
-                l2 = F(l1);
-
-                if (!INGRID(state, gx, gy) || !INGRID(state, gx2, gy2)) return "";
-
-                /* disallow laying a mark over a line, or vice versa. */
-                if (ismark) {
-                    if ((state->lines[gy*w+gx] & l1) || (state->lines[gy2*w+gx2] & l2))
-                        return "";
-                } else {
-                    if ((state->marks[gy*w+gx] & l1) || (state->marks[gy2*w+gx2] & l2))
-                        return "";
+                    direction = (x < cx) ? L : R;
                 }
-
-                sprintf(tmpbuf, "%c%d,%d,%d;%c%d,%d,%d",
-                        movec, l1, gx, gy, movec, l2, gx2, gy2);
-                return dupstr(tmpbuf);
+               return mark_in_direction(state, gx, gy, direction,
+                                        (button == RIGHT_RELEASE), tmpbuf);
             }
         }
     }
@@ -2027,8 +2093,6 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
     if (button == 'H' || button == 'h')
         return dupstr("H");
 
-    /* TODO cursor */
-
     return NULL;
 }
 
@@ -2231,7 +2295,10 @@ static void draw_square(drawing *dr, game_drawstate *ds, game_ui *ui,
     clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
 
     /* Clear the square. */
-    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE,
+             (lflags & DS_CURSOR) ?
+             COL_CURSOR_BACKGROUND : COL_BACKGROUND);
+             
 
     if (get_gui_style() == GUI_LOOPY) {
         /* Draw small dot, underneath any lines. */
@@ -2355,6 +2422,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
 
             f |= flashing;
 
+           if (ui->cursor_active && x == ui->curx && y == ui->cury)
+               f |= DS_CURSOR;
+
             if (f != ds->lflags[y*w+x] || force) {
                 ds->lflags[y*w+x] = f;
                 draw_square(dr, ds, ui, x, y, f, state->shared->clues[y*w+x]);
index 4b42d33..6885740 100644 (file)
@@ -2958,6 +2958,12 @@ indicating that you are sure the loop does not go through that edge.
 white clue has to be a corner, but don't yet know which way the corner
 turns, you might mark the one way it \e{can't} go with a cross.)
 
+Alternatively, use the cursor keys to move the cursor.  Use the Enter
+key to begin and end keyboard `drag' operations.  Use the Space key to
+cancel the drag.  Use Ctrl-arrowkey and Shift-arrowkey to simulate a
+left or right click, respectively, on the edge in the given direction
+relative to the cursor, i.e. to draw a segment or a cross.
+
 (All the actions described in \k{common-actions} are also available.)
 
 \H{pearl-parameters} \I{parameters, for Pearl}Pearl parameters