[gnome-games] gnotravex: Port from C to Vala
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games] gnotravex: Port from C to Vala
- Date: Sat, 19 Nov 2011 01:43:18 +0000 (UTC)
commit af7197eb48b8d5aa62a4915afe1d1cb9344c6b7e
Author: Robert Ancell <robert ancell canonical com>
Date: Thu Nov 17 08:57:42 2011 +1100
gnotravex: Port from C to Vala
configure.in | 4 +-
gnotravex/data/Makefile.am | 4 -
gnotravex/data/baize.png | Bin 470 -> 0 bytes
gnotravex/src/Makefile.am | 23 +-
gnotravex/src/config.vapi | 2 +
gnotravex/src/gnotravex.c | 1902 ----------------------------------------
gnotravex/src/gnotravex.vala | 413 +++++++++
gnotravex/src/puzzle-view.vala | 450 ++++++++++
gnotravex/src/puzzle.vala | 280 ++++++
gnotravex/src/theme.vala | 272 ++++++
10 files changed, 1437 insertions(+), 1913 deletions(-)
---
diff --git a/configure.in b/configure.in
index f2cc752..130e52a 100644
--- a/configure.in
+++ b/configure.in
@@ -126,7 +126,7 @@ for game in $gamelist; do
*) ;;
esac
case $game in
- glchess|gnomine|mahjongg) need_vala=yes ;;
+ glchess|gnomine|gnotravex|mahjongg) need_vala=yes ;;
*) ;;
esac
case $game in
@@ -158,7 +158,7 @@ for game in $gamelist; do
*) ;;
esac
case $game in
- glines|gnobots2|gnotravex|gnotski|iagno) allow_smclient=yes ;;
+ glines|gnobots2|gnotski|iagno) allow_smclient=yes ;;
*) ;;
esac
case $game in
diff --git a/gnotravex/data/Makefile.am b/gnotravex/data/Makefile.am
index 1faca71..2589d05 100644
--- a/gnotravex/data/Makefile.am
+++ b/gnotravex/data/Makefile.am
@@ -1,6 +1,3 @@
-imagedir = $(pkgdatadir)/pixmaps
-image_DATA = baize.png
-
desktop_in_files = gnotravex.desktop.in.in
desktopdir = $(datadir)/applications
desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
@@ -14,7 +11,6 @@ gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
man_MANS = gnotravex.6
EXTRA_DIST = \
- $(image_DATA) \
$(gsettings_in_file) \
$(man_MANS)
diff --git a/gnotravex/src/Makefile.am b/gnotravex/src/Makefile.am
index 8b938cf..f584e03 100644
--- a/gnotravex/src/Makefile.am
+++ b/gnotravex/src/Makefile.am
@@ -1,13 +1,26 @@
bin_PROGRAMS = gnotravex
gnotravex_SOURCES = \
- gnotravex.c
-
-gnotravex_CPPFLAGS = \
- -I$(top_srcdir) \
- $(AM_CPPFLAGS)
+ config.vapi \
+ gnotravex.vala \
+ puzzle.vala \
+ puzzle-view.vala \
+ theme.vala
+
+gnotravex_VALAFLAGS = \
+ --pkg posix \
+ --pkg gtk+-3.0 \
+ --vapidir $(top_srcdir)/libgames-support \
+ --pkg GnomeGamesSupport-1.0
+
+if ENABLE_SETGID
+gnotravex_VALAFLAGS += -D ENABLE_SETGID
+endif
gnotravex_CFLAGS = \
+ -I$(top_srcdir)/libgames-support \
+ -DVERSION=\"$(VERSION)\" \
+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
$(GTK_CFLAGS) \
$(AM_CFLAGS)
diff --git a/gnotravex/src/config.vapi b/gnotravex/src/config.vapi
new file mode 100644
index 0000000..6477226
--- /dev/null
+++ b/gnotravex/src/config.vapi
@@ -0,0 +1,2 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
\ No newline at end of file
diff --git a/gnotravex/src/gnotravex.vala b/gnotravex/src/gnotravex.vala
new file mode 100644
index 0000000..ec1c6a0
--- /dev/null
+++ b/gnotravex/src/gnotravex.vala
@@ -0,0 +1,413 @@
+public class Gnotravex : Gtk.Window
+{
+ private const int LONG_COUNT = 15;
+ private const int SHORT_COUNT = 5;
+ private const int DELAY = 10;
+
+ private const string KEY_GRID_SIZE = "grid-size";
+ private const string KEY_CLICK_MOVE = "click-to-move";
+
+ private Settings settings;
+
+ private Puzzle puzzle;
+ private GnomeGamesSupport.Clock clock;
+ private GnomeGamesSupport.Scores highscores;
+
+ private PuzzleView view;
+ private const GnomeGamesSupport.ScoresCategory scorecats[] =
+ {
+ { "2x2", N_("2Ã2") },
+ { "3x3", N_("3Ã3") },
+ { "4x4", N_("4Ã4") },
+ { "5x5", N_("5Ã5") },
+ { "6x6", N_("6Ã6") }
+ };
+
+ const Gtk.RadioActionEntry size_action_entry[] =
+ {
+ {"Size2x2", null, N_("_2Ã2"), null, N_("Play on a 2Ã2 board"), 2},
+ {"Size3x3", null, N_("_3Ã3"), null, N_("Play on a 3Ã3 board"), 3},
+ {"Size4x4", null, N_("_4Ã4"), null, N_("Play on a 4Ã4 board"), 4},
+ {"Size5x5", null, N_("_5Ã5"), null, N_("Play on a 5Ã5 board"), 5},
+ {"Size6x6", null, N_("_6Ã6"), null, N_("Play on a 6Ã6 board"), 6}
+ };
+
+ private Gtk.Action new_game_action;
+ private GnomeGamesSupport.PauseAction pause_action;
+ private Gtk.Action solve_action;
+ private Gtk.Action scores_action;
+ private Gtk.Action move_up_action;
+ private Gtk.Action move_left_action;
+ private Gtk.Action move_right_action;
+ private Gtk.Action move_down_action;
+
+ private const string ui_description =
+ "<ui>" +
+ " <menubar name='MainMenu'>" +
+ " <menu action='GameMenu'>" +
+ " <menuitem action='NewGame'/>" +
+ " <menuitem action='PauseGame'/>" +
+ " <separator/>" +
+ " <menu action='MoveMenu'>" +
+ " <menuitem action='MoveUp'/>" +
+ " <menuitem action='MoveLeft'/>" +
+ " <menuitem action='MoveRight'/>" +
+ " <menuitem action='MoveDown'/>" +
+ " </menu>" +
+ " <menuitem action='Solve'/>" +
+ " <separator/>" +
+ " <menuitem action='Scores'/>" +
+ " <separator/>" +
+ " <menuitem action='Quit'/>" +
+ " </menu>" +
+ " <menu action='SettingsMenu'>" +
+ " <menuitem action='Fullscreen'/>" +
+ " <menuitem action='ClickToMove'/>" +
+ " <separator/>" +
+ " <menuitem action='Size2x2'/>" +
+ " <menuitem action='Size3x3'/>" +
+ " <menuitem action='Size4x4'/>" +
+ " <menuitem action='Size5x5'/>" +
+ " <menuitem action='Size6x6'/>" +
+ " </menu>" +
+ " <menu action='HelpMenu'>" +
+ " <menuitem action='Contents'/>" +
+ " <menuitem action='About'/>" +
+ " </menu>" +
+ " </menubar>" +
+ " <toolbar name='Toolbar'>" +
+ " <toolitem action='NewGame'/>" +
+ " <toolitem action='PauseGame'/>" +
+ " <toolitem action='LeaveFullscreen'/>" +
+ " </toolbar>" +
+ "</ui>";
+
+ public Gnotravex ()
+ {
+ settings = new Settings ("org.gnome.gnotravex");
+
+ highscores = new GnomeGamesSupport.Scores ("gnotravex", scorecats, null, null, 0, GnomeGamesSupport.ScoreStyle.TIME_ASCENDING);
+
+ title = _("Tetravex");
+ GnomeGamesSupport.settings_bind_window_state ("/org/gnome/gnotravex/", this);
+
+ var ui_manager = new Gtk.UIManager ();
+ var action_group = new Gtk.ActionGroup ("actions");
+ action_group.set_translation_domain (GETTEXT_PACKAGE);
+ action_group.add_actions (action_entry, this);
+ action_group.add_radio_actions (size_action_entry, -1, size_cb);
+ action_group.add_toggle_actions (toggles, this);
+ ui_manager.insert_action_group (action_group, 0);
+
+ try
+ {
+ ui_manager.add_ui_from_string (ui_description, -1);
+ }
+ catch (Error e)
+ {
+ critical ("Failed to parse UI: %s", e.message);
+ }
+
+ new_game_action = action_group.get_action ("NewGame");
+ solve_action = action_group.get_action ("Solve");
+ scores_action = action_group.get_action ("Scores");
+ move_up_action = action_group.get_action ("MoveUp");
+ move_left_action = action_group.get_action ("MoveLeft");
+ move_right_action = action_group.get_action ("MoveRight");
+ move_down_action = action_group.get_action ("MoveDown");
+ pause_action = new GnomeGamesSupport.PauseAction ("PauseGame");
+ pause_action.is_important = true;
+ pause_action.state_changed.connect (pause_cb);
+ action_group.add_action_with_accel (pause_action, null);
+ var fullscreen_action = new GnomeGamesSupport.FullscreenAction ("Fullscreen", this);
+ action_group.add_action_with_accel (fullscreen_action, null);
+ var leave_fullscreen_action = new GnomeGamesSupport.FullscreenAction ("LeaveFullscreen", this);
+ action_group.add_action_with_accel (leave_fullscreen_action, null);
+ var action = (Gtk.ToggleAction) action_group.get_action ("ClickToMove");
+ action.active = settings.get_boolean (KEY_CLICK_MOVE);
+ var size = settings.get_int (KEY_GRID_SIZE);
+ if (size < 2 || size > 6)
+ size = 3;
+ var size_action = (Gtk.RadioAction) action_group.get_action (size_action_entry[size-2].name);
+ size_action.active = true;
+
+ var grid = new Gtk.Grid ();
+ grid.show ();
+ add (grid);
+
+ var toolbar = ui_manager.get_widget ("/Toolbar");
+ toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_PRIMARY_TOOLBAR);
+ toolbar.show ();
+ grid.attach (toolbar, 0, 0, 1, 1);
+
+ var menubar = ui_manager.get_widget ("/MainMenu");
+ menubar.show ();
+ grid.attach (menubar, 0, 0, 1, 1);
+
+ view = new PuzzleView ();
+ view.hexpand = true;
+ view.vexpand = true;
+ view.click_to_move = settings.get_boolean (KEY_CLICK_MOVE);
+ view.button_press_event.connect (view_button_press_event);
+ view.show ();
+ grid.attach (view, 0, 1, 1, 1);
+
+ var statusbar = new Gtk.Statusbar ();
+ statusbar.show ();
+ GnomeGamesSupport.stock_prepare_for_statusbar_tooltips (ui_manager, statusbar);
+ grid.attach (statusbar, 0, 2, 1, 1);
+
+ var time_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ time_box.show ();
+ statusbar.pack_start (time_box, false, false, 0);
+
+ var time_label = new Gtk.Label (_("Time:"));
+ time_label.show ();
+ time_box.pack_start (time_label, false, false, 0);
+
+ var label = new Gtk.Label (" ");
+ label.show ();
+ time_box.pack_start (label, false, false, 0);
+
+ clock = new GnomeGamesSupport.Clock ();
+ clock.show ();
+ time_box.pack_start (clock, false, false, 0);
+
+ add_accel_group (ui_manager.get_accel_group ());
+
+ new_game ();
+ }
+
+ private void new_game ()
+ {
+ if (puzzle != null)
+ SignalHandler.disconnect_by_func (puzzle, null, this);
+
+ pause_action.sensitive = true;
+
+ var size = settings.get_int (KEY_GRID_SIZE);
+ if (size < 2 || size > 6)
+ size = 3;
+ highscores.set_category (scorecats[size - 2].key);
+ puzzle = new Puzzle (size);
+ puzzle.solved.connect (solved_cb);
+ view.puzzle = puzzle;
+ view.is_paused = false;
+
+ clock.reset ();
+ clock.start ();
+ }
+
+ private void solved_cb (Puzzle puzzle)
+ {
+ clock.stop ();
+
+ var seconds = clock.get_seconds ();
+ var pos = highscores.add_time_score ((seconds / 60) * 1.0 + (seconds % 60) / 100.0);
+
+ var scores_dialog = new GnomeGamesSupport.ScoresDialog (this, highscores, _("Tetravex Scores"));
+ scores_dialog.set_category_description (_("Size:"));
+ scores_dialog.set_hilight (pos);
+ scores_dialog.set_message ("<b>%s</b>\n\n%s".printf (_("Congratulations!"), pos == 1 ? _("Your score is the best!") : _("Your score has made the top ten.")));
+ scores_dialog.set_buttons (GnomeGamesSupport.ScoresButtons.QUIT_BUTTON | GnomeGamesSupport.ScoresButtons.NEW_GAME_BUTTON);
+ if (scores_dialog.run () == Gtk.ResponseType.REJECT)
+ Gtk.main_quit ();
+ else
+ new_game ();
+ scores_dialog.destroy ();
+ }
+
+ private void new_game_cb (Gtk.Action action)
+ {
+ new_game ();
+ }
+
+ private void quit_cb (Gtk.Action action)
+ {
+ Gtk.main_quit ();
+ }
+
+ private void scores_cb (Gtk.Action action)
+ {
+ var scores_dialog = new GnomeGamesSupport.ScoresDialog (this, highscores, _("Tetravex Scores"));
+ scores_dialog.set_category_description (_("Size:"));
+ scores_dialog.run ();
+ scores_dialog.destroy ();
+ }
+
+ private bool view_button_press_event (Gtk.Widget widget, Gdk.EventButton event)
+ {
+ /* Cancel pause on click */
+ if (view.is_paused)
+ {
+ toggle_pause ();
+ return true;
+ }
+
+ return false;
+ }
+
+ private void pause_cb (Gtk.Action action)
+ {
+ toggle_pause ();
+ }
+
+ private void toggle_pause ()
+ {
+ if (view.is_paused)
+ {
+ pause_action.set_is_paused (false);
+ solve_action.sensitive = true;
+ clock.start ();
+ view.is_paused = false;
+ }
+ else
+ {
+ pause_action.set_is_paused (true);
+ solve_action.sensitive = false;
+ clock.stop ();
+ view.is_paused = true;
+ }
+ }
+
+ private void solve_cb (Gtk.Action action)
+ {
+ puzzle.solve ();
+ clock.stop ();
+ }
+
+ private void help_cb (Gtk.Action action)
+ {
+ GnomeGamesSupport.help_display (this, "gnotravex", null);
+ }
+
+ private void about_cb (Gtk.Action action)
+ {
+ string[] authors = { "Lars Rydlinge", "Robert Ancell", null };
+ string[] documenters = { "Rob Bradford", null };
+ var license = GnomeGamesSupport.get_license (_("Tetravex"));
+ Gtk.show_about_dialog (this,
+ "program-name", _("Tetravex"),
+ "version", VERSION,
+ "comments",
+ _("GNOME Tetravex is a simple puzzle where pieces must be positioned so that the same numbers are touching each other.\n\nTetravex is a part of GNOME Games."),
+ "copyright",
+ "Copyright \xc2\xa9 1999-2008 Lars Rydlinge",
+ "license", license,
+ "wrap-license", true,
+ "authors", authors,
+ "documenters", documenters,
+ "translator-credits", _("translator-credits"),
+ "logo-icon-name", "gnome-tetravex",
+ "website", "http://www.gnome.org/projects/gnome-games",
+ "website-label", _("GNOME Games web site"),
+ null);
+ }
+
+ private void size_cb (Gtk.Action action)
+ {
+ var size = ((Gtk.RadioAction) action).get_current_value ();
+
+ if (size == settings.get_int (KEY_GRID_SIZE))
+ return;
+ settings.set_int (KEY_GRID_SIZE, size);
+ new_game ();
+ }
+
+ private void clickmove_toggle_cb (Gtk.Action action)
+ {
+ var click_to_move = ((Gtk.ToggleAction) action).active;
+
+ if (click_to_move == settings.get_boolean (KEY_CLICK_MOVE))
+ return;
+
+ settings.set_boolean (KEY_CLICK_MOVE, click_to_move);
+ view.click_to_move = click_to_move;
+ }
+
+ private void move_up_cb (Gtk.Action action)
+ {
+ puzzle.move_up ();
+ }
+
+ private void move_left_cb (Gtk.Action action)
+ {
+ puzzle.move_left ();
+ }
+
+ private void move_right_cb (Gtk.Action action)
+ {
+ puzzle.move_right ();
+ }
+
+ private void move_down_cb (Gtk.Action action)
+ {
+ puzzle.move_down ();
+ }
+
+ private const Gtk.ActionEntry[] action_entry =
+ {
+ {"GameMenu", null, N_("_Game")},
+ {"MoveMenu", null, N_("_Move")},
+ {"SettingsMenu", null, N_("_Settings")},
+ {"SizeMenu", null, N_("_Size")},
+ {"HelpMenu", null, N_("_Help")},
+ {"NewGame", GnomeGamesSupport.STOCK_NEW_GAME, null, null, null, new_game_cb},
+ {"Solve", Gtk.Stock.REFRESH, N_("Sol_ve"), null, N_("Solve the game"), solve_cb},
+ {"Scores", GnomeGamesSupport.STOCK_SCORES, null, null, null, scores_cb},
+ {"Quit", Gtk.Stock.QUIT, null, null, null, quit_cb},
+ {"MoveUp", Gtk.Stock.GO_UP, N_("_Up"), "<control>Up", N_("Move the pieces up"), move_up_cb},
+ {"MoveLeft", Gtk.Stock.GO_BACK, N_("_Left"), "<control>Left", N_("Move the pieces left"), move_left_cb},
+ {"MoveRight", Gtk.Stock.GO_FORWARD, N_("_Right"), "<control>Right", N_("Move the pieces right"), move_right_cb},
+ {"MoveDown", Gtk.Stock.GO_DOWN, N_("_Down"), "<control>Down", N_("Move the pieces down"), move_down_cb},
+ {"Contents", GnomeGamesSupport.STOCK_CONTENTS, null, null, null, help_cb},
+ {"About", Gtk.Stock.ABOUT, null, null, null, about_cb}
+ };
+ private const Gtk.ToggleActionEntry toggles[] =
+ {
+ {"ClickToMove", null, N_("_Click to Move"), null, "Pick up and drop tiles by clicking", clickmove_toggle_cb}
+ };
+
+ public static int main (string[] args)
+ {
+ if (!GnomeGamesSupport.runtime_init ("gnotravex"))
+ return Posix.EXIT_FAILURE;
+
+#if ENABLE_SETGID
+ GnomeGamesSupport.setgid_io_init ();
+#endif
+
+ var context = new OptionContext ("");
+ context.set_translation_domain (GETTEXT_PACKAGE);
+ context.add_group (Gtk.get_option_group (true));
+ try
+ {
+ context.parse (ref args);
+ }
+ catch (Error e)
+ {
+ stderr.printf ("%s\n", e.message);
+ return Posix.EXIT_FAILURE;
+ }
+
+ Environment.set_application_name (_("Tetravex"));
+ GnomeGamesSupport.stock_init ();
+ Gtk.Window.set_default_icon_name ("gnome-tetravex");
+
+ var app = new Gnotravex ();
+ app.delete_event.connect (window_delete_event_cb);
+ app.show ();
+
+ Gtk.main ();
+
+ GnomeGamesSupport.runtime_shutdown ();
+
+ return Posix.EXIT_SUCCESS;
+ }
+
+ private static bool window_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event)
+ {
+ Gtk.main_quit ();
+ return false;
+ }
+}
diff --git a/gnotravex/src/puzzle-view.vala b/gnotravex/src/puzzle-view.vala
new file mode 100644
index 0000000..4633180
--- /dev/null
+++ b/gnotravex/src/puzzle-view.vala
@@ -0,0 +1,450 @@
+private class TileImage
+{
+ /* Tile being moved */
+ public Tile tile;
+
+ /* Location of tile */
+ public double x;
+ public double y;
+
+ /* Co-ordinates to move from */
+ public double source_x;
+ public double source_y;
+
+ /* Time started moving */
+ public double source_time;
+
+ /* Co-ordinates to target for */
+ public double target_x;
+ public double target_y;
+
+ /* Duration of movement */
+ public double duration;
+
+ public TileImage (Tile tile)
+ {
+ this.tile = tile;
+ }
+}
+
+public class PuzzleView : Gtk.DrawingArea
+{
+ /* Minimum size of a tile */
+ private const int minimum_size = 40;
+
+ /* Puzzle being rendered */
+ private Puzzle? _puzzle = null;
+ public Puzzle puzzle
+ {
+ get { return _puzzle; }
+ set
+ {
+ if (_puzzle != null)
+ SignalHandler.disconnect_by_func (_puzzle, null, this);
+ _puzzle = value;
+ tiles.remove_all ();
+ for (var y = 0; y < puzzle.size; y++)
+ {
+ for (var x = 0; x < puzzle.size * 2; x++)
+ {
+ var tile = puzzle.get_tile (x, y);
+ if (tile == null)
+ continue;
+
+ var image = new TileImage (tile);
+ move_tile_to_location (image, x, y);
+ tiles.insert (tile, image);
+ }
+ }
+ _puzzle.tile_moved.connect (tile_moved_cb);
+ queue_resize ();
+ }
+ }
+
+ /* Theme */
+ private Theme theme;
+
+ public bool click_to_move = false;
+
+ /* Tile being controlled by the mouse */
+ private TileImage? selected_tile = null;
+
+ /* The position inside the tile where the cursor is */
+ private double selected_x_offset;
+ private double selected_y_offset;
+
+ /* Tile images */
+ private HashTable<Tile, TileImage> tiles;
+
+ /* Animation timer */
+ private Timer animation_timer;
+ private uint animation_timeout = 0;
+
+ private bool _is_paused = false;
+ public bool is_paused
+ {
+ get { return _is_paused; }
+ set
+ {
+ _is_paused = value;
+ queue_draw ();
+ }
+ }
+
+ public PuzzleView ()
+ {
+ set_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
+
+ tiles = new HashTable <Tile, TileImage> (direct_hash, direct_equal);
+
+ theme = new Theme ();
+
+ animation_timer = new Timer ();
+ animation_timer.start ();
+ }
+
+ private void redraw_tile (TileImage image)
+ {
+ uint x_offset, y_offset, size, gap;
+ get_dimensions (out x_offset, out y_offset, out size, out gap);
+
+ queue_draw_area ((int) (image.x + 0.5), (int) (image.y + 0.5), (int) size, (int) size);
+ }
+
+ private void move_tile_to_location (TileImage image, uint x, uint y, double duration = 0)
+ {
+ uint x_offset, y_offset, size, gap;
+ get_dimensions (out x_offset, out y_offset, out size, out gap);
+
+ var target_x = x_offset + x * size;
+ if (x >= puzzle.size)
+ target_x += gap;
+ var target_y = y_offset + y * size;
+ move_tile (image, target_x, target_y, duration);
+ }
+
+ private void move_tile (TileImage image, double x, double y, double duration = 0)
+ {
+ if (image.x == x && image.y == y)
+ return;
+
+ image.source_x = image.x;
+ image.source_y = image.y;
+ image.source_time = animation_timer.elapsed ();
+ image.target_x = x;
+ image.target_y = y;
+ image.duration = duration;
+
+ /* Move immediately */
+ if (duration == 0)
+ {
+ redraw_tile (image);
+ image.x = image.target_x;
+ image.y = image.target_y;
+ redraw_tile (image);
+ return;
+ }
+
+ /* Start animation (maximum of 100fps) */
+ if (animation_timeout == 0)
+ animation_timeout = Timeout.add (10, animate_cb);
+ }
+
+ private bool animate_cb ()
+ {
+ var t = animation_timer.elapsed ();
+
+ uint x_offset, y_offset, size, gap;
+ get_dimensions (out x_offset, out y_offset, out size, out gap);
+
+ var animating = false;
+ var iter = HashTableIter<Tile, TileImage> (tiles);
+ while (true)
+ {
+ Tile tile;
+ TileImage image;
+ if (!iter.next (out tile, out image))
+ break;
+
+ if (image.x == image.target_x && image.y == image.target_y)
+ continue;
+
+ /* Redraw where the tile was */
+ redraw_tile (image);
+
+ /* Move the tile */
+ if (t >= image.source_time + image.duration)
+ {
+ image.x = image.target_x;
+ image.y = image.target_y;
+ }
+ else
+ {
+ var d = (t - image.source_time) / image.duration;
+ image.x = image.source_x + (image.target_x - image.source_x) * d;
+ image.y = image.source_y + (image.target_y - image.source_y) * d;
+ animating = true;
+ }
+
+ /* Draw where the tile is */
+ redraw_tile (image);
+ }
+
+ /* Keep animating if still have tiles */
+ if (animating)
+ return true;
+
+ animation_timeout = 0;
+ return false;
+ }
+
+ public override void get_preferred_width (out int minimum, out int natural)
+ {
+ var size = 0;
+ if (puzzle != null)
+ size = (int) ((puzzle.size * 2 + 1.5) * minimum_size);
+ minimum = natural = int.max (size, 500);
+ }
+
+ public override void get_preferred_height (out int minimum, out int natural)
+ {
+ var size = 0;
+ if (puzzle != null)
+ size = (int) ((puzzle.size + 1) * minimum_size);
+ minimum = natural = int.max (size, 300);
+ }
+
+ private void get_dimensions (out uint x, out uint y, out uint size, out uint gap)
+ {
+ /* Fit in with a half tile border and spacing between boards */
+ var width = (uint) (get_allocated_width () / (2 * puzzle.size + 1.5));
+ var height = (uint) (get_allocated_height () / (puzzle.size + 1));
+ size = uint.min (width, height);
+ gap = size / 2;
+ x = (get_allocated_width () - 2 * puzzle.size * size - gap) / 2;
+ y = (get_allocated_height () - puzzle.size * size) / 2;
+ }
+
+ private void tile_moved_cb (Puzzle puzzle, Tile tile, uint x, uint y)
+ {
+ move_tile_to_location (tiles.lookup (tile), x, y, 0.2);
+ }
+
+ public override bool configure_event (Gdk.EventConfigure event)
+ {
+ /* Move everything to its correct location */
+ var iter = HashTableIter<Tile, TileImage> (tiles);
+ while (true)
+ {
+ Tile tile;
+ TileImage image;
+ if (!iter.next (out tile, out image))
+ break;
+ uint x, y;
+ puzzle.get_tile_location (tile, out x, out y);
+ move_tile_to_location (image, x, y);
+ }
+ selected_tile = null;
+
+ return false;
+ }
+
+ public override bool draw (Cairo.Context context)
+ {
+ if (puzzle == null)
+ return false;
+
+ uint x_offset, y_offset, size, gap;
+ get_dimensions (out x_offset, out y_offset, out size, out gap);
+
+ /* Draw arrow */
+ context.save ();
+ var w = gap * 0.5;
+ var ax = x_offset + puzzle.size * size + (gap - w) * 0.5;
+ var ay = y_offset + puzzle.size * size * 0.5;
+ context.translate (ax, ay);
+ theme.draw_arrow (context, size, gap);
+ context.restore ();
+
+ /* Draw sockets */
+ for (var y = 0; y < puzzle.size; y++)
+ {
+ for (var x = 0; x < puzzle.size * 2; x++)
+ {
+ context.save ();
+ if (x >= puzzle.size)
+ context.translate (x_offset + gap + x * size, y_offset + y * size);
+ else
+ context.translate (x_offset + x * size, y_offset + y * size);
+ theme.draw_socket (context, size);
+ context.restore ();
+ }
+ }
+
+ /* Draw stationary tiles */
+ var iter = HashTableIter<Tile, TileImage> (tiles);
+ while (true)
+ {
+ Tile tile;
+ TileImage image;
+ if (!iter.next (out tile, out image))
+ break;
+
+ if (image == selected_tile || image.x != image.target_x || image.y != image.target_y)
+ continue;
+
+ context.save ();
+ context.translate ((int) (image.x + 0.5), (int) (image.y + 0.5));
+ if (is_paused)
+ theme.draw_paused_tile (context, size);
+ else
+ theme.draw_tile (context, size, tile);
+ context.restore ();
+ }
+
+ /* Draw moving tiles */
+ iter = HashTableIter<Tile, TileImage> (tiles);
+ while (true)
+ {
+ Tile tile;
+ TileImage image;
+ if (!iter.next (out tile, out image))
+ break;
+
+ if (image != selected_tile && image.x == image.target_x && image.y == image.target_y)
+ continue;
+
+ context.save ();
+ context.translate ((int) (image.x + 0.5), (int) (image.y + 0.5));
+ if (is_paused)
+ theme.draw_paused_tile (context, size);
+ else
+ theme.draw_tile (context, size, tile);
+ context.restore ();
+ }
+
+ /* Draw pause overlay */
+ if (is_paused)
+ {
+ context.set_source_rgba (0, 0, 0, 0.75);
+ context.paint ();
+
+ context.select_font_face ("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
+ context.set_font_size (get_allocated_width () * 0.125);
+
+ var text = _("Paused");
+ Cairo.TextExtents extents;
+ context.text_extents (text, out extents);
+ context.move_to ((get_allocated_width () - extents.width) / 2.0, (get_allocated_height () + extents.height) / 2.0);
+ context.set_source_rgb (1, 1, 1);
+ context.show_text (text);
+ }
+
+ return false;
+ }
+
+ private void pick_tile (double x, double y)
+ {
+ if (selected_tile != null)
+ return;
+
+ if (puzzle.is_solved)
+ return;
+
+ uint x_offset, y_offset, size, gap;
+ get_dimensions (out x_offset, out y_offset, out size, out gap);
+
+ var iter = HashTableIter<Tile, TileImage> (tiles);
+ while (true)
+ {
+ Tile tile;
+ TileImage image;
+ if (!iter.next (out tile, out image))
+ break;
+
+ if (x >= image.x && x <= image.x + size && y >= image.y && y <= image.y + size)
+ {
+ selected_tile = image;
+ selected_x_offset = x - image.x;
+ selected_y_offset = y - image.y;
+ }
+ }
+ }
+
+ private void drop_tile (double x, double y)
+ {
+ if (selected_tile == null)
+ return;
+
+ uint x_offset, y_offset, size, gap;
+ get_dimensions (out x_offset, out y_offset, out size, out gap);
+
+ /* Select from the middle of the tile */
+ x += size * 0.5 - selected_x_offset;
+ y += size * 0.5 - selected_x_offset;
+
+ var tile_y = (int) Math.floor ((y - y_offset) / size);
+ tile_y = int.max (tile_y, 0);
+ tile_y = int.min (tile_y, (int) puzzle.size - 1);
+
+ /* Check which side we are on */
+ int tile_x;
+ if (x > x_offset + size * puzzle.size + gap * 0.5)
+ {
+ tile_x = (int) puzzle.size + (int) Math.floor ((x - (x_offset + puzzle.size * size + gap)) / size);
+ tile_x = int.max (tile_x, (int) puzzle.size);
+ tile_x = int.min (tile_x, 2 * (int) puzzle.size - 1);
+ }
+ else
+ {
+ tile_x = (int) Math.floor ((x - x_offset) / size);
+ tile_x = int.max (tile_x, 0);
+ tile_x = int.min (tile_x, (int) puzzle.size - 1);
+ }
+
+ /* Drop the tile here, or move it back if can't */
+ uint selected_x, selected_y;
+ puzzle.get_tile_location (selected_tile.tile, out selected_x, out selected_y);
+ if (puzzle.can_switch (selected_x, selected_y, (uint) tile_x, (uint) tile_y))
+ puzzle.switch_tiles (selected_x, selected_y, (uint) tile_x, (uint) tile_y);
+ else
+ move_tile_to_location (selected_tile, selected_x, selected_y, 0.2);
+ selected_tile = null;
+ }
+
+ public override bool button_press_event (Gdk.EventButton event)
+ {
+ if (is_paused)
+ return false;
+
+ /* Ignore double click events */
+ if (event.type != Gdk.EventType.BUTTON_PRESS)
+ return false;
+
+ if (event.button == 1)
+ {
+ if (selected_tile == null)
+ pick_tile (event.x, event.y);
+ else if (click_to_move)
+ drop_tile (event.x, event.y);
+ }
+
+ return false;
+ }
+
+ public override bool button_release_event (Gdk.EventButton event)
+ {
+ if (event.button == 1 && selected_tile != null && !click_to_move)
+ drop_tile (event.x, event.y);
+
+ return false;
+ }
+
+ public override bool motion_notify_event (Gdk.EventMotion event)
+ {
+ if (selected_tile != null)
+ move_tile (selected_tile, (int) (event.x - selected_x_offset), (int) (event.y - selected_y_offset));
+
+ return false;
+ }
+}
diff --git a/gnotravex/src/puzzle.vala b/gnotravex/src/puzzle.vala
new file mode 100644
index 0000000..b5ebb4d
--- /dev/null
+++ b/gnotravex/src/puzzle.vala
@@ -0,0 +1,280 @@
+public class Tile
+{
+ /* Edge colors */
+ public int north;
+ public int west;
+ public int east;
+ public int south;
+
+ /* Solution location */
+ public uint x;
+ public uint y;
+
+ public Tile (uint x, uint y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+}
+
+public class Puzzle
+{
+ private uint _size;
+ public uint size
+ {
+ get { return _size; }
+ }
+ private Tile[,] board;
+
+ public signal void tile_moved (Tile tile, uint x, uint y);
+ public signal void solved ();
+
+ public bool is_solved
+ {
+ get
+ {
+ /* Solved if entire left hand side is complete (we ensure only tiles
+ that fit are allowed */
+ for (var x = 0; x < size; x++)
+ {
+ for (var y = 0; y < size; y++)
+ {
+ var tile = board[x, y];
+ if (tile == null)
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public Puzzle (uint size)
+ {
+ _size = size;
+ board = new Tile[size * 2, size];
+ for (var x = 0; x < size; x++)
+ for (var y = 0; y < size; y++)
+ board[x, y] = new Tile (x, y);
+
+ /* Pick random colours for edges */
+ for (var x = 0; x < size; x++)
+ {
+ for (var y = 0; y <= size; y++)
+ {
+ var n = Random.int_range (0, 10);
+ if (y - 1 >= 0)
+ board[x, y - 1].south = n;
+ if (y < size)
+ board[x, y].north = n;
+ }
+ }
+ for (var x = 0; x <= size; x++)
+ {
+ for (var y = 0; y < size; y++)
+ {
+ var n = Random.int_range (0, 10);
+ if (x - 1 >= 0)
+ board[x - 1, y].east = n;
+ if (x < size)
+ board[x, y].west = n;
+ }
+ }
+
+ /* Pick up the tiles... */
+ List<Tile> tiles = null;
+ for (var x = 0; x < size; x++)
+ {
+ for (var y = 0; y < size; y++)
+ {
+ tiles.append (board[x, y]);
+ board[x, y] = null;
+ }
+ }
+
+ /* ...and place then randomly on the right hand side */
+ for (var x = 0; x < size; x++)
+ {
+ for (var y = 0; y < size; y++)
+ {
+ var n = Random.int_range (0, (int32) tiles.length ());
+ var tile = tiles.nth_data (n);
+ board[x + size, y] = tile;
+ tiles.remove (tile);
+ }
+ }
+ }
+
+ public Tile? get_tile (uint x, uint y)
+ {
+ return board[x, y];
+ }
+
+ public void get_tile_location (Tile tile, out uint x, out uint y)
+ {
+ x = y = 0;
+ for (x = 0; x < size * 2; x++)
+ for (y = 0; y < size; y++)
+ if (board[x, y] == tile)
+ return;
+ }
+
+ public bool tile_fits (uint x0, uint y0, uint x1, uint y1)
+ {
+ var tile = board[x0, y0];
+ if (tile == null)
+ return false;
+
+ if (x1 > 0 && !(x1 - 1 == x0 && y1 == y0) && board[x1 - 1, y1] != null && board[x1 - 1, y1].east != tile.west)
+ return false;
+ if (x1 < size - 1 && !(x1 + 1 == x0 && y1 == y0) && board[x1 + 1, y1] != null && board[x1 + 1, y1].west != tile.east)
+ return false;
+ if (y1 > 0 && !(x1 == x0 && y1 - 1 == y0) && board[x1, y1 - 1] != null && board[x1, y1 - 1].south != tile.north)
+ return false;
+ if (y1 < size - 1 && !(x1 == x0 && y1 + 1 == y0) && board[x1, y1 + 1] != null && board[x1, y1 + 1].north != tile.south)
+ return false;
+
+ return true;
+ }
+
+ public bool can_switch (uint x0, uint y0, uint x1, uint y1)
+ {
+ if (x0 == x1 && y0 == y1)
+ return false;
+
+ var t0 = board[x0, y0];
+ var t1 = board[x1, y1];
+
+ /* No tiles to switch */
+ if (t0 == null && t1 == null)
+ return false;
+
+ /* If placing onto the final area check if it fits */
+ if (t0 != null && x1 < size && !tile_fits (x0, y0, x1, y1))
+ return false;
+ if (t1 != null && x0 < size && !tile_fits (x1, y1, x0, y0))
+ return false;
+
+ return true;
+ }
+
+ public void switch_tiles (uint x0, uint y0, uint x1, uint y1)
+ {
+ if (x0 == x1 && y0 == y1)
+ return;
+
+ var t0 = board[x0, y0];
+ var t1 = board[x1, y1];
+ board[x0, y0] = t1;
+ board[x1, y1] = t0;
+
+ if (t0 != null)
+ tile_moved (t0, x1, y1);
+ if (t1 != null)
+ tile_moved (t1, x0, y0);
+
+ if (is_solved)
+ solved ();
+ }
+
+ public bool can_move_up
+ {
+ get
+ {
+ for (var x = 0; x < size; x++)
+ if (board[x, 0] != null)
+ return false;
+ return true;
+ }
+ }
+
+ public void move_up ()
+ {
+ if (!can_move_up)
+ return;
+ for (var y = 1; y < size; y++)
+ for (var x = 0; x < size; x++)
+ switch_tiles (x, y, x, y - 1);
+ }
+
+ public bool can_move_down
+ {
+ get
+ {
+ for (var x = 0; x < size; x++)
+ if (board[x, size - 1] != null)
+ return false;
+ return true;
+ }
+ }
+
+ public void move_down ()
+ {
+ if (!can_move_down)
+ return;
+ for (var y = (int) size - 2; y >= 0; y--)
+ for (var x = 0; x < size; x++)
+ switch_tiles (x, y, x, y + 1);
+ }
+
+ public bool can_move_left
+ {
+ get
+ {
+ for (var y = 0; y < size; y++)
+ if (board[0, y] != null)
+ return false;
+ return true;
+ }
+ }
+
+ public void move_left ()
+ {
+ if (!can_move_left)
+ return;
+ for (var x = 1; x < size; x++)
+ for (var y = 0; y < size; y++)
+ switch_tiles (x, y, x - 1, y);
+ }
+
+ public bool can_move_right
+ {
+ get
+ {
+ for (var y = 0; y < size; y++)
+ if (board[size - 1, y] != null)
+ return false;
+ return true;
+ }
+ }
+
+ public void move_right ()
+ {
+ if (!can_move_right)
+ return;
+ for (var x = (int) size - 2; x >= 0; x--)
+ for (var y = 0; y < size; y++)
+ switch_tiles (x, y, x + 1, y);
+ }
+
+ public void solve ()
+ {
+ List<Tile> wrong_tiles = null;
+ for (var x = 0; x < size * 2; x++)
+ {
+ for (var y = 0; y < size; y++)
+ {
+ var tile = board[x, y];
+ if (tile != null && (tile.x != x || tile.y != y))
+ wrong_tiles.append (tile);
+ board[x, y] = null;
+ }
+ }
+
+ foreach (var tile in wrong_tiles)
+ {
+ board[tile.x, tile.y] = tile;
+ tile_moved (tile, tile.x, tile.y);
+ }
+ }
+}
diff --git a/gnotravex/src/theme.vala b/gnotravex/src/theme.vala
new file mode 100644
index 0000000..75a9410
--- /dev/null
+++ b/gnotravex/src/theme.vala
@@ -0,0 +1,272 @@
+public class Theme
+{
+ /* Colors of tiles and text */
+ private Cairo.Pattern tile_colors[10];
+ private Cairo.Pattern paused_color;
+ private Cairo.Pattern text_colors[10];
+
+ public Theme ()
+ {
+ tile_colors[0] = make_color_pattern ("#000000");
+ tile_colors[1] = make_color_pattern ("#C17D11");
+ tile_colors[2] = make_color_pattern ("#CC0000");
+ tile_colors[3] = make_color_pattern ("#F57900");
+ tile_colors[4] = make_color_pattern ("#EDD400");
+ tile_colors[5] = make_color_pattern ("#73D216");
+ tile_colors[6] = make_color_pattern ("#3465A4");
+ tile_colors[7] = make_color_pattern ("#75507B");
+ tile_colors[8] = make_color_pattern ("#BABDB6");
+ tile_colors[9] = make_color_pattern ("#FFFFFF");
+
+ paused_color = make_color_pattern ("#CCCCCC");
+
+ text_colors[0] = new Cairo.Pattern.rgb (1, 1, 1);
+ text_colors[1] = new Cairo.Pattern.rgb (1, 1, 1);
+ text_colors[2] = new Cairo.Pattern.rgb (1, 1, 1);
+ text_colors[3] = new Cairo.Pattern.rgb (1, 1, 1);
+ text_colors[4] = new Cairo.Pattern.rgb (0, 0, 0);
+ text_colors[5] = new Cairo.Pattern.rgb (0, 0, 0);
+ text_colors[6] = new Cairo.Pattern.rgb (1, 1, 1);
+ text_colors[7] = new Cairo.Pattern.rgb (1, 1, 1);
+ text_colors[8] = new Cairo.Pattern.rgb (0, 0, 0);
+ text_colors[9] = new Cairo.Pattern.rgb (0, 0, 0);
+ }
+
+ private Cairo.Pattern make_color_pattern (string color)
+ {
+ var r = (hex_value (color[1]) * 16 + hex_value (color[2])) / 255.0;
+ var g = (hex_value (color[3]) * 16 + hex_value (color[4])) / 255.0;
+ var b = (hex_value (color[5]) * 16 + hex_value (color[6])) / 255.0;
+ return new Cairo.Pattern.rgb (r, g, b);
+ }
+
+ private double hex_value (char c)
+ {
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ else
+ return 0;
+ }
+
+ public void draw_arrow (Cairo.Context context, uint size, uint gap)
+ {
+ var w = gap * 0.5;
+ var h = size * 1.5;
+ var depth = uint.min ((uint) (size * 0.025), 2);
+ var dx = 1.4142 * depth;
+ var dy = 6.1623 * depth;
+
+ /* Background */
+ context.move_to (0, 0);
+ context.line_to (w, h * 0.5);
+ context.line_to (w, -h * 0.5);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.125);
+ context.fill ();
+
+ /* Arrow highlight */
+ context.move_to (w, -h * 0.5);
+ context.line_to (w, h * 0.5);
+ context.line_to (w - depth, h * 0.5 - dy);
+ context.line_to (w - depth, -h * 0.5 + dy);
+ context.close_path ();
+ context.set_source_rgba (1, 1, 1, 0.125);
+ context.fill ();
+
+ /* Arrow shadow */
+ context.move_to (w, -h * 0.5);
+ context.line_to (0, 0);
+ context.line_to (w, h * 0.5);
+ context.line_to (w - depth, h * 0.5 - dy);
+ context.line_to (dx, 0);
+ context.line_to (w - depth, -h * 0.5 + dy);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.25);
+ context.fill ();
+ }
+
+ public void draw_socket (Cairo.Context context, uint size)
+ {
+ var depth = uint.min ((uint) (size * 0.05), 4);
+
+ /* Background */
+ context.rectangle (depth, depth, size - depth * 2, size - depth * 2);
+ context.set_source_rgba (0, 0, 0, 0.125);
+ context.fill ();
+
+ /* Shadow */
+ context.move_to (size, 0);
+ context.line_to (0, 0);
+ context.line_to (0, size);
+ context.line_to (depth, size - depth);
+ context.line_to (depth, depth);
+ context.line_to (size - depth, depth);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.25);
+ context.fill ();
+
+ /* Highlight */
+ context.move_to (0, size);
+ context.line_to (size, size);
+ context.line_to (size, 0);
+ context.line_to (size - depth, depth);
+ context.line_to (size - depth, size - depth);
+ context.line_to (depth, size - depth);
+ context.close_path ();
+ context.set_source_rgba (1, 1, 1, 0.125);
+ context.fill ();
+ }
+
+ public void draw_paused_tile (Cairo.Context context, uint size)
+ {
+ draw_tile_background (context, size, paused_color, paused_color, paused_color, paused_color);
+ }
+
+ public void draw_tile (Cairo.Context context, uint size, Tile? tile)
+ {
+ draw_tile_background (context, size, tile_colors[tile.north], tile_colors[tile.east], tile_colors[tile.south], tile_colors[tile.west]);
+
+ context.select_font_face ("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
+ context.set_font_size (size / 3.5);
+ context.set_source (text_colors[tile.north]);
+ draw_number (context, size * 0.5, size / 5, tile.north);
+ context.set_source (text_colors[tile.south]);
+ draw_number (context, size * 0.5, size * 4 / 5, tile.south);
+ context.set_source (text_colors[tile.east]);
+ draw_number (context, size * 4 / 5, size * 0.5, tile.east);
+ context.set_source (text_colors[tile.west]);
+ draw_number (context, size / 5, size * 0.5, tile.west);
+ }
+
+ private void draw_tile_background (Cairo.Context context, uint size, Cairo.Pattern north_color, Cairo.Pattern east_color, Cairo.Pattern south_color, Cairo.Pattern west_color)
+ {
+ var depth = uint.min ((uint) (size * 0.05), 4);
+ var dx = 2.4142 * depth;
+ var dy = 1.4142 * depth;
+
+ /* North */
+ context.rectangle (0, 0, size, size * 0.5);
+ context.set_source (north_color);
+ context.fill ();
+
+ /* North highlight */
+ context.move_to (0, 0);
+ context.line_to (size, 0);
+ context.line_to (size - dx, depth);
+ context.line_to (dx, depth);
+ context.line_to (size * 0.5, size * 0.5 - dy);
+ context.line_to (size * 0.5, size * 0.5);
+ context.close_path ();
+ context.set_source_rgba (1, 1, 1, 0.125);
+ context.fill ();
+
+ /* North shadow */
+ context.move_to (size, 0);
+ context.line_to (size * 0.5, size * 0.5);
+ context.line_to (size * 0.5, size * 0.5 - dy);
+ context.line_to (size - dx, depth);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.25);
+ context.fill ();
+
+ /* South */
+ context.rectangle (0, size * 0.5, size, size * 0.5);
+ context.set_source (south_color);
+ context.fill ();
+
+ /* South highlight */
+ context.move_to (0, size);
+ context.line_to (dx, size - depth);
+ context.line_to (size * 0.5, size * 0.5 + dy);
+ context.line_to (size * 0.5, size * 0.5);
+ context.close_path ();
+ context.set_source_rgba (1, 1, 1, 0.125);
+ context.fill ();
+
+ /* South shadow */
+ context.move_to (0, size);
+ context.line_to (size, size);
+ context.line_to (size * 0.5, size * 0.5);
+ context.line_to (size * 0.5, size * 0.5 + dy);
+ context.line_to (size - dx, size - depth);
+ context.line_to (dx, size - depth);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.25);
+ context.fill ();
+
+ /* East */
+ context.move_to (size, 0);
+ context.line_to (size, size);
+ context.line_to (size * 0.5, size * 0.5);
+ context.close_path ();
+ context.set_source (east_color);
+ context.fill ();
+
+ /* East highlight */
+ context.move_to (size, 0);
+ context.line_to (size * 0.5, size * 0.5);
+ context.line_to (size, size);
+ context.line_to (size - depth, size - dx);
+ context.line_to (size * 0.5 + dy, size * 0.5);
+ context.line_to (size - depth, dx);
+ context.close_path ();
+ context.set_source_rgba (1, 1, 1, 0.125);
+ context.fill ();
+
+ /* East shadow */
+ context.move_to (size, 0);
+ context.line_to (size, size);
+ context.line_to (size - depth, size - dx);
+ context.line_to (size - depth, dx);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.25);
+ context.fill ();
+
+ /* West */
+ context.move_to (0, 0);
+ context.line_to (0, size);
+ context.line_to (size * 0.5, size * 0.5);
+ context.close_path ();
+ context.set_source (west_color);
+ context.fill ();
+
+ /* West highlight */
+ context.move_to (0, 0);
+ context.line_to (0, size);
+ context.line_to (depth, size - dx);
+ context.line_to (depth, dx);
+ context.close_path ();
+ context.set_source_rgba (1, 1, 1, 0.125);
+ context.fill ();
+
+ /* West shadow */
+ context.move_to (0, 0);
+ context.line_to (size * 0.5, size * 0.5);
+ context.line_to (0, size);
+ context.line_to (depth, size - dx);
+ context.line_to (size * 0.5 - dy, size * 0.5);
+ context.line_to (depth, dx);
+ context.close_path ();
+ context.set_source_rgba (0, 0, 0, 0.25);
+ context.fill ();
+
+ /* Draw outline */
+ context.set_line_width (1.0);
+ context.set_source_rgb (0.0, 0.0, 0.0);
+ context.rectangle (0.5, 0.5, size - 1.0, size - 1.0);
+ context.stroke ();
+ }
+
+ private void draw_number (Cairo.Context context, double x, double y, uint number)
+ {
+ var text = "%u".printf (number);
+ Cairo.TextExtents extents;
+ context.text_extents (text, out extents);
+ context.move_to (x - extents.width / 2.0, y + extents.height / 2.0);
+ context.show_text (text);
+ }
+}
\ No newline at end of file
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]