[gnome-mines] Added keyboard support for the main minefield.
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-mines] Added keyboard support for the main minefield.
- Date: Fri, 15 Feb 2013 01:21:46 +0000 (UTC)
commit 391932f9df73bb2154fed375adda285597a3c09c
Author: Isaac Lenton <isaac isuniversal com>
Date: Mon Feb 11 01:53:13 2013 +1000
Added keyboard support for the main minefield.
Navigation: Up/Down/Left/Right or HJKL
Left Click: Space or Enter
Place Flag: Ctrl + Space
src/gnome-mines.vala | 5 +-
src/minefield-view.vala | 343 ++++++++++++++++++++++++++++++++++++++---------
src/minefield.vala | 18 ++--
3 files changed, 292 insertions(+), 74 deletions(-)
---
diff --git a/src/gnome-mines.vala b/src/gnome-mines.vala
index 47456db..6dddcf7 100644
--- a/src/gnome-mines.vala
+++ b/src/gnome-mines.vala
@@ -517,6 +517,7 @@ public class Mines : Gtk.Application
is_new_game_screen = false;
custom_game_screen.hide ();
minefield_view.show ();
+ minefield_view.has_focus = true;
new_game_screen.hide ();
set_face_image (smile_face_image);
@@ -912,7 +913,9 @@ public class ScoreDialog : Gtk.Dialog
if (show_quit)
{
add_button (Gtk.Stock.QUIT, Gtk.ResponseType.CLOSE);
- add_button (_("New Game"), Gtk.ResponseType.OK);
+
+ var button = add_button (_("New Game"), Gtk.ResponseType.OK);
+ button.has_focus = true;
}
else
add_button (Gtk.Stock.OK, Gtk.ResponseType.DELETE_EVENT);
diff --git a/src/minefield-view.vala b/src/minefield-view.vala
index 2bb37c0..1d16aea 100644
--- a/src/minefield-view.vala
+++ b/src/minefield-view.vala
@@ -1,3 +1,85 @@
+private class Position : Object
+{
+ public signal void redraw (uint x, uint y);
+ public signal bool validate (int x, int y);
+ public signal int set_x (int x);
+ public signal int set_y (int y);
+
+ private bool _is_set = false;
+ public bool is_set
+ {
+ get { return _is_set; }
+ set
+ {
+ if (_is_set != value && is_valid)
+ redraw (x, y);
+
+ _is_set = value;
+ }
+ }
+
+ public bool is_valid
+ {
+ get { return validate (x, y); }
+ }
+
+ private int _x = 0;
+ public int x
+ {
+ get { return _x; }
+ set
+ {
+ if (_x == value)
+ return;
+
+ if (is_set && is_valid)
+ redraw (x, y);
+
+ _x = set_x (value);
+
+ if (is_set && is_valid)
+ redraw (x, y);
+ }
+ }
+
+ private int _y = 0;
+ public int y
+ {
+ get { return _y; }
+ set
+ {
+ if (_y == value)
+ return;
+
+ if (is_set && is_valid)
+ redraw (x, y);
+
+ _y = set_y (value);
+
+ if (is_set && is_valid)
+ redraw (x, y);
+ }
+ }
+
+ public int[] position
+ {
+ set
+ {
+ if (_x == value[0] && _y == value[1])
+ return;
+
+ if (is_set && is_valid)
+ redraw (x, y);
+
+ _x = set_x (value[0]);
+ _y = set_y (value[1]);
+
+ if (is_set && is_valid)
+ redraw (x, y);
+ }
+ }
+}
+
public class MinefieldView : Gtk.DrawingArea
{
/* true if allowed to mark locations with question marks */
@@ -9,9 +91,9 @@ public class MinefieldView : Gtk.DrawingArea
/* true if automatically set flags on middle click */
private bool use_autoflag;
- /* Location being clicked on */
- private int selected_x = -1;
- private int selected_y = -1;
+ /* Position of keyboard cursor and selected squares */
+ private Position keyboard_cursor;
+ private Position selected;
/* Pre-rendered images */
private uint render_size = 0;
@@ -64,7 +146,10 @@ public class MinefieldView : Gtk.DrawingArea
public MinefieldView ()
{
- set_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
+ set_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.KEY_PRESS_MASK |
Gdk.EventMask.KEY_RELEASE_MASK);
+ can_focus = true;
+ selected = new Position ();
+ keyboard_cursor = new Position ();
number_patterns = new Cairo.Pattern[8];
}
@@ -77,11 +162,25 @@ public class MinefieldView : Gtk.DrawingArea
if (_minefield != null)
SignalHandler.disconnect_by_func (_minefield, null, this);
_minefield = value;
- selected_x = -1;
- selected_y = -1;
+
+ selected.is_set = false;
+ selected.redraw.connect (redraw_sector_cb);
+ selected.redraw.connect ((x, y) => { if (_minefield.is_cleared (x, y)) redraw_adjacent (x, y);
});
+ selected.set_x.connect ((x) => { return x; });
+ selected.set_y.connect ((y) => { return y; });
+ selected.validate.connect (_minefield.is_location);
+
+ keyboard_cursor.is_set = false;
+ keyboard_cursor.position = {0, 0};
+ keyboard_cursor.redraw.connect (redraw_sector_cb);
+ keyboard_cursor.set_x.connect ((x) => { return (int) (x % _minefield.width); });
+ keyboard_cursor.set_y.connect ((y) => { return (int) (y % _minefield.height); });
+ keyboard_cursor.validate.connect ((x, y) => { return true; });
+
_minefield.redraw_sector.connect (redraw_sector_cb);
_minefield.explode.connect (explode_cb);
_minefield.paused_changed.connect (() => { queue_draw (); });
+
queue_resize ();
}
}
@@ -218,18 +317,22 @@ public class MinefieldView : Gtk.DrawingArea
private void draw_square (Cairo.Context cr, uint x, uint y)
{
- /* Work out if the cursor is being held down on this square */
- var is_down = x == selected_x && y == selected_y && minefield.get_flag (x, y) != FlagType.FLAG;
- if (selected_x >= 0 && minefield.is_cleared (selected_x, selected_y))
+ /* Work out if the cursor is being held down on this square or neighbouring cleared squares */
+ var is_down = false;
+ if (selected.is_valid && selected.is_set)
{
- foreach (var neighbour in neighbour_map)
+ is_down = x == selected.x && y == selected.y && minefield.get_flag (x, y) != FlagType.FLAG;
+ if (!is_down && minefield.is_cleared (selected.x, selected.y))
{
- var nx = selected_x + neighbour.x;
- var ny = selected_y + neighbour.y;
- if (!minefield.is_location (nx, ny))
- continue;
- if (x == nx && y == ny && minefield.get_flag (nx, ny) != FlagType.FLAG)
- is_down = true;
+ foreach (var neighbour in neighbour_map)
+ {
+ var nx = (int) selected.x + neighbour.x;
+ var ny = (int) selected.y + neighbour.y;
+ if (!minefield.is_location (nx, ny))
+ continue;
+ if (x == nx && y == ny && minefield.get_flag (nx, ny) != FlagType.FLAG)
+ is_down = true;
+ }
}
}
@@ -386,6 +489,24 @@ public class MinefieldView : Gtk.DrawingArea
}
}
+ /* Draw keyboard cursor */
+ if (keyboard_cursor.is_set)
+ {
+ double key_centre[2] = { x_offset + (keyboard_cursor.x+0.5) * mine_size, y_offset +
(keyboard_cursor.y+0.5) * mine_size };
+ var key_cursor = new Cairo.Pattern.radial (key_centre[0], key_centre[1], 0.0, key_centre[0],
key_centre[1], 0.25 * mine_size);
+ key_cursor.add_color_stop_rgba (0.0, 1.0, 1.0, 1.0, 1.0);
+ key_cursor.add_color_stop_rgba (0.8, 1.0, 1.0, 1.0, 0.1);
+ key_cursor.add_color_stop_rgba (0.9, 0.0, 0.0, 0.0, 0.5);
+ key_cursor.add_color_stop_rgba (1.0, 0.0, 0.0, 0.0, 0.2);
+ key_cursor.add_color_stop_rgba (1.0, 0.0, 0.0, 0.0, 0.0);
+
+ cr.save ();
+ cr.rectangle (key_centre[0] - 0.45 * mine_size, key_centre[1] - 0.45 * mine_size, 0.9 *
mine_size, 0.9 * mine_size);
+ cr.set_source (key_cursor);
+ cr.fill ();
+ cr.restore ();
+ }
+
/* Draw pause overlay */
if (minefield.paused)
{
@@ -440,8 +561,8 @@ public class MinefieldView : Gtk.DrawingArea
{
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (minefield.is_location (nx, ny))
redraw_sector_cb (nx, ny);
}
@@ -458,8 +579,8 @@ public class MinefieldView : Gtk.DrawingArea
uint n_unknown = 0;
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (!minefield.is_location (nx, ny))
continue;
if (minefield.get_flag (nx, ny) == FlagType.FLAG)
@@ -484,8 +605,8 @@ public class MinefieldView : Gtk.DrawingArea
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (!m.is_location (nx, ny))
continue;
@@ -502,93 +623,187 @@ public class MinefieldView : Gtk.DrawingArea
if (event.type != Gdk.EventType.BUTTON_PRESS)
return false;
+ /* Check for end cases and paused game */
if (minefield.exploded || minefield.is_complete || minefield.paused)
return false;
- var x = (int) Math.floor ((event.x - x_offset) / mine_size);
- var y = (int) Math.floor ((event.y - y_offset) / mine_size);
- if (!minefield.is_location (x, y))
+ /* Does the user have the space key down? */
+ if (selected.is_set && keyboard_cursor.is_set)
+ return false;
+
+ /* Hide any lingering previously selected and get new location */
+ selected.is_set = false;
+ selected.x = (int) Math.floor ((event.x - x_offset) / mine_size);
+ selected.y = (int) Math.floor ((event.y - y_offset) / mine_size);
+
+ /* Is the current position a minefield square? */
+ if (!selected.is_valid)
return false;
/* Right or Ctrl+Left button to toggle flags */
if (event.button == 3 || (event.button == 1 && (event.state & Gdk.ModifierType.CONTROL_MASK) != 0))
{
- toggle_mark (x, y);
- return false;
+ toggle_mark (selected.x, selected.y);
+ unlook ();
}
-
/* Left button to clear */
- if (event.button == 1)
+ else if (event.button == 1)
{
- selected_x = x;
- selected_y = y;
- redraw_sector_cb (x, y);
-
+ selected.is_set = true;
look ();
-
- if (minefield.is_cleared (x, y))
- redraw_adjacent (x, y);
}
+ keyboard_cursor.is_set = false;
+ keyboard_cursor.position = {selected.x, selected.y};
+
return false;
}
public override bool motion_notify_event (Gdk.EventMotion event)
{
- if (minefield.exploded || minefield.is_complete)
+ /* Check for end cases and paused game */
+ if (minefield.exploded || minefield.is_complete || minefield.paused)
return false;
-
- if (selected_x < 0)
+
+ /* Check that the user isn't currently navigating with keyboard */
+ if (!selected.is_set || keyboard_cursor.is_set)
return false;
var x = (int) Math.floor ((event.x - x_offset) / mine_size);
var y = (int) Math.floor ((event.y - y_offset) / mine_size);
- if (!minefield.is_location (x, y))
+ selected.position = {x, y};
+
+ return false;
+ }
+
+ public override bool button_release_event (Gdk.EventButton event)
+ {
+ if (event.button != 1)
+ return false;
+
+ /* Check for end cases and paused game */
+ if (minefield.exploded || minefield.is_complete || minefield.paused)
return false;
- if (x == selected_x && y == selected_y)
+ /* Check that the user isn't currently using the mouse */
+ if (!selected.is_set || keyboard_cursor.is_set)
return false;
- /* Redraw existing selected squares */
- redraw_sector_cb (selected_x, selected_y);
- if (minefield.is_cleared (selected_x, selected_y))
- redraw_adjacent (selected_x, selected_y);
+ unlook ();
- /* Draw new selected squares */
- redraw_sector_cb (x, y);
- if (minefield.is_cleared (x, y))
- redraw_adjacent (x, y);
+ if (minefield.is_cleared (selected.x, selected.y))
+ {
+ multi_release (selected.x, selected.y);
+ redraw_adjacent (selected.x, selected.y);
+ }
+ else if (minefield.get_flag (selected.x, selected.y) != FlagType.FLAG)
+ minefield.clear_mine (selected.x, selected.y);
- selected_x = x;
- selected_y = y;
+ keyboard_cursor.position = {selected.x, selected.y};
+ selected.is_set = false;
return false;
}
- public override bool button_release_event (Gdk.EventButton event)
+ public override bool key_press_event (Gdk.EventKey event)
{
- if (minefield.exploded || minefield.is_complete)
+ /* Check for end cases and paused game */
+ if (minefield.exploded || minefield.is_complete || minefield.paused)
return false;
- if (selected_x < 0)
+ /* Check that the user isn't currently using the mouse */
+ if (selected.is_set && !keyboard_cursor.is_set)
return false;
- if (event.button == 1)
+ var x = keyboard_cursor.x;
+ var y = keyboard_cursor.y;
+
+ switch (event.keyval)
{
- unlook ();
+ case Gdk.Key.Left:
+ case Gdk.Key.h:
+ x--;
+ break;
+
+ case Gdk.Key.Right:
+ case Gdk.Key.l:
+ x++;
+ break;
+
+ case Gdk.Key.Up:
+ case Gdk.Key.k:
+ y--;
+ break;
+
+ case Gdk.Key.Down:
+ case Gdk.Key.j:
+ y++;
+ break;
- if (minefield.is_cleared (selected_x, selected_y))
+ case Gdk.Key.space:
+ case Gdk.Key.Return:
+ if (keyboard_cursor.is_set)
{
- multi_release (selected_x, selected_y);
- redraw_adjacent (selected_x, selected_y);
+ selected.is_set = false;
+
+ if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0)
+ {
+ toggle_mark (keyboard_cursor.x, keyboard_cursor.y);
+ }
+ else
+ {
+ selected.position = {x, y};
+ selected.is_set = true;
+ look ();
+ }
}
- else if (minefield.get_flag (selected_x, selected_y) != FlagType.FLAG)
- minefield.clear_mine (selected_x, selected_y);
- redraw_sector_cb (selected_x, selected_y);
+ break;
+
+ default:
+ return false;
+ }
+
+ if (x == keyboard_cursor.x && y == keyboard_cursor.y)
+ return true;
+
+ if (!keyboard_cursor.is_set)
+ {
+ keyboard_cursor.is_set = true;
+ return true;
+ }
- selected_x = -1;
- selected_y = -1;
+ keyboard_cursor.position = {x, y};
+
+ if (selected.is_set)
+ selected.position = {keyboard_cursor.x, keyboard_cursor.y};
+
+ return true;
+ }
+
+ public override bool key_release_event (Gdk.EventKey event)
+ {
+ if (event.keyval != Gdk.Key.space)
+ return false;
+
+ /* Check for end cases and paused game */
+ if (minefield.exploded || minefield.is_complete || minefield.paused)
+ return false;
+
+ /* Check that the user isn't currently using the mouse */
+ if (!selected.is_set || !keyboard_cursor.is_set)
+ return false;
+
+ unlook ();
+
+ if (minefield.is_cleared (selected.x, selected.y))
+ {
+ multi_release (selected.x, selected.y);
+ redraw_adjacent (selected.x, selected.y);
}
+ else if (minefield.get_flag (selected.x, selected.y) != FlagType.FLAG)
+ minefield.clear_mine (selected.x, selected.y);
+
+ selected.is_set = false;
return false;
}
diff --git a/src/minefield.vala b/src/minefield.vala
index 6356974..d547641 100644
--- a/src/minefield.vala
+++ b/src/minefield.vala
@@ -147,7 +147,7 @@ public class Minefield
return locations[x, y].cleared;
}
- public bool is_location (uint x, uint y)
+ public bool is_location (int x, int y)
{
return x >= 0 && y >= 0 && x < width && y < height;
}
@@ -208,8 +208,8 @@ public class Minefield
{
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (is_location (nx, ny))
clear_mines_recursive (nx, ny);
}
@@ -228,8 +228,8 @@ public class Minefield
/* FIXME: Doesn't check if have changed, just if might have changed */
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (is_location (nx, ny) && is_cleared (nx, ny))
redraw_sector (nx, ny);
}
@@ -314,8 +314,8 @@ public class Minefield
uint n = 0;
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (is_location (nx, ny) && has_mine (nx, ny))
n++;
}
@@ -330,8 +330,8 @@ public class Minefield
uint n_mines = 0, n_flags = 0;
foreach (var neighbour in neighbour_map)
{
- var nx = x + neighbour.x;
- var ny = y + neighbour.y;
+ var nx = (int) x + neighbour.x;
+ var ny = (int) y + neighbour.y;
if (!is_location (nx, ny))
continue;
if (has_mine (nx, ny))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]