Keyboard cursor support for Rectangles, from James H.

[originally from svn r8414]
This commit is contained in:
Simon Tatham
2009-01-15 18:20:02 +00:00
parent 4850845205
commit 5480199874
2 changed files with 108 additions and 38 deletions

View File

@ -761,11 +761,19 @@ specification.
\H{rectangles-controls} \I{controls, for Rectangles}Rectangles controls \H{rectangles-controls} \I{controls, for Rectangles}Rectangles controls
This game is played with the mouse. This game is played with the mouse or cursor keys.
Left-click any edge to toggle it on or off, or click and drag to draw Left-click any edge to toggle it on or off, or left-click and drag to draw
an entire rectangle (or line) on the grid in one go (removing any an entire rectangle (or line) on the grid in one go (removing any existing
existing edges within that rectangle). edges within that rectangle). Right-clicking and dragging will allow you
to erase the contents of a rectangle without affecting its edges.
Alternatively, use the cursor keys to move the position indicator
around the board. Pressing the return key then allows you to use the
cursor keys to drag a rectangle out from that position, and pressing
the return key again completes the rectangle. Using the space bar
instead of the return key allows you to erase the contents of a
rectangle without affecting its edges, as above.
When a rectangle of the correct size is completed, it will be shaded. When a rectangle of the correct size is completed, it will be shaded.

130
rect.c
View File

@ -36,7 +36,8 @@ enum {
COL_LINE, COL_LINE,
COL_TEXT, COL_TEXT,
COL_GRID, COL_GRID,
COL_DRAG, COL_DRAG, COL_DRAGERASE,
COL_CURSOR,
NCOLOURS NCOLOURS
}; };
@ -146,7 +147,7 @@ static void decode_params(game_params *ret, char const *string)
} }
if (*string == 'e') { if (*string == 'e') {
string++; string++;
ret->expandfactor = atof(string); ret->expandfactor = (float)atof(string);
while (*string && while (*string &&
(*string == '.' || isdigit((unsigned char)*string))) string++; (*string == '.' || isdigit((unsigned char)*string))) string++;
} }
@ -213,7 +214,7 @@ static game_params *custom_params(config_item *cfg)
ret->w = atoi(cfg[0].sval); ret->w = atoi(cfg[0].sval);
ret->h = atoi(cfg[1].sval); ret->h = atoi(cfg[1].sval);
ret->expandfactor = atof(cfg[2].sval); ret->expandfactor = (float)atof(cfg[2].sval);
ret->unique = cfg[3].ival; ret->unique = cfg[3].ival;
return ret; return ret;
@ -1159,9 +1160,9 @@ static char *new_game_desc(game_params *params, random_state *rs,
* Set up the smaller width and height which we will use to * Set up the smaller width and height which we will use to
* generate the base grid. * generate the base grid.
*/ */
params2->w = params->w / (1.0F + params->expandfactor); params2->w = (int)((float)params->w / (1.0F + params->expandfactor));
if (params2->w < 2 && params->w >= 2) params2->w = 2; if (params2->w < 2 && params->w >= 2) params2->w = 2;
params2->h = params->h / (1.0F + params->expandfactor); params2->h = (int)((float)params->h / (1.0F + params->expandfactor));
if (params2->h < 2 && params->h >= 2) params2->h = 2; if (params2->h < 2 && params->h >= 2) params2->h = 2;
grid = snewn(params2->w * params2->h, int); grid = snewn(params2->w * params2->h, int);
@ -2163,6 +2164,11 @@ struct game_ui {
* treated as a small drag rather than a click. * treated as a small drag rather than a click.
*/ */
int dragged; int dragged;
/* This flag is set if we're doing an erase operation (i.e.
* removing edges in the centre of the rectangle without altering
* the outlines).
*/
int erasing;
/* /*
* These are the co-ordinates of the top-left and bottom-right squares * These are the co-ordinates of the top-left and bottom-right squares
* in the drag box, respectively, or -1 otherwise. * in the drag box, respectively, or -1 otherwise.
@ -2171,6 +2177,11 @@ struct game_ui {
int y1; int y1;
int x2; int x2;
int y2; int y2;
/*
* These are the coordinates of a cursor, whether it's visible, and
* whether it was used to start a drag.
*/
int cur_x, cur_y, cur_visible, cur_dragging;
}; };
static game_ui *new_ui(game_state *state) static game_ui *new_ui(game_state *state)
@ -2180,11 +2191,12 @@ static game_ui *new_ui(game_state *state)
ui->drag_start_y = -1; ui->drag_start_y = -1;
ui->drag_end_x = -1; ui->drag_end_x = -1;
ui->drag_end_y = -1; ui->drag_end_y = -1;
ui->dragged = FALSE; ui->dragged = ui->erasing = FALSE;
ui->x1 = -1; ui->x1 = -1;
ui->y1 = -1; ui->y1 = -1;
ui->x2 = -1; ui->x2 = -1;
ui->y2 = -1; ui->y2 = -1;
ui->cur_x = ui->cur_y = ui->cur_visible = ui->cur_dragging = 0;
return ui; return ui;
} }
@ -2291,7 +2303,7 @@ static void coord_round(float x, float y, int *xr, int *yr)
*/ */
static int grid_draw_rect(game_state *state, static int grid_draw_rect(game_state *state,
unsigned char *hedge, unsigned char *vedge, unsigned char *hedge, unsigned char *vedge,
int c, int really, int c, int really, int outline,
int x1, int y1, int x2, int y2) int x1, int y1, int x2, int y2)
{ {
int x, y; int x, y;
@ -2304,9 +2316,10 @@ static int grid_draw_rect(game_state *state,
for (y = y1; y <= y2; y++) for (y = y1; y <= y2; y++)
if (HRANGE(state,x,y)) { if (HRANGE(state,x,y)) {
int val = index(state,hedge,x,y); int val = index(state,hedge,x,y);
if (y == y1 || y == y2) if (y == y1 || y == y2) {
if (!outline) continue;
val = c; val = c;
else if (c == 1) } else if (c == 1)
val = 0; val = 0;
changed = changed || (index(state,hedge,x,y) != val); changed = changed || (index(state,hedge,x,y) != val);
if (really) if (really)
@ -2320,9 +2333,10 @@ static int grid_draw_rect(game_state *state,
for (x = x1; x <= x2; x++) for (x = x1; x <= x2; x++)
if (VRANGE(state,x,y)) { if (VRANGE(state,x,y)) {
int val = index(state,vedge,x,y); int val = index(state,vedge,x,y);
if (x == x1 || x == x2) if (x == x1 || x == x2) {
if (!outline) continue;
val = c; val = c;
else if (c == 1) } else if (c == 1)
val = 0; val = 0;
changed = changed || (index(state,vedge,x,y) != val); changed = changed || (index(state,vedge,x,y) != val);
if (really) if (really)
@ -2334,9 +2348,9 @@ static int grid_draw_rect(game_state *state,
static int ui_draw_rect(game_state *state, game_ui *ui, static int ui_draw_rect(game_state *state, game_ui *ui,
unsigned char *hedge, unsigned char *vedge, int c, unsigned char *hedge, unsigned char *vedge, int c,
int really) int really, int outline)
{ {
return grid_draw_rect(state, hedge, vedge, c, really, return grid_draw_rect(state, hedge, vedge, c, really, outline,
ui->x1, ui->y1, ui->x2, ui->y2); ui->x1, ui->y1, ui->x2, ui->y2);
} }
@ -2355,30 +2369,61 @@ static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
int x, int y, int button) int x, int y, int button)
{ {
int xc, yc; int xc, yc;
int startdrag = FALSE, enddrag = FALSE, active = FALSE; int startdrag = FALSE, enddrag = FALSE, active = FALSE, erasing = FALSE;
char buf[80], *ret; char buf[80], *ret;
button &= ~MOD_MASK; button &= ~MOD_MASK;
if (button == LEFT_BUTTON) { coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
startdrag = TRUE; startdrag = TRUE;
} else if (button == LEFT_RELEASE) { ui->cur_visible = ui->cur_dragging = FALSE;
active = TRUE;
erasing = (button == RIGHT_BUTTON);
} else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) {
/* We assert we should have had a LEFT_BUTTON first. */
assert(!ui->cur_visible);
assert(!ui->cur_dragging);
enddrag = TRUE; enddrag = TRUE;
} else if (button != LEFT_DRAG) { erasing = (button == RIGHT_RELEASE);
} else if (IS_CURSOR_MOVE(button)) {
move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
ui->cur_visible = TRUE;
active = TRUE;
if (!ui->cur_dragging) return "";
coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
} else if (IS_CURSOR_SELECT(button)) {
if (!ui->cur_visible) {
assert(!ui->cur_dragging);
ui->cur_visible = TRUE;
return "";
}
coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
erasing = (button == CURSOR_SELECT2);
if (ui->cur_dragging) {
ui->cur_dragging = FALSE;
enddrag = TRUE;
active = TRUE;
} else {
ui->cur_dragging = TRUE;
startdrag = TRUE;
active = TRUE;
}
} else if (button != LEFT_DRAG && button != RIGHT_DRAG) {
return NULL; return NULL;
} }
coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
if (startdrag && if (startdrag &&
xc >= 0 && xc <= 2*from->w && xc >= 0 && xc <= 2*from->w &&
yc >= 0 && yc <= 2*from->h) { yc >= 0 && yc <= 2*from->h) {
ui->drag_start_x = xc; ui->drag_start_x = xc;
ui->drag_start_y = yc; ui->drag_start_y = yc;
ui->drag_end_x = xc; ui->drag_end_x = -1;
ui->drag_end_y = yc; ui->drag_end_y = -1;
ui->dragged = FALSE; ui->dragged = FALSE;
ui->erasing = erasing;
active = TRUE; active = TRUE;
} }
@ -2417,12 +2462,14 @@ static char *interpret_move(game_state *from, game_ui *ui, game_drawstate *ds,
if (enddrag && (ui->drag_start_x >= 0)) { if (enddrag && (ui->drag_start_x >= 0)) {
if (xc >= 0 && xc <= 2*from->w && if (xc >= 0 && xc <= 2*from->w &&
yc >= 0 && yc <= 2*from->h) { yc >= 0 && yc <= 2*from->h &&
erasing == ui->erasing) {
if (ui->dragged) { if (ui->dragged) {
if (ui_draw_rect(from, ui, from->hedge, if (ui_draw_rect(from, ui, from->hedge,
from->vedge, 1, FALSE)) { from->vedge, 1, FALSE, !ui->erasing)) {
sprintf(buf, "R%d,%d,%d,%d", sprintf(buf, "%c%d,%d,%d,%d",
(int)(ui->erasing ? 'E' : 'R'),
ui->x1, ui->y1, ui->x2 - ui->x1, ui->y2 - ui->y1); ui->x1, ui->y1, ui->x2 - ui->x1, ui->y2 - ui->y1);
ret = dupstr(buf); ret = dupstr(buf);
} }
@ -2486,7 +2533,7 @@ static game_state *execute_move(game_state *from, char *move)
return ret; return ret;
} else if (move[0] == 'R' && } else if ((move[0] == 'R' || move[0] == 'E') &&
sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 && sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
x1 >= 0 && x2 >= 0 && x1+x2 <= from->w && x1 >= 0 && x2 >= 0 && x1+x2 <= from->w &&
y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) { y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) {
@ -2503,8 +2550,9 @@ static game_state *execute_move(game_state *from, char *move)
ret = dup_game(from); ret = dup_game(from);
if (mode == 'R') { if (mode == 'R' || mode == 'E') {
grid_draw_rect(ret, ret->hedge, ret->vedge, 1, TRUE, x1, y1, x2, y2); grid_draw_rect(ret, ret->hedge, ret->vedge, 1, TRUE,
mode == 'R', x1, y1, x2, y2);
} else if (mode == 'H') { } else if (mode == 'H') {
hedge(ret,x1,y1) = !hedge(ret,x1,y1); hedge(ret,x1,y1) = !hedge(ret,x1,y1);
} else if (mode == 'V') { } else if (mode == 'V') {
@ -2539,8 +2587,9 @@ static game_state *execute_move(game_state *from, char *move)
*/ */
#define CORRECT (1L<<16) #define CORRECT (1L<<16)
#define CURSOR (1L<<17)
#define COLOUR(k) ( (k)==1 ? COL_LINE : COL_DRAG ) #define COLOUR(k) ( (k)==1 ? COL_LINE : (k)==2 ? COL_DRAG : COL_DRAGERASE )
#define MAX4(x,y,z,w) ( max(max(x,y),max(z,w)) ) #define MAX4(x,y,z,w) ( max(max(x,y),max(z,w)) )
static void game_compute_size(game_params *params, int tilesize, static void game_compute_size(game_params *params, int tilesize,
@ -2574,6 +2623,10 @@ static float *game_colours(frontend *fe, int *ncolours)
ret[COL_DRAG * 3 + 1] = 0.0F; ret[COL_DRAG * 3 + 1] = 0.0F;
ret[COL_DRAG * 3 + 2] = 0.0F; ret[COL_DRAG * 3 + 2] = 0.0F;
ret[COL_DRAGERASE * 3 + 0] = 0.2F;
ret[COL_DRAGERASE * 3 + 1] = 0.2F;
ret[COL_DRAGERASE * 3 + 2] = 1.0F;
ret[COL_CORRECT * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0]; ret[COL_CORRECT * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
ret[COL_CORRECT * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1]; ret[COL_CORRECT * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
ret[COL_CORRECT * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2]; ret[COL_CORRECT * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
@ -2586,6 +2639,10 @@ static float *game_colours(frontend *fe, int *ncolours)
ret[COL_TEXT * 3 + 1] = 0.0F; ret[COL_TEXT * 3 + 1] = 0.0F;
ret[COL_TEXT * 3 + 2] = 0.0F; ret[COL_TEXT * 3 + 2] = 0.0F;
ret[COL_CURSOR * 3 + 0] = 1.0F;
ret[COL_CURSOR * 3 + 1] = 0.5F;
ret[COL_CURSOR * 3 + 2] = 0.5F;
*ncolours = NCOLOURS; *ncolours = NCOLOURS;
return ret; return ret;
} }
@ -2614,14 +2671,15 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state, static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
int x, int y, unsigned char *hedge, unsigned char *vedge, int x, int y, unsigned char *hedge, unsigned char *vedge,
unsigned char *corners, int correct) unsigned char *corners, unsigned long bgflags)
{ {
int cx = COORD(x), cy = COORD(y); int cx = COORD(x), cy = COORD(y);
char str[80]; char str[80];
draw_rect(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID); draw_rect(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
draw_rect(dr, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1, draw_rect(dr, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
correct ? COL_CORRECT : COL_BACKGROUND); (bgflags & CURSOR) ? COL_CURSOR :
(bgflags & CORRECT) ? COL_CORRECT : COL_BACKGROUND);
if (grid(state,x,y)) { if (grid(state,x,y)) {
sprintf(str, "%d", grid(state,x,y)); sprintf(str, "%d", grid(state,x,y));
@ -2680,7 +2738,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
vedge = snewn(state->w*state->h, unsigned char); vedge = snewn(state->w*state->h, unsigned char);
memcpy(hedge, state->hedge, state->w*state->h); memcpy(hedge, state->hedge, state->w*state->h);
memcpy(vedge, state->vedge, state->w*state->h); memcpy(vedge, state->vedge, state->w*state->h);
ui_draw_rect(state, ui, hedge, vedge, 2, TRUE); ui_draw_rect(state, ui, hedge, vedge, ui->erasing ? 3 : 2, TRUE, TRUE);
} else { } else {
hedge = state->hedge; hedge = state->hedge;
vedge = state->vedge; vedge = state->vedge;
@ -2742,10 +2800,12 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
c |= (unsigned long)index(state,corners,x+1,y+1) << 14; c |= (unsigned long)index(state,corners,x+1,y+1) << 14;
if (index(state, state->correct, x, y) && !flashtime) if (index(state, state->correct, x, y) && !flashtime)
c |= CORRECT; c |= CORRECT;
if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y)
c |= CURSOR;
if (index(ds,ds->visible,x,y) != c) { if (index(ds,ds->visible,x,y) != c) {
draw_tile(dr, ds, state, x, y, hedge, vedge, corners, draw_tile(dr, ds, state, x, y, hedge, vedge, corners,
(c & CORRECT) ? 1 : 0); (c & (CORRECT|CURSOR)) );
index(ds,ds->visible,x,y) = c; index(ds,ds->visible,x,y) = c;
} }
} }
@ -2806,8 +2866,8 @@ static void game_print_size(game_params *params, float *x, float *y)
* I'll use 5mm squares by default. * I'll use 5mm squares by default.
*/ */
game_compute_size(params, 500, &pw, &ph); game_compute_size(params, 500, &pw, &ph);
*x = pw / 100.0; *x = pw / 100.0F;
*y = ph / 100.0; *y = ph / 100.0F;
} }
static void game_print(drawing *dr, game_state *state, int tilesize) static void game_print(drawing *dr, game_state *state, int tilesize)
@ -2903,3 +2963,5 @@ const struct game thegame = {
FALSE, game_timing_state, FALSE, game_timing_state,
0, /* flags */ 0, /* flags */
}; };
/* vim: set shiftwidth=4 tabstop=8: */