[gnome-tetravex/arnaudb/new-ux: 1/3] Improve end-of-game UX.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-tetravex/arnaudb/new-ux: 1/3] Improve end-of-game UX.
- Date: Tue, 24 Sep 2019 18:12:47 +0000 (UTC)
commit a86a2b4207d519d12f43f78e52fed8e579cc380e
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Sun Sep 22 20:21:18 2019 +0200
Improve end-of-game UX.
po/POTFILES.in | 2 +
po/POTFILES.skip | 1 +
src/gnome-tetravex.gresource.xml | 5 +-
src/gnome-tetravex.vala | 87 ++++++++++++-------
src/history.vala | 175 +++++++++++++++++++++++++++++++++------
src/meson.build | 19 +++--
src/puzzle-view.vala | 69 ++++++++++++---
src/score-dialog.vala | 44 ++--------
src/score-overlay-entry.ui | 34 ++++++++
src/score-overlay.ui | 68 +++++++++++++++
src/score-overlay.vala | 164 ++++++++++++++++++++++++++++++++++++
src/tetravex.css | 38 +++++++++
src/theme.vala | 22 +++--
13 files changed, 607 insertions(+), 121 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 634ef6a..c3c0cbd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,4 +10,6 @@ src/help-overlay.ui
src/puzzle.vala
src/puzzle-view.vala
src/score-dialog.vala
+src/score-overlay.vala
+src/score-overlay.ui
src/theme.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 61008a4..4f4527e 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -2,4 +2,5 @@ src/gnome-tetravex.c
src/puzzle.c
src/puzzle-view.c
src/score-dialog.c
+src/score-overlay.c
src/theme.c
diff --git a/src/gnome-tetravex.gresource.xml b/src/gnome-tetravex.gresource.xml
index e405d53..4ee78ee 100644
--- a/src/gnome-tetravex.gresource.xml
+++ b/src/gnome-tetravex.gresource.xml
@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/Tetravex">
- <file preprocess="xml-stripblanks">gnome-tetravex.ui</file>
<file preprocess="xml-stripblanks">app-menu.ui</file>
+ <file preprocess="xml-stripblanks">gnome-tetravex.ui</file>
+ <file preprocess="xml-stripblanks">score-overlay.ui</file>
+ <file preprocess="xml-stripblanks">score-overlay-entry.ui</file>
+ <file>tetravex.css</file>
</gresource>
<gresource prefix="/org/gnome/Tetravex/gtk">
<file preprocess="xml-stripblanks">help-overlay.ui</file>
diff --git a/src/gnome-tetravex.vala b/src/gnome-tetravex.vala
index 34e21e5..1fec2c0 100644
--- a/src/gnome-tetravex.vala
+++ b/src/gnome-tetravex.vala
@@ -45,6 +45,8 @@ private class Tetravex : Gtk.Application
private SimpleAction solve_action;
private SimpleAction finish_action;
+ private ScoreOverlay score_overlay;
+
private const OptionEntry [] option_entries =
{
/* Translators: command-line option description, see 'gnome-tetravex --help' */
@@ -123,6 +125,12 @@ private class Tetravex : Gtk.Application
history = new History (Path.build_filename (Environment.get_user_data_dir (), "gnome-tetravex",
"history"));
+ CssProvider css_provider = new CssProvider ();
+ css_provider.load_from_resource ("/org/gnome/Tetravex/tetravex.css");
+ Gdk.Screen? gdk_screen = Gdk.Screen.get_default ();
+ if (gdk_screen != null) // else..?
+ StyleContext.add_provider_for_screen ((!) gdk_screen, css_provider,
STYLE_PROVIDER_PRIORITY_APPLICATION);
+
window = (ApplicationWindow) builder.get_object ("gnome-tetravex-window");
this.add_window (window);
window.key_press_event.connect (on_key_press_event);
@@ -172,7 +180,22 @@ private class Tetravex : Gtk.Application
view.hexpand = true;
view.vexpand = true;
view.button_release_event.connect (view_button_release_event);
- grid.attach (view, 0, 0, 3, 1);
+
+ Overlay overlay = new Overlay ();
+ overlay.add (view);
+ overlay.show ();
+
+ score_overlay = new ScoreOverlay ();
+ overlay.add_overlay (score_overlay);
+ overlay.set_overlay_pass_through (score_overlay, true);
+
+ view.bind_property ("board-height", score_overlay, "board-height", BindingFlags.DEFAULT |
BindingFlags.SYNC_CREATE);
+ view.bind_property ("x-offset-right", score_overlay, "margin-left", BindingFlags.DEFAULT |
BindingFlags.SYNC_CREATE);
+ view.bind_property ("right-margin", score_overlay, "margin-right", BindingFlags.DEFAULT |
BindingFlags.SYNC_CREATE);
+ view.bind_property ("y-offset", score_overlay, "margin-top", BindingFlags.DEFAULT |
BindingFlags.SYNC_CREATE);
+ view.bind_property ("y-offset", score_overlay, "margin-bottom", BindingFlags.DEFAULT |
BindingFlags.SYNC_CREATE);
+
+ grid.attach (overlay, 0, 0, 3, 1);
settings.bind ("mouse-use-extra-buttons", view,
"mouse-use-extra-buttons", SettingsBindFlags.GET |
SettingsBindFlags.NO_SENSITIVITY);
@@ -358,6 +381,7 @@ private class Tetravex : Gtk.Application
solve_action.set_enabled (true);
finish_action.set_enabled (false);
new_game_solve_stack.set_visible_child_name ("solve");
+ score_overlay.hide ();
if (puzzle_init_done)
SignalHandler.disconnect_by_func (puzzle, null, this);
@@ -428,35 +452,22 @@ private class Tetravex : Gtk.Application
{
DateTime date = new DateTime.now_local ();
uint duration = (uint) (puzzle.elapsed + 0.5);
- HistoryEntry entry = new HistoryEntry (date, puzzle.size, duration);
- history.add (entry);
- history.save ();
-
- int score_dialog_action = show_scores (entry, true);
- if (score_dialog_action == ResponseType.CLOSE)
- window.destroy ();
- else if (score_dialog_action == ResponseType.OK)
- new_game ();
- else if (score_dialog_action != ResponseType.REJECT)
- new_game_solve_stack.set_visible_child_name ("new-game");
- }
-
- private bool scores_dialog_visible = false; // security for #5
- private int show_scores (HistoryEntry? selected_entry = null, bool show_quit = false)
- {
- if (scores_dialog_visible)
- return ResponseType.REJECT;
-
- scores_dialog_visible = true;
- ScoreDialog dialog = new ScoreDialog (history, puzzle.size, selected_entry, show_quit);
- dialog.modal = true;
- dialog.transient_for = window;
-
- int result = dialog.run ();
- dialog.destroy ();
- scores_dialog_visible = false;
-
- return result;
+ last_history_entry = new HistoryEntry (date, puzzle.size, duration);
+
+ HistoryEntry? other_score_0;
+ HistoryEntry? other_score_1;
+ HistoryEntry? other_score_2;
+ uint position = history.get_place ((!) last_history_entry,
+ puzzle.size,
+ out other_score_0,
+ out other_score_1,
+ out other_score_2);
+ score_overlay.set_score (puzzle.size, position, (!) last_history_entry, other_score_0,
other_score_1, other_score_2);
+
+ new_game_solve_stack.set_visible_child_name ("new-game");
+ view.hide_right_sockets ();
+
+ score_overlay.show ();
}
private void new_game_cb ()
@@ -486,9 +497,21 @@ private class Tetravex : Gtk.Application
new_game ();
}
- private void scores_cb ()
+ private HistoryEntry? last_history_entry = null;
+ private bool scores_dialog_visible = false; // security for #5
+ private void scores_cb (/* SimpleAction action, Variant? variant */)
{
- show_scores ();
+ if (scores_dialog_visible)
+ return;
+
+ scores_dialog_visible = true;
+ ScoreDialog dialog = new ScoreDialog (history, puzzle.size, puzzle.is_solved ? last_history_entry :
null);
+ dialog.modal = true;
+ dialog.transient_for = window;
+
+ dialog.run ();
+ dialog.destroy ();
+ scores_dialog_visible = false;
}
private bool view_button_release_event (Widget widget, Gdk.EventButton event)
diff --git a/src/history.vala b/src/history.vala
index 822df20..aab7d41 100644
--- a/src/history.vala
+++ b/src/history.vala
@@ -14,21 +14,108 @@ private class History : Object
[CCode (notify = false)] public string filename { private get; protected construct; }
internal List<HistoryEntry> entries = new List<HistoryEntry> ();
+ /*\
+ * * getting
+ \*/
+
internal signal void entry_added (HistoryEntry entry);
- internal History (string filename)
+ internal uint get_place (HistoryEntry entry,
+ uint8 puzzle_size,
+ out HistoryEntry? other_entry_0,
+ out HistoryEntry? other_entry_1,
+ out HistoryEntry? other_entry_2)
{
- Object (filename: filename);
- load ();
+ entries.insert_sorted (entry, HistoryEntry.compare_entries);
+ entry_added (entry);
+ save ();
+
+ unowned List<HistoryEntry> entry_item = entries.find (entry);
+ unowned List<HistoryEntry> best_time_item;
+ uint best_position = get_best_time_position (entry_item, out best_time_item);
+ uint position = entries.position (entry_item) - best_position + 1;
+ switch (position)
+ {
+ case 1:
+ unowned List<HistoryEntry>? tmp_item = entry_item.next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ other_entry_0 = null;
+ other_entry_1 = null;
+ other_entry_2 = null;
+ break;
+ }
+ other_entry_0 = ((!) tmp_item).data;
+ tmp_item = ((!) tmp_item).next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ other_entry_1 = null;
+ other_entry_2 = null;
+ break;
+ }
+ other_entry_1 = ((!) tmp_item).data;
+ tmp_item = ((!) tmp_item).next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ other_entry_2 = null;
+ else
+ other_entry_2 = ((!) tmp_item).data;
+ break;
+
+ case 2:
+ other_entry_0 = best_time_item.data;
+ unowned List<HistoryEntry>? tmp_item = entry_item.next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ other_entry_1 = null;
+ other_entry_2 = null;
+ break;
+ }
+ other_entry_1 = ((!) tmp_item).data;
+ tmp_item = ((!) tmp_item).next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ other_entry_2 = null;
+ else
+ other_entry_2 = ((!) tmp_item).data;
+ break;
+
+ default:
+ other_entry_0 = best_time_item.data;
+ other_entry_1 = entry_item.prev.data;
+ unowned List<HistoryEntry>? next_entry_item = entry_item.next;
+ if (next_entry_item == null || ((!) next_entry_item).data.size != puzzle_size)
+ other_entry_2 = null;
+ else
+ other_entry_2 = ((!) next_entry_item).data;
+ break;
+ }
+ return position;
}
- internal void add (HistoryEntry entry)
+ private uint get_best_time_position (List<HistoryEntry> entry_item, out unowned List<HistoryEntry>
best_time_item)
{
- entries.append (entry);
- entry_added (entry);
+ uint8 puzzle_size = entry_item.data.size;
+ best_time_item = entries.first ();
+ if (puzzle_size == 2 || entry_item == best_time_item)
+ return 0;
+
+ best_time_item = entry_item;
+ do { best_time_item = best_time_item.prev; }
+ while (best_time_item != entries && best_time_item.data.size == puzzle_size);
+ best_time_item = best_time_item.next;
+ return entries.position (best_time_item);
+ }
+
+ /*\
+ * * loading
+ \*/
+
+ internal History (string filename)
+ {
+ Object (filename: filename);
+ load ();
}
- internal void load ()
+ private inline void load ()
{
string contents = "";
try
@@ -57,11 +144,34 @@ private class History : Object
// FIXME use try_parse
- add (new HistoryEntry ((!) date, size, duration));
+ entries.prepend (new HistoryEntry ((!) date, size, duration));
}
+ entries.sort (HistoryEntry.compare_entries);
+ }
+
+ private inline DateTime? parse_date (string date)
+ {
+ if (date.length < 19 || date[4] != '-' || date[7] != '-' || date[10] != 'T' || date[13] != ':' ||
date[16] != ':')
+ return null;
+
+ // FIXME use try_parse
+
+ int year = int.parse (date.substring (0, 4));
+ int month = int.parse (date.substring (5, 2));
+ int day = int.parse (date.substring (8, 2));
+ int hour = int.parse (date.substring (11, 2));
+ int minute = int.parse (date.substring (14, 2));
+ int seconds = int.parse (date.substring (17, 2));
+ string timezone = date.substring (19);
+
+ return new DateTime (new TimeZone (timezone), year, month, day, hour, minute, seconds);
}
- internal void save ()
+ /*\
+ * * saving
+ \*/
+
+ private inline void save ()
{
string contents = "";
@@ -81,24 +191,6 @@ private class History : Object
warning ("Failed to save history: %s", e.message);
}
}
-
- private DateTime? parse_date (string date)
- {
- if (date.length < 19 || date[4] != '-' || date[7] != '-' || date[10] != 'T' || date[13] != ':' ||
date[16] != ':')
- return null;
-
- // FIXME use try_parse
-
- int year = int.parse (date.substring (0, 4));
- int month = int.parse (date.substring (5, 2));
- int day = int.parse (date.substring (8, 2));
- int hour = int.parse (date.substring (11, 2));
- int minute = int.parse (date.substring (14, 2));
- int seconds = int.parse (date.substring (17, 2));
- string timezone = date.substring (19);
-
- return new DateTime (new TimeZone (timezone), year, month, day, hour, minute, seconds);
- }
}
private class HistoryEntry : Object // TODO make struct? needs using HistoryEntry? for the List...
@@ -111,4 +203,33 @@ private class HistoryEntry : Object // TODO make struct? needs using HistoryEntr
{
Object (date: date, size: size, duration: duration);
}
+
+ /*\
+ * * utilities
+ \*/
+
+ internal static string get_duration_string (uint duration)
+ {
+ if (duration >= 3600)
+ /* Translators: that is the duration of a game, as seen in the Scores dialog, if game has taken
one hour or more; the %u are replaced by the hours (h), minutes (m) and seconds (s); as an example, you might
want to use "%u:%.2u:%.2u", that is quite international (the ".2" meaning "two digits, padding with 0") */
+ return _("%uh %um %us").printf (duration / 3600, (duration / 60) % 60, duration % 60);
+
+ if (duration >= 60)
+ /* Translators: that is the duration of a game, as seen in the Scores dialog, if game has taken
between one minute and one hour; the %u are replaced by the minutes (m) and seconds (s); as an example, you
might want to use "%.2u:%.2u", that is quite international (the ".2" meaning "two digits, padding with 0") */
+ return _("%um %us").printf (duration / 60, duration % 60);
+
+ else
+ /* Translators: that is the duration of a game, as seen in the Scores dialog, if game has taken
less than one minute; the %u is replaced by the number of seconds (s) it has taken; as an example, you might
want to use "00:%.2u", that is quite international (the ".2" meaning "two digits, padding with 0") */
+ return _("%us").printf (duration);
+ }
+
+ internal static int compare_entries (HistoryEntry a, HistoryEntry b)
+ {
+ if (a.size != b.size)
+ return (int) a.size - (int) b.size;
+ if (a.duration != b.duration)
+ return (int) a.duration - (int) b.duration;
+ else
+ return a.date.compare (b.date);
+ }
}
diff --git a/src/meson.build b/src/meson.build
index 5aaf72b..e9363bf 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -2,14 +2,19 @@ resources = gnome.compile_resources ('resources', 'gnome-tetravex.gresource.xml'
source_dir: '.',
c_name: 'resources')
+sources = files (
+ 'config.vapi',
+ 'gnome-tetravex.vala',
+ 'history.vala',
+ 'puzzle.vala',
+ 'puzzle-view.vala',
+ 'score-dialog.vala',
+ 'score-overlay.vala',
+ 'theme.vala'
+)
+
gnome_tetravex = executable ('gnome-tetravex',
- [ 'config.vapi',
- 'gnome-tetravex.vala',
- 'history.vala',
- 'puzzle.vala',
- 'puzzle-view.vala',
- 'score-dialog.vala',
- 'theme.vala'] + resources,
+ sources + resources,
dependencies: [ glib_dep,
gtk_dep,
libm_dep ],
diff --git a/src/puzzle-view.vala b/src/puzzle-view.vala
index e57e42b..017d26a 100644
--- a/src/puzzle-view.vala
+++ b/src/puzzle-view.vala
@@ -62,6 +62,7 @@ private class PuzzleView : Gtk.DrawingArea
if (puzzle_init_done)
SignalHandler.disconnect_by_func ((!) _puzzle, null, this);
+ show_right_sockets ();
_puzzle = value;
last_selected_tile = null;
tiles.remove_all ();
@@ -110,8 +111,12 @@ private class PuzzleView : Gtk.DrawingArea
private uint animation_timeout = 0;
/* Set in configure event */
+ [CCode (notify = true)] internal uint board_height { internal get; private set; default = 0; }
+ [CCode (notify = true)] internal uint x_offset_right { internal get; private set; default = 0; }
+ [CCode (notify = true)] internal uint y_offset { internal get; private set; default = 0; }
+ [CCode (notify = true)] internal uint right_margin { internal get; private set; default = 0; }
private uint x_offset = 0;
- private uint y_offset = 0;
+ private uint gap_offset = 0;
private uint tilesize = 0;
private uint gap = 0;
private double arrow_x = 0.0;
@@ -264,17 +269,21 @@ private class PuzzleView : Gtk.DrawingArea
uint width = (uint) (allocated_width / (2 * puzzle.size + 1.5));
uint height = (uint) (allocated_height / (puzzle.size + 1));
tilesize = uint.min (width, height);
+ board_height = puzzle.size * tilesize;
gap = tilesize / 2;
- x_offset = (allocated_width - 2 * puzzle.size * tilesize - gap) / 2;
- y_offset = (allocated_height - puzzle.size * tilesize ) / 2;
+ x_offset = (allocated_width - 2 * board_height - gap) / 2;
+ y_offset = (allocated_height - board_height ) / 2;
+ gap_offset = x_offset + board_height;
+ x_offset_right = gap_offset + gap;
+ right_margin = allocated_width - x_offset_right - board_height;
board_x_maxi = allocated_width - (int) tilesize;
board_y_maxi = allocated_height - (int) tilesize;
- snap_distance = (tilesize * puzzle.size) / 40.0;
+ snap_distance = board_height / 40.0;
- arrow_x = x_offset + puzzle.size * tilesize + gap * 0.25;
- arrow_y = y_offset + puzzle.size * tilesize * 0.5;
+ arrow_x = gap_offset + gap * 0.25;
+ arrow_y = y_offset + board_height * 0.5;
/* Precalculate sockets positions */
for (uint y = 0; y < puzzle.size; y++)
@@ -316,12 +325,12 @@ private class PuzzleView : Gtk.DrawingArea
/* Draw arrow */
context.save ();
context.translate (arrow_x, arrow_y);
- theme.draw_arrow (context, tilesize, gap);
+ theme.draw_arrow (context, tilesize, gap, socket_animation_level);
context.restore ();
/* Draw sockets */
for (uint y = 0; y < puzzle.size; y++)
- for (uint x = 0; x < puzzle.size * 2; x++)
+ for (uint x = 0; x < puzzle.size; x++)
{
context.save ();
context.translate (sockets_xs [x, y], sockets_ys [x, y]);
@@ -329,6 +338,15 @@ private class PuzzleView : Gtk.DrawingArea
context.restore ();
}
+ for (uint y = 0; y < puzzle.size; y++)
+ for (uint x = puzzle.size; x < puzzle.size * 2; x++)
+ {
+ context.save ();
+ context.translate (sockets_xs [x, y], sockets_ys [x, y]);
+ theme.draw_socket (context, tilesize, socket_animation_level);
+ context.restore ();
+ }
+
/* Draw tiles */
SList<TileImage> moving_tiles = new SList<TileImage> ();
HashTableIter<Tile, TileImage> iter = HashTableIter<Tile, TileImage> (tiles);
@@ -430,7 +448,7 @@ private class PuzzleView : Gtk.DrawingArea
private bool on_right_half (double x)
{
- return x > x_offset + tilesize * puzzle.size + gap * 0.5;
+ return x > x_offset_right - gap * 0.5;
}
private void drop_tile (double x, double y)
@@ -449,7 +467,7 @@ private class PuzzleView : Gtk.DrawingArea
int16 tile_x;
if (on_right_half (x))
{
- tile_x = (int16) puzzle.size + (int16) Math.floor ((x - (x_offset + puzzle.size * tilesize +
gap)) / tilesize);
+ tile_x = (int16) puzzle.size + (int16) Math.floor ((x - x_offset_right) / tilesize);
tile_x = tile_x.clamp ((int16) puzzle.size, 2 * (int16) puzzle.size - 1);
}
else
@@ -644,6 +662,10 @@ private class PuzzleView : Gtk.DrawingArea
tile_selected = false;
}
+ /*\
+ * * history proxies
+ \*/
+
internal void undo ()
{
last_selected_tile = null;
@@ -655,4 +677,31 @@ private class PuzzleView : Gtk.DrawingArea
last_selected_tile = null;
puzzle.redo ();
}
+
+ /*\
+ * * final animation
+ \*/
+
+ private uint8 socket_animation_level = 0;
+
+ internal void hide_right_sockets ()
+ {
+ Timeout.add (75, () => {
+ socket_animation_level++;
+ queue_draw_area ((int) gap_offset,
+ (int) y_offset,
+ (int) (board_height + gap),
+ (int) board_height);
+
+ if (socket_animation_level < 17)
+ return Source.CONTINUE;
+ else
+ return Source.REMOVE;
+ });
+ }
+
+ private inline void show_right_sockets ()
+ {
+ socket_animation_level = 0;
+ }
}
diff --git a/src/score-dialog.vala b/src/score-dialog.vala
index 45350a4..49560ae 100644
--- a/src/score-dialog.vala
+++ b/src/score-dialog.vala
@@ -20,26 +20,14 @@ private class ScoreDialog : Dialog
private ComboBox size_combo;
private TreeView scores;
- internal ScoreDialog (History history, uint8 size, HistoryEntry? selected_entry = null, bool show_quit =
false)
+ internal ScoreDialog (History history, uint8 size, HistoryEntry? selected_entry = null)
{
+ Object (use_header_bar: /* true */ 1);
+
this.history = history;
history.entry_added.connect (entry_added_cb);
this.selected_entry = selected_entry;
- if (show_quit)
- {
- /* Translators: label of a button of the Scores dialog, as it is displayed at the end of a game;
quits the application */
- add_button (_("Quit"), ResponseType.CLOSE);
-
-
- /* Translators: label of a button of the Scores dialog, as it is displayed at the end of a game;
starts a new game */
- add_button (_("New Game"), ResponseType.OK);
- }
- else
- {
- /* Translators: label of a button of the Scores dialog, as it is displayed when called from the
hamburger menu; closes the dialog */
- add_button (_("OK"), ResponseType.DELETE_EVENT);
- }
set_size_request (200, 300);
Box vbox = new Box (Orientation.VERTICAL, 5);
@@ -88,7 +76,7 @@ private class ScoreDialog : Dialog
scroll.add (scores);
List<unowned HistoryEntry> entries = history.entries.copy ();
- entries.sort (compare_entries);
+ entries.sort (HistoryEntry.compare_entries);
foreach (HistoryEntry entry in entries)
entry_added_cb (entry);
@@ -102,7 +90,7 @@ private class ScoreDialog : Dialog
score_model.clear ();
List<unowned HistoryEntry> entries = history.entries.copy ();
- entries.sort (compare_entries);
+ entries.sort (HistoryEntry.compare_entries);
foreach (HistoryEntry entry in entries)
{
@@ -112,18 +100,7 @@ private class ScoreDialog : Dialog
/* "the preferred date representation for the current locale without the time" */
string date_label = entry.date.format ("%x");
- string time_label;
- if (entry.duration >= 3600)
- /* Translators: that is the duration of a game, as seen in the Scores dialog, if game has
taken one hour or more; the %u are replaced by the hours (h), minutes (m) and seconds (s); as an example, you
might want to use "%u:%.2u:%.2u", that is quite international (the ".2" meaning "two digits, padding with 0")
*/
- time_label = _("%uh %um %us").printf (entry.duration / 3600, (entry.duration / 60) % 60,
entry.duration % 60);
-
- else if (entry.duration >= 60)
- /* Translators: that is the duration of a game, as seen in the Scores dialog, if game has
taken between one minute and one hour; the %u are replaced by the minutes (m) and seconds (s); as an example,
you might want to use "%.2u:%.2u", that is quite international (the ".2" meaning "two digits, padding with
0") */
- time_label = _("%um %us").printf (entry.duration / 60, entry.duration % 60);
-
- else
- /* Translators: that is the duration of a game, as seen in the Scores dialog, if game has
taken less than one minute; the %u is replaced by the number of seconds (s) it has taken; as an example, you
might want to use "00:%.2u", that is quite international (the ".2" meaning "two digits, padding with 0") */
- time_label = _("%us").printf (entry.duration);
+ string time_label = HistoryEntry.get_duration_string (entry.duration);
int weight = Pango.Weight.NORMAL;
if (entry == selected_entry)
@@ -149,15 +126,6 @@ private class ScoreDialog : Dialog
}
}
- private static int compare_entries (HistoryEntry a, HistoryEntry b)
- {
- if (a.size != b.size)
- return (int) a.size - (int) b.size;
- if (a.duration != b.duration)
- return (int) a.duration - (int) b.duration;
- return a.date.compare (b.date);
- }
-
private void size_changed_cb (ComboBox combo)
{
TreeIter iter;
diff --git a/src/score-overlay-entry.ui b/src/score-overlay-entry.ui
new file mode 100644
index 0000000..780e5ee
--- /dev/null
+++ b/src/score-overlay-entry.ui
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="ScoreOverlayEntry" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="width-request">160</property> <!-- two tiles of 80 max -->
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel" id="place_label">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="top-attach">2</property>
+ <property name="left-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="value_label">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">end</property>
+ </object>
+ <packing>
+ <property name="top-attach">2</property>
+ <property name="left-attach">2</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/score-overlay.ui b/src/score-overlay.ui
new file mode 100644
index 0000000..72bd2fd
--- /dev/null
+++ b/src/score-overlay.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="ScoreOverlay" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="row-homogeneous">True</property>
+ <property name="column-homogeneous">True</property>
+ <property name="column-spacing">4</property>
+ <style>
+ <class name="score-overlay"/>
+ </style>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Congratulations!</property>
+ <style>
+ <class name="score-title"/>
+ </style>
+ </object>
+ <packing>
+ <property name="top-attach">0</property>
+ <property name="left-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="ScoreOverlayEntry" id="score_0"/>
+ <packing>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="ScoreOverlayEntry" id="score_1"/>
+ <packing>
+ <property name="top-attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="ScoreOverlayEntry" id="score_2"/>
+ <packing>
+ <property name="top-attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="ScoreOverlayEntry" id="score_3"/>
+ <packing>
+ <property name="top-attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Show scores</property>
+ <property name="action-name">app.scores</property>
+ </object>
+ <packing>
+ <property name="top-attach">6</property>
+ <property name="left-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/score-overlay.vala b/src/score-overlay.vala
new file mode 100644
index 0000000..7ab9b6a
--- /dev/null
+++ b/src/score-overlay.vala
@@ -0,0 +1,164 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2019 Arnaud Bonatti
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Tetravex/score-overlay.ui")]
+private class ScoreOverlay : Grid
+{
+ [CCode (notify = true)] internal uint board_height
+ {
+ internal set
+ {
+ if (value < 250 && value != 0)
+ get_style_context ().add_class ("small-window");
+ else
+ get_style_context ().remove_class ("small-window");
+ }
+ }
+
+ /*\
+ * * updating labels
+ \*/
+
+ [GtkChild] private ScoreOverlayEntry score_0;
+ [GtkChild] private ScoreOverlayEntry score_1;
+ [GtkChild] private ScoreOverlayEntry score_2;
+ [GtkChild] private ScoreOverlayEntry score_3;
+
+ internal void set_score (uint8 puzzle_size,
+ uint /* [1[ */ position,
+ HistoryEntry entry,
+ HistoryEntry? other_entry_0,
+ HistoryEntry? other_entry_1,
+ HistoryEntry? other_entry_2)
+ {
+ switch (position)
+ {
+ case 1:
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has been the fastest for a puzzle of this size; introduces the game time */
+ score_0.set_place_label (_("New best time!"));
+ score_0.set_value_label (HistoryEntry.get_duration_string (entry.duration), true);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has been the fastest for a puzzle of this size; introduces the old best time */
+ score_1.set_place_label (_("Second:"));
+ if (other_entry_0 != null)
+ score_1.set_value_label (HistoryEntry.get_duration_string (((!)
other_entry_0).duration));
+ else
+ score_1.set_value_label (null);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has been the fastest for a puzzle of this size; introduces the old second best time */
+ score_2.set_place_label (_("Third:"));
+ if (other_entry_1 != null)
+ score_2.set_value_label (HistoryEntry.get_duration_string (((!)
other_entry_1).duration));
+ else
+ score_2.set_value_label (null);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has been the fastest for a puzzle of this size; introduces the old third best time */
+ score_3.set_place_label (_("Out of podium:"));
+ if (other_entry_2 != null)
+ score_3.set_value_label (HistoryEntry.get_duration_string (((!)
other_entry_2).duration));
+ else
+ score_3.set_value_label (null);
+ break;
+
+ case 2:
+ if (other_entry_0 == null)
+ assert_not_reached ();
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has made the second best time for a puzzle of this size; introduces the best time ever */
+ score_0.set_place_label (_("Best time:"));
+ score_0.set_value_label (HistoryEntry.get_duration_string (((!) other_entry_0).duration));
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has made the second best time for a puzzle of this size; introduces the game time */
+ score_1.set_place_label (_("Your time:"));
+ score_1.set_value_label (HistoryEntry.get_duration_string (entry.duration), true);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has made the second best time for a puzzle of this size; introduces the old second best time */
+ score_2.set_place_label (_("Third:"));
+ if (other_entry_1 != null)
+ score_2.set_value_label (HistoryEntry.get_duration_string (((!)
other_entry_1).duration));
+ else
+ score_2.set_value_label (null);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has made the second best time for a puzzle of this size; introduces the old third best time */
+ score_3.set_place_label (_("Out of podium:"));
+ if (other_entry_2 != null)
+ score_3.set_value_label (HistoryEntry.get_duration_string (((!)
other_entry_2).duration));
+ else
+ score_3.set_value_label (null);
+ break;
+
+ default:
+ if (other_entry_0 == null || other_entry_1 == null)
+ assert_not_reached ();
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has not made the first or second best time for a puzzle of this size; introduces the best time
ever */
+ score_0.set_place_label (_("Best time:"));
+ score_0.set_value_label (HistoryEntry.get_duration_string (((!) other_entry_0).duration));
+
+ if (position == 3)
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has not made the first or second best time for a puzzle of this size; introduces the second best
time */
+ score_1.set_place_label (_("Second:"));
+
+ else if (position == 4)
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has not made the first or second best time for a puzzle of this size; introduces the third best
time */
+ score_1.set_place_label (_("Third:"));
+
+ else
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has not made the first or second best time for a puzzle of this size; the %u is replaced by the
rank before the one of the game played */
+ score_1.set_place_label (_("Place %u:").printf (position - 1));
+ score_1.set_value_label (HistoryEntry.get_duration_string (((!) other_entry_1).duration));
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has not made the first or second best time for a puzzle of this size; introduces the game time */
+ score_2.set_place_label (_("Your time:"));
+ score_2.set_value_label (HistoryEntry.get_duration_string (entry.duration), true);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if
the player has not made the first or second best time for a puzzle of this size; the %u is replaced by the
rank after the one of the game played */
+ score_3.set_place_label (_("Place %u:").printf (position + 1));
+ if (other_entry_2 != null)
+ score_3.set_value_label (HistoryEntry.get_duration_string (((!)
other_entry_2).duration));
+ else
+ score_3.set_value_label (null);
+ break;
+ }
+ }
+}
+
+[GtkTemplate (ui = "/org/gnome/Tetravex/score-overlay-entry.ui")]
+private class ScoreOverlayEntry : Grid
+{
+ [GtkChild] private Label place_label;
+ [GtkChild] private Label value_label;
+
+ internal void set_place_label (string label)
+ {
+ place_label.set_label (label);
+ }
+
+ internal void set_value_label (string? label, bool bold_label = false)
+ {
+ if (label != null)
+ {
+ value_label.set_label ((!) label);
+ value_label.get_style_context ().remove_class ("italic-label");
+ }
+ else
+ {
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; appears if the
player has made one of the worst scores for a game of this size; says that the rank after the one of the game
is "free", inviting to do worse */
+ value_label.set_label (_("Free!"));
+ value_label.get_style_context ().add_class ("italic-label");
+ }
+
+ if (bold_label)
+ value_label.get_style_context ().add_class ("bold-label");
+ else
+ value_label.get_style_context ().remove_class ("bold-label");
+ }
+}
diff --git a/src/tetravex.css b/src/tetravex.css
new file mode 100644
index 0000000..835d29c
--- /dev/null
+++ b/src/tetravex.css
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 Arnaud Bonatti
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+ .score-title {
+ font-weight:bolder;
+
+ font-size:200%;
+ margin-bottom:1.5rem;
+ margin-top:0.5rem;
+
+ transition:font-size 0.5s,
+ margin-bottom 0.5s,
+ margin-top 0.5s;
+}
+.small-window .score-title {
+ font-size:150%;
+ margin-bottom:1rem;
+ margin-top:0.2rem;
+}
+
+grid.score-overlay button {
+ margin-top:0.5rem;
+}
+
+.bold-label {
+ font-weight:bolder;
+}
+
+.italic-label {
+ font-style:italic;
+}
diff --git a/src/theme.vala b/src/theme.vala
index 769f11a..42b6ba3 100644
--- a/src/theme.vala
+++ b/src/theme.vala
@@ -71,11 +71,13 @@ private class Theme : Object
* * drawing fixed things
\*/
- internal void draw_arrow (Cairo.Context context, uint size, uint gap)
+ internal void draw_arrow (Cairo.Context context, uint size, uint gap, uint animation_level = /* 0-16 */
0)
{
double w = gap * 0.5;
double h = size * 1.5;
- uint depth = uint.min ((uint) (size * 0.025), 2);
+ double depth = double.min (size * 0.025, 2.0) - (double) animation_level / 6.0;
+ if (depth <= 0.0)
+ depth = 0.0;
double dx = 1.4142 * depth;
double dy = 6.1623 * depth;
@@ -84,7 +86,10 @@ private class Theme : Object
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);
+ if (animation_level == 0)
+ context.set_source_rgba (0, 0, 0, 0.125);
+ else
+ context.set_source_rgba (0, 0, 0, 0.125 * (16.0 - (double) animation_level) / 16.0);
context.fill ();
/* Arrow highlight */
@@ -108,13 +113,18 @@ private class Theme : Object
context.fill ();
}
- internal void draw_socket (Cairo.Context context, uint size)
+ internal void draw_socket (Cairo.Context context, uint size, uint animation_level = /* 0-16 */ 0)
{
- uint depth = uint.min ((uint) (size * 0.05), 4);
+ double depth = double.min (size * 0.05, 4.0) - (double) animation_level / 4.0;
+ if (depth <= 0.0)
+ depth = 0.0;
/* Background */
context.rectangle (depth, depth, size - depth * 2, size - depth * 2);
- context.set_source_rgba (0, 0, 0, 0.125);
+ if (animation_level == 0)
+ context.set_source_rgba (0, 0, 0, 0.125);
+ else
+ context.set_source_rgba (0, 0, 0, 0.125 * (16.0 - (double) animation_level) / 16.0);
context.fill ();
/* Shadow */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]