[swell-foop/arnaudb/wip/gtk4: 12/30] Drop Clutter.




commit bd786f890e836b5fe7361b955d704923b724c03e
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Thu May 14 20:46:45 2020 +0200

    Drop Clutter.

 data/swell-foop.css                   |  71 +++-
 data/ui/game-view.ui                  |  86 +++++
 src/game-view.vala                    | 594 +++++++++++++++-------------------
 src/org.gnome.SwellFoop.gresource.xml |   1 +
 src/swell-foop.vala                   |  61 +---
 src/window.vala                       |  57 +++-
 6 files changed, 482 insertions(+), 388 deletions(-)
---
diff --git a/data/swell-foop.css b/data/swell-foop.css
index e7db802..80b4de2 100644
--- a/data/swell-foop.css
+++ b/data/swell-foop.css
@@ -1,3 +1,11 @@
+.board {
+  background-color: black;
+}
+
+/*\
+* * first game screen
+\*/
+
 .welcome {
   font-size:1.5em;
 }
@@ -5,7 +13,7 @@
 .tip {
   font-size: 1em;
 }
- 
+
 .play {
   font-size: 1em;
   padding-right:12px;
@@ -13,3 +21,64 @@
   padding-top:6px;
   padding-bottom:6px;
 }
+
+/*\
+* * tiles
+\*/
+
+/* TODO handle opacity this way */
+.tile {
+  transition: opacity 0.2s;
+}
+.tile.null-opacity {
+  opacity:0.0;
+}
+.tile.half-opacity {
+  opacity:0.7;
+}
+.tile.full-opacity {
+  opacity:1.0;
+}
+
+/* tile images */
+.tile.red {
+  background-image: -gtk-icontheme('red');
+}
+.tile.blue {
+  background-image: -gtk-icontheme('blue');
+}
+.tile.green {
+  background-image: -gtk-icontheme('green');
+}
+.tile.yellow {
+  background-image: -gtk-icontheme('yellow');
+}
+
+/*\
+* * scores
+\*/
+
+.final-score {
+  color:white;
+  font-family: "Bitstream Vera Sans";
+}
+
+.final-score-label {
+  font-size:28px;   /* warning: more or less the bigger we can set, when playing on small size */
+  padding:1em 0.1em;
+}
+
+.final-score-button {
+  font-size:18px;
+  padding:0.3em 1.2em;
+}
+
+.score-label {
+  color:white;
+  font-family: "Bitstream Vera Sans";
+  font-size:28px;
+}
+
+/*\
+* * the end
+\*/
diff --git a/data/ui/game-view.ui b/data/ui/game-view.ui
new file mode 100644
index 0000000..736d0c5
--- /dev/null
+++ b/data/ui/game-view.ui
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   This file is part of Swell-Foop.
+
+   Copyright (C) 2020 Arnaud Bonatti <arnaud bonatti gmail com>
+
+   Swell-Foop 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.
+
+   Swell-Foop is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Swell-Foop.  If not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+  <requires lib="gtk+" version="3.98"/>
+  <template class="GameView" parent="GtkWidget">
+    <child>
+      <object class="GtkOverlay">
+        <child>
+          <object class="Board" id="board"/>
+        </child>
+        <child type="overlay">
+          <object class="GtkRevealer" id="score_revealer">
+            <property name="can-target">False</property>
+            <property name="transition-duration">250</property>
+            <property name="transition-type">crossfade</property>
+            <child>
+              <object class="GtkLabel" id="score_label">
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <style>
+                  <class name="score-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child type="overlay">
+          <object class="GtkRevealer" id="final_score_revealer">
+            <property name="visible">False</property>
+            <property name="transition-type">crossfade</property>
+            <child>
+              <object class="GtkGrid">
+                <property name="orientation">vertical</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <style>
+                  <class name="final-score"/>
+                </style>
+                <child>
+                  <object class="GtkLabel" id="final_score_label">
+                    <property name="justify">center</property>
+                    <style>
+                      <class name="final-score-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="use-underline">True</property>
+                    <!-- Translators: text of a button that appears on the board at the end of a game -->
+                    <property name="label" translatable="yes">_Play Again</property>
+                    <property name="action-name">win.new-game</property>
+                    <property name="halign">center</property>
+                    <style>
+                      <class name="suggested-action"/>
+                      <class name="final-score-button"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/game-view.vala b/src/game-view.vala
index 69b50b8..154a03d 100644
--- a/src/game-view.vala
+++ b/src/game-view.vala
@@ -15,72 +15,122 @@
  *  events.
  */
 
-using Config;
+using Gtk;
 
-private class GameView : GtkClutter.Embed
+[GtkTemplate (ui = "/org/gnome/SwellFoop/ui/game-view.ui")]
+private class GameView : Widget
 {
-    private Clutter.Stage stage;
-    private GameGroup group;
+    [GtkChild] private Board board;
+
+    private Game game;
+    private ulong game_complete_handler = 0;
+    private ulong score_updated_handler = 0;
 
     construct
     {
-        stage = (Clutter.Stage) get_stage ();
-        stage.background_color = Clutter.Color.from_string ("#000000");  /* background color is black */
-
-        group = new GameGroup ();
-        stage.add_child (group);
-
-        /* Request an appropriate size for the game view */
-        init_size ();
+        BinLayout layout = new BinLayout ();
+        set_layout_manager (layout);
     }
 
-    private void init_size ()
+    internal void set_game (Game game)
     {
-        stage.set_size (group.width, group.height);
-        set_size_request ((int) stage.width, (int) stage.height);
+        if (game_complete_handler != 0 || score_updated_handler != 0)
+            SignalHandler.disconnect_by_func (game, null, this);
+
+        this.game = game;
+        game_complete_handler = game.complete.connect (on_game_complete);
+        score_updated_handler = game.update_score.connect (on_score_updated);
+
+        board.game = game;
+
+        on_new_game ();
     }
 
     /*\
     * * proxy calls
     \*/
 
-    internal void set_game (Game game)
+    private bool is_zealous = true;
+    internal void set_is_zealous (bool is_zealous)
     {
-        group.game = game;
-        init_size ();
+        this.is_zealous = is_zealous;
+        board.is_zealous = is_zealous;
     }
 
-    internal void set_theme_name (string theme_name)
+    internal void board_left_cb ()
     {
-        group.theme_name = theme_name;
+        board.board_left_cb ();
     }
 
-    internal void set_is_zealous (bool is_zealous)
+    internal void cursor_move (int x, int y)
     {
-        group.is_zealous = is_zealous;
+        board.cursor_move (x, y);
     }
 
-    internal void board_left_cb ()
+    internal void cursor_click ()
     {
-        group.board_left_cb ();
+        board.cursor_click ();
     }
 
-    internal void cursor_move (int x, int y)
+    /*\
+    * * scores
+    \*/
+
+    [GtkChild] private Revealer score_revealer;
+    [GtkChild] private Label    score_label;
+
+    private ulong score_revealed_handler = 0;
+
+    private inline void on_score_updated (uint points)
     {
-        group.cursor_move (x, y);
+        if (!is_zealous || points == 0)
+            return;
+
+        /* Translators: text displayed in the center of the board each time the player scores; the %u is 
replaced by the number of points */
+        score_label.set_text (_("+%u").printf (points));
+
+        score_revealer.set_reveal_child (true);
+        if (score_revealed_handler == 0)
+            score_revealed_handler = Timeout.add (250, () => {
+                    score_revealer.set_reveal_child (false);
+                    score_revealed_handler = 0;
+                    return Source.REMOVE;
+                });
     }
 
-    internal void cursor_click ()
+    /*\
+    * * final score
+    \*/
+
+    [GtkChild] private Revealer final_score_revealer;
+    [GtkChild] private Label    final_score_label;
+
+    /* Show the final score when the game is over */
+    private inline void on_game_complete ()
+    {
+        /* Translators: text of a label that appears on the board at the end of a game; the %u is replaced 
by the score */
+        var points_label = ngettext ("%u point", "%u points", game.score).printf (game.score);
+
+        /* Translators: text of a label that appears on the board at the end of a game */
+        final_score_label.set_markup ("<b>%s</b>\n%s".printf (_("Game Over!"), points_label));
+
+        final_score_revealer.show ();
+        final_score_revealer.set_transition_duration (500);
+        final_score_revealer.set_reveal_child (true);
+    }
+
+    private inline void on_new_game ()
     {
-        group.cursor_click ();
+        final_score_revealer.set_transition_duration (0);
+        final_score_revealer.set_reveal_child (false);
+        final_score_revealer.hide ();
     }
 }
 
-private class GameGroup : Clutter.Group
+private class Board : Widget
 {
-    private TileActor? highlighted = null;
+    private TileView? highlighted = null;
 
-    private CursorActor cursor;
     private bool cursor_active = false;
     private int _cursor_x;
     private int cursor_x
@@ -97,10 +147,10 @@ private class GameGroup : Clutter.Group
     }
 
     /* A 2D array holding all tiles */
-    private TileActor? [,] tiles;
+    private TileView? [,] tiles;
 
     /* Group containing all the actors in the current game */
-    private Clutter.Actor game_actors;
+//    private Clutter.Actor game_actors;
 
     /* Game being played */
     private bool game_is_set = false;
@@ -110,10 +160,10 @@ private class GameGroup : Clutter.Group
         private get { if (!game_is_set) assert_not_reached (); return _game; }
         internal set
         {
-            if (game_is_set)
-                game_actors.destroy ();
-            game_actors = new Clutter.Actor ();
-            add_child (game_actors);
+//            if (game_is_set)
+//                game_actors.destroy ();
+//            game_actors = new Clutter.Actor ();
+//            add_child (game_actors);
 
             /* Remove old tiles */
             remove_tiles ();
@@ -123,35 +173,14 @@ private class GameGroup : Clutter.Group
             _game = value;
             game_is_set = true;
             game.undone.connect (move_undone_cb);
-            game.complete.connect (game_complete_cb);
-            game.update_score.connect (update_score_cb);
 
             /* Put tiles in new locations */
-            tiles = new TileActor? [game.columns, game.rows];
+            tiles = new TileView? [game.columns, game.rows];
             cursor_x = 0;
             cursor_y = 0;
             place_tiles ();
 
-            width  = tile_size * game.columns;
-            height = tile_size * game.rows;
-        }
-    }
-
-    /* This is a <ThemeName -- ThemeObject> container */
-    private HashTable<string, Theme> themes;
-
-    /* Theme being used */
-    private string _theme_name = "shapesandcolors";
-    internal string theme_name
-    {
-        private get { return _theme_name; }
-        internal set
-        {
-            if (theme_name == value)
-                return;
-            _theme_name = value;
-            remove_tiles ();
-            place_tiles ();
+            set_size_request (tile_size * game.columns, tile_size * game.rows);
         }
     }
 
@@ -176,8 +205,6 @@ private class GameGroup : Clutter.Group
                 tile.destroy ();
             }
         }
-
-        cursor.destroy ();
     }
 
     private void place_tiles ()
@@ -185,156 +212,167 @@ private class GameGroup : Clutter.Group
         if (!game_is_set)
             return;
 
-        var theme = themes.lookup (theme_name);
-        if (theme == null)
-            theme = themes.lookup ("shapesandcolors");
-
         for (var x = 0; x < game.columns; x++)
         {
             for (var y = 0; y < game.rows; y++)
             {
                 /* For each tile object, we create a tile actor for it */
-                var l = game.get_tile (x, y);
-                if (l == null || l.closed)
-                    continue;
-                var tile = new TileActor (l, theme.textures[l.color - 1], tile_size);
+                Tile? tile = game.get_tile (x, y);
+                TileView tile_view;
+                if (tile == null || ((!) tile).closed)
+                    tile_view = new TileView.empty (tile_size);
+                else
+                    tile_view = new TileView (tile, tile_size);
 
                 /* The event from the model will be caught and responded by the view */
-                l.move.connect (move_cb);
-                l.close.connect (close_cb);
+                if (tile != null)
+                {
+                    ((!) tile).move.connect (move_cb);
+                    ((!) tile).close.connect (close_cb);
+                }
 
                 /* Physical position in the stage */
-                float xx, yy;
-                xx = x * tile_size;
-                yy = (game.rows - y - 1) * tile_size;
-                tile.set_position (xx, yy);
+//                float xx, yy;
+//                xx = x * tile_size;
+//                yy = (game.rows - y - 1) * tile_size;
+//                tile_view.set_position (xx, yy);
 
                 /* Respond to the user interactions */
-                tile.reactive = true;
-                var tap = new Clutter.TapAction ();
-                tile.add_action (tap);
-                tap.tap.connect (remove_region_cb);
-                tile.enter_event.connect (tile_entered_cb);
-                tile.leave_event.connect (tile_left_cb);
-
-                tiles[x, y] = tile;
-                game_actors.add_child (tile);
+                if (tile_view.click_controller != null)
+                    ((!) tile_view.click_controller).pressed.connect (remove_region_cb);
+
+                tile_view.inout_controller.enter.connect (tile_entered_cb);
+                tile_view.inout_controller.leave.connect (tile_left_cb);
+
+                tiles[x, y] = tile_view;
+                tile_view.insert_before (this, /* insert last */ null);
+                GridLayoutChild child_layout = (GridLayoutChild) layout.get_layout_child (tile_view);
+                child_layout.set_top_attach (game.rows - y - 1);
+                child_layout.set_left_attach (x);
             }
         }
-
-        cursor = new CursorActor (theme.cursor, tile_size);
-        game_actors.add_child (cursor);
-        cursor.hide ();
     }
 
     internal bool is_zealous { private get; internal set; }
 
+    private GridLayout layout;
     construct
     {
-        /* Initialize the theme resources */
-        themes = new HashTable<string, Theme> (str_hash, str_equal);
-        var theme = new Theme ("colors");
-        themes.insert ("colors", theme);
+        layout = new GridLayout ();
+        layout.set_row_homogeneous (true);
+        layout.set_column_homogeneous (true);
+        set_layout_manager (layout);
 
-        theme = new Theme ("shapesandcolors");
-        themes.insert ("shapesandcolors", theme);
-
-        theme = new Theme ("boringshapes");
-        themes.insert ("boringshapes", theme);
+        add_css_class ("board");
     }
 
     /* When a tile in the model layer is closed, play an animation at the view layer */
     private inline void close_cb (uint8 grid_x, uint8 grid_y)
     {
-        unowned TileActor? tile_actor = tiles[grid_x, grid_y];
+        unowned TileView? tile_actor = tiles[grid_x, grid_y];
         if (tile_actor != null)
-            ((!) tile_actor).animate_out ();
+            ((!) tile_actor).update_opacity (Opacity.NULL);
     }
 
     /* When a tile in the model layer is moved, play an animation at the view layer */
     private inline void move_cb (uint8 old_x, uint8 old_y, uint8 new_x, uint8 new_y)
     {
-        var tile = tiles[old_x, old_y];
-        tiles[new_x, new_y] = tile;
-        var new_xx = new_x * tile_size;
-        var new_yy = (game.rows - new_y - 1) * tile_size;
+        // swap tiles in the tiles array
+        unowned TileView? tile_view_1 = tiles[old_x, old_y];
+        if (tile_view_1 == null)
+            assert_not_reached ();
+        unowned TileView? tile_view_2 = tiles[new_x, new_y];
+
+        tiles[new_x, new_y] = tile_view_1;
+        tiles[old_x, old_y] = tile_view_2;
+
+        // reorder tiles views visually
+        GridLayoutChild child_layout;
+
+        child_layout = (GridLayoutChild) layout.get_layout_child ((!) tile_view_1);
+        child_layout.set_top_attach (game.rows - new_y - 1);
+        child_layout.set_left_attach (new_x);
+
+        child_layout = (GridLayoutChild) layout.get_layout_child ((!) tile_view_2);
+        child_layout.set_top_attach (game.rows - old_y - 1);
+        child_layout.set_left_attach (old_x);
+
+//        // launch tile animation
+//        var new_xx = new_x * tile_size;
+//        var new_yy = (game.rows - new_y - 1) * tile_size;
 
-        tile.animate_to (new_xx, new_yy, is_zealous);
+//        tile.animate_to (new_xx, new_yy, is_zealous);
     }
 
-    /* Sets the opacity for all tiles connected to the actor */
-    private void opacity_for_connected_tiles (TileActor? actor, Opacity opacity)
+    /* Sets the opacity for all tiles connected to the given tile */
+    private void opacity_for_connected_tiles (TileView? given_tile, Opacity opacity)
     {
-        if (actor == null)
+        if (given_tile == null)
             return;
 
-        var connected_tiles = game.connected_tiles (actor.tile);
-        foreach (var l in connected_tiles)
+        var connected_tiles = game.connected_tiles (given_tile.tile);
+        foreach (unowned Tile tile in connected_tiles)
         {
-            TileActor? tile_actor = tiles[l.grid_x, l.grid_y];
-            if (tile_actor != null)
-                ((!) tile_actor).update_opacity (opacity);
+            TileView? tile_view = tiles[tile.grid_x, tile.grid_y];
+            if (tile_view != null)
+                ((!) tile_view).update_opacity (opacity);
         }
     }
 
     /* When the mouse enters a tile, bright up the connected tiles */
-    private bool tile_entered_cb (Clutter.Actor actor, Clutter.CrossingEvent event)
+    private void tile_entered_cb (EventControllerMotion inout_controller, double x, double y, 
Gdk.CrossingMode mode)
     {
         if (cursor_active)
-            return false;
-
-        var tile = (TileActor) actor;
+            return;
 
-        opacity_for_connected_tiles (tile, Opacity.FULL);
-        highlighted = tile;
+        TileView tile_view = (TileView) inout_controller.get_widget ();
 
-        return false;
+        opacity_for_connected_tiles (tile_view, Opacity.FULL);
+        highlighted = tile_view;
     }
 
     /* When the mouse leaves a tile, lower the brightness of the connected tiles */
-    private bool tile_left_cb (Clutter.Actor actor, Clutter.CrossingEvent event)
+    private void tile_left_cb (EventControllerMotion inout_controller, Gdk.CrossingMode mode)
     {
         if (cursor_active)
-            return false;
-
-        var tile = (TileActor) actor;
+            return;
 
-        opacity_for_connected_tiles (tile, Opacity.HALF);
+        TileView tile_view = (TileView) inout_controller.get_widget ();
 
-        return false;
+        opacity_for_connected_tiles (tile_view, Opacity.HALF);
     }
 
     /* When the user click a tile, send the model to remove the connected tile. */
-    private void remove_region_cb (Clutter.TapAction tap, Clutter.Actor actor)
+    private void remove_region_cb (GestureClick click_controller, int n_press, double x, double y)
     {
-        var tile = (TileActor) actor;
+        TileView tile_view = (TileView) click_controller.get_widget ();
 
         opacity_for_connected_tiles (highlighted, Opacity.HALF);
 
         if (cursor_active)
         {
             cursor_active = false;
-            cursor.hide ();
+//            cursor.hide ();
         }
 
         /* Move the cursor to where the mouse was clicked. Expected for mixed mouse/keyboard use */
-        cursor_x = tile.tile.grid_x;
-        cursor_y = tile.tile.grid_y;
+        cursor_x = tile_view.tile.grid_x;
+        cursor_y = tile_view.tile.grid_y;
 
-        game.remove_connected_tiles (tile.tile);
+        game.remove_connected_tiles (tile_view.tile);
     }
 
     /* When the mouse leaves the application window, reset all tiles to the default brightness */
     internal void board_left_cb ()
     {
-        foreach (TileActor? tile_actor in tiles)
+        foreach (TileView? tile_actor in tiles)
             if (tile_actor != null)
                 ((!) tile_actor).update_opacity (Opacity.HALF);
     }
 
-    private TileActor? find_tile_at_position (int position_x, int position_y)
+    private TileView? find_tile_at_position (int position_x, int position_y)
     {
-        foreach (TileActor? tile_actor in tiles)
+        foreach (TileView? tile_actor in tiles)
             if (tile_actor != null
              && ((!) tile_actor).tile.grid_x == position_x
              && ((!) tile_actor).tile.grid_y == position_y)
@@ -362,13 +400,13 @@ private class GameGroup : Clutter.Group
             cursor_active = true;
 
         // highlight and unhighlight
-        TileActor? cursor_tile = find_tile_at_position (cursor_x, cursor_y);
+        TileView? cursor_tile = find_tile_at_position (cursor_x, cursor_y);
 
         if ((highlighted != null && cursor_tile == null)
          || (highlighted == null && cursor_tile != null)
          || (highlighted != null && cursor_tile != null && ((!) highlighted).tile.color != ((!) 
cursor_tile).tile.color))
         {
-            // opacity_for_connected_tiles() handles correctly a null TileActor
+            // opacity_for_connected_tiles() handles correctly a null TileView
             opacity_for_connected_tiles (highlighted, Opacity.HALF);
             opacity_for_connected_tiles (cursor_tile, Opacity.FULL);
         }
@@ -379,8 +417,8 @@ private class GameGroup : Clutter.Group
         float xx, yy;
         xx = cursor_x * tile_size;
         yy = (game.rows - 1 - cursor_y) * tile_size;
-        cursor.set_position (xx, yy);
-        cursor.show ();
+//        cursor.set_position (xx, yy);
+//        cursor.show ();
     }
 
     /* Keyboard Cursor Click */
@@ -391,93 +429,12 @@ private class GameGroup : Clutter.Group
         opacity_for_connected_tiles (highlighted, Opacity.FULL);
     }
 
-    /* Show flying score animation after each tile-removing click */
-    private void update_score_cb (uint points_awarded)
-    {
-        if (is_zealous)
-        {
-            var text = new ScoreActor (game.rows / 5, width, height);
-            game_actors.add_child (text);
-            text.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.BOTH, 0.5f));
-            text.animate_score (points_awarded);
-        }
-    }
-
-    /* Show the final score when the game is over */
-    private void game_complete_cb ()
-    {
-        var text = new ScoreActor (game.rows / 5, width, height);
-        game_actors.add_child (text);
-        text.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.BOTH, 0.5f));
-        text.animate_final_score (game.score);
-
-        /* Translators: text of a button that appears on the board at the end of a game */
-        var play_again_button = new Gtk.Button.with_mnemonic (_("_Play Again"));
-        play_again_button.width_request = 130;
-        play_again_button.height_request = 40;
-        play_again_button.action_name = "win.new-game";
-        play_again_button.show ();
-
-        var style = play_again_button.get_style_context ();
-        style.add_class ("suggested-action");
-
-        var button_actor = new GtkClutter.Actor.with_contents (play_again_button);
-        game_actors.add_child (button_actor);
-        button_actor.visible = true;
-        button_actor.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.X_AXIS, 0.5f));
-        button_actor.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.Y_AXIS, 0.88f));
-
-        button_actor.set_easing_mode (Clutter.AnimationMode.EASE_OUT_ELASTIC);
-        button_actor.set_easing_duration (2000);
-        button_actor.z_position = -50;
-        button_actor.set_opacity (255);
-    }
-
     private inline void move_undone_cb ()
     {
         game = game;
     }
 }
 
-/**
- *  This class holds the textures for a specific theme. These textures are used for creating light
- *  actors and cursor actor.
- */
-private class Theme : Object
-{
-    internal Clutter.Image[] textures;
-    internal Clutter.Image cursor;
-
-    internal Theme (string name)
-    {
-        textures = new Clutter.Image [4];
-        string[] colors = {"blue", "green", "yellow", "red"};
-
-        /* Create the textures required to render */
-        try
-        {
-            for (uint8 i = 0; i < 4; i++) {
-                var pixbuf = new Gdk.Pixbuf.from_file (Path.build_filename (Config.DATADIR, "themes", name, 
colors[i] + ".svg"));
-                textures[i] = new Clutter.Image ();
-                textures[i].set_data (pixbuf.get_pixels (), Cogl.PixelFormat.RGBA_8888,
-                                      pixbuf.get_width (), pixbuf.get_height (),  pixbuf.get_rowstride ());
-            }
-            var pixbuf = new Gdk.Pixbuf.from_file (Path.build_filename (Config.DATADIR, "themes", name, 
"highlight.svg"));
-            cursor = new Clutter.Image ();
-            cursor.set_data (pixbuf.get_pixels (), Cogl.PixelFormat.RGBA_8888,
-                             pixbuf.get_width (), pixbuf.get_height (),  pixbuf.get_rowstride ());
-        }
-        catch (Clutter.TextureError e)
-        {
-            warning ("Failed to load textures: %s", e.message);
-        }
-        catch (GLib.Error e)
-        {
-            warning ("Failed to load textures: %s", e.message);
-        }
-    }
-}
-
 private enum Opacity
 {
     NULL,
@@ -488,136 +445,115 @@ private enum Opacity
 /**
  *  This class defines the view of a tile. All clutter related stuff goes here
  */
-private class TileActor : Clutter.Actor
+private class TileView : Widget
 {
     /* Tile being represented */
-    internal Tile tile;
-
-    internal TileActor (Tile tile, Clutter.Image texture, int size)
-    {
-        this.tile = tile;
-        update_opacity (Opacity.HALF);
-        set_size (size, size);
-        content = texture;
-
-        set_content_gravity (Clutter.ContentGravity.CENTER);
-        set_pivot_point (0.5f, 0.5f);
-    }
-
-    internal void update_opacity (Opacity opacity)
-    {
-        set_easing_duration (200);
-        set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
-        switch (opacity)
-        {
-            case Opacity.NULL:  set_opacity (  0);  break;
-            case Opacity.HALF:  set_opacity (180);  break;
-            case Opacity.FULL:  set_opacity (255);  break;
-            default: assert_not_reached ();
-        }
-    }
+    public Tile? tile   { internal get; protected construct; default = null; }
+    public uint size    { internal get; protected construct; }
 
-    /* Destroy the tile */
-    internal void animate_out ()
-    {
-        /* When the animination is done, hide the actor */
-        set_easing_mode (Clutter.AnimationMode.LINEAR);
-        set_easing_duration (500);
-        set_scale (2.0, 2.0);
-        update_opacity (Opacity.NULL);
-        transitions_completed.connect (hide_tile_cb);
-    }
+    public EventControllerMotion inout_controller { internal get; protected construct; }
+    public GestureClick?         click_controller { internal get; protected construct; default = null; }
 
-    private void hide_tile_cb ()
-    {
-        hide ();
-    }
+    private bool tile_destroyed = false;
 
-    /* Define how the tile moves */
-    internal void animate_to (double new_x, double new_y, bool is_zealous = false)
+    internal TileView (Tile tile, uint size)
     {
-        var anim_mode = is_zealous ? Clutter.AnimationMode.EASE_OUT_BOUNCE : 
Clutter.AnimationMode.EASE_OUT_QUAD;
-        set_easing_mode (anim_mode);
-        set_easing_duration (500);
-        set_position ((float) new_x, (float) new_y);
+        EventControllerMotion _inout_controller = new EventControllerMotion ();
+        GestureClick _click_controller = new GestureClick ();
+        Object (tile: tile,
+                size: size,
+                inout_controller: _inout_controller,
+                click_controller: _click_controller);
     }
-}
 
-private class CursorActor : Clutter.Actor
-{
-    internal CursorActor (Clutter.Content texture, int size)
+    internal TileView.empty (uint size)
     {
-        set_opacity (180);
-        set_size (size, size);
-        content = texture;
-
-        set_content_gravity (Clutter.ContentGravity.CENTER);
-        set_pivot_point (0.5f, 0.5f);
+        EventControllerMotion _inout_controller = new EventControllerMotion ();
+        Object (size: size,
+                inout_controller: _inout_controller);
     }
-}
-
-/**
- *  This class defines the view of a score. All clutter related stuff goes here
- */
-private class ScoreActor : Clutter.Group
-{
-    private Clutter.Text label;
-    private float scene_width;
-    private float scene_height;
-    private int game_size;
 
-    internal ScoreActor (int game_size, double width, double height)
+    construct
     {
-        label = new Clutter.Text ();
-        label.set_color (Clutter.Color.from_string ("rgba(255, 255, 255, 255)"));
+        set_size_request ((int) size, (int) size);
+        add_css_class ("tile");
 
-        add_child (label);
+        if (tile == null)
+            add_css_class ("removed");
+        else
+            switch (tile.color)
+            {
+                case 4: add_css_class ("red");      break;
+                case 1: add_css_class ("blue");     break;
+                case 2: add_css_class ("green");    break;
+                case 3: add_css_class ("yellow");   break;
+                case 0: add_css_class ("removed");  break;
+                default: assert_not_reached ();
+            }
 
-        set_pivot_point (0.5f, 0.5f);
+//        add_css_class ("half-opacity");
+        if (tile == null || ((!) tile).color == 0)
+            update_opacity (Opacity.NULL);
+        else
+            update_opacity (Opacity.HALF);
 
-        this.scene_width = (float) width;
-        this.scene_height = (float) height;
-        this.game_size = game_size;
+        add_controller (inout_controller);
+        if (click_controller != null)
+            add_controller ((!) click_controller);
     }
 
-    internal void animate_score (uint points)
+    internal void update_opacity (Opacity opacity)
     {
-        if (points == 0)
+        if (tile_destroyed)
             return;
 
-        label.set_font_name ("Bitstrem Vera Sans Bold 30");
-        label.set_text ("+" + points.to_string ());
-
-        /* The score will be shown repeatedly therefore we need to reset some important properties
-         * before the actual animation */
-        set_opacity (255);
-        z_position = 0f;
-
-        set_easing_mode (Clutter.AnimationMode.EASE_OUT_SINE);
-        set_easing_duration (600);
-        z_position = 500f;
-        set_opacity (0);
-        transitions_completed.connect (() => { destroy (); });
+//        string [] css_classes = get_css_classes ();
+//        uint i;
+//        for (i = 0; i < css_classes.length; i++)
+//            if (css_classes [i] == "null-opacity"
+//             || css_classes [i] == "half-opacity"
+//             || css_classes [i] == "full-opacity")
+//                break;
+
+//        switch (opacity)
+//        {
+//            case Opacity.NULL: if (css_classes [i] == "null-opacity") return; css_classes [i] = 
"null-opacity"; break;
+//            case Opacity.HALF: if (css_classes [i] == "half-opacity") return; css_classes [i] = 
"half-opacity"; break;
+//            case Opacity.FULL: if (css_classes [i] == "full-opacity") return; css_classes [i] = 
"full-opacity"; break;
+//        }
+//        set_css_classes (css_classes);    // FIXME https://gitlab.gnome.org/GNOME/vala/issues/994
+        switch (opacity)
+        {
+            case Opacity.NULL: set_opacity (0.0);
+                               tile_destroyed = true;
+                               can_target = false;
+                               if (click_controller != null)
+                                   remove_controller ((!) click_controller);
+                               break;
+            case Opacity.HALF: set_opacity (0.7); break;
+            case Opacity.FULL: set_opacity (1.0); break;
+        }
     }
 
-    internal void animate_final_score (uint points)
-    {
-        label.set_font_name ("Bitstrem Vera Sans 30");
-        var points_label = ngettext (/* Label showing the number of points at the end of the game */
-                                     "%u point", "%u points", points).printf (points);
+    /* Destroy the tile */
+//    internal void animate_out ()
+//    {
+        /* When the animination is done, hide the actor */
+//        update_opacity (Opacity.NULL);
+//        transitions_completed.connect (hide_tile_cb);
+//    }
 
-        /* Translators: text of a label that appears on the board at the end of a game */
-        label.set_markup ("<b>%s</b>\n%s".printf (_("Game Over!"), points_label));
-        label.set_line_alignment (Pango.Alignment.CENTER);
-
-        /* The score will be shown repeatedly therefore we need to reset some important properties
-         * before the actual animation */
-        set_opacity (255);
-        z_position = -300f + game_size * 100;
-
-        set_easing_mode (Clutter.AnimationMode.EASE_OUT_ELASTIC);
-        set_easing_duration (2000);
-        z_position = -200 + game_size * 150;
-        set_opacity (255);
-    }
+//    private void hide_tile_cb ()
+//    {
+//        hide ();
+//    }
+
+    /* Define how the tile moves */
+//    internal void animate_to (double new_x, double new_y, bool is_zealous = false)
+//    {
+//        var anim_mode = is_zealous ? Clutter.AnimationMode.EASE_OUT_BOUNCE : 
Clutter.AnimationMode.EASE_OUT_QUAD;
+//        set_easing_mode (anim_mode);
+//        set_easing_duration (500);
+//        set_position ((float) new_x, (float) new_y);
+//    }
 }
diff --git a/src/org.gnome.SwellFoop.gresource.xml b/src/org.gnome.SwellFoop.gresource.xml
index 32dda5e..a9159c8 100644
--- a/src/org.gnome.SwellFoop.gresource.xml
+++ b/src/org.gnome.SwellFoop.gresource.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/org/gnome/SwellFoop/ui">
     <file preprocess="xml-stripblanks" alias="first-run-stack.ui">../data/ui/first-run-stack.ui</file>
+    <file preprocess="xml-stripblanks" alias="game-view.ui">../data/ui/game-view.ui</file>
     <file alias="swell-foop.css">../data/swell-foop.css</file>
     <file preprocess="xml-stripblanks" alias="swell-foop.ui">../data/ui/swell-foop.ui</file>
   </gresource>
diff --git a/src/swell-foop.vala b/src/swell-foop.vala
index 9e553d6..62d99af 100644
--- a/src/swell-foop.vala
+++ b/src/swell-foop.vala
@@ -36,10 +36,26 @@ public class SwellFoop : Gtk.Application
         { "quit",          quit_cb        }
     };
 
+    public static int main (string[] args)
+    {
+        Intl.setlocale (LocaleCategory.ALL, "");
+        Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+        Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+        Intl.textdomain (Config.GETTEXT_PACKAGE);
+
+        Environment.set_application_name (PROGRAM_NAME);
+        Gtk.Window.set_default_icon_name ("org.gnome.SwellFoop");
+
+        var app = new SwellFoop ();
+        return app.run (args);
+    }
+
     /* Constructor */
     public SwellFoop ()
     {
         Object (application_id: "org.gnome.SwellFoop", flags: ApplicationFlags.FLAGS_NONE);
+
+        add_main_option_entries (option_entries);
     }
 
     protected override void startup ()
@@ -115,49 +131,4 @@ public class SwellFoop : Gtk.Application
                                "logo-icon-name", "org.gnome.SwellFoop",
                                "website", Config.PACKAGE_URL);
     }
-
-    public static int main (string[] args)
-    {
-        Intl.setlocale (LocaleCategory.ALL, "");
-        Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
-        Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
-        Intl.textdomain (Config.GETTEXT_PACKAGE);
-
-        if (GtkClutter.init (ref args) != Clutter.InitError.SUCCESS)
-        {
-            warning ("Failed to initialise Clutter");
-            return Posix.EXIT_FAILURE;
-        }
-
-        var context = new OptionContext (null);
-        context.set_translation_domain (Config.GETTEXT_PACKAGE);
-
-        context.add_group (Gtk.get_option_group (true));
-        context.add_group (Clutter.get_option_group_without_init ());
-
-        context.add_main_entries (option_entries, Config.GETTEXT_PACKAGE);
-
-        try
-        {
-            context.parse (ref args);
-        }
-        catch (Error e)
-        {
-            stderr.printf ("%s\n", e.message);
-            return Posix.EXIT_FAILURE;
-        }
-
-        if (version)
-        {
-            /* NOTE: Is not translated so can be easily parsed */
-            stderr.printf ("%1$s %2$s\n", "swell-foop", Config.VERSION);
-            return Posix.EXIT_SUCCESS;
-        }
-
-        Environment.set_application_name (PROGRAM_NAME);
-        Gtk.Window.set_default_icon_name ("org.gnome.SwellFoop");
-
-        var app = new SwellFoop ();
-        return app.run (args);
-    }
 }
diff --git a/src/window.vala b/src/window.vala
index 998d661..f5b18d8 100644
--- a/src/window.vala
+++ b/src/window.vala
@@ -79,6 +79,12 @@ private class SwellFoopWindow : ApplicationWindow
 
     construct
     {
+        CssProvider css_provider = new CssProvider ();
+        css_provider.load_from_resource ("/org/gnome/SwellFoop/ui/swell-foop.css");
+        Gdk.Display? gdk_display = Gdk.Display.get_default ();
+        if (gdk_display != null) // else..?
+            StyleContext.add_provider_for_display ((!) gdk_display, css_provider, 
STYLE_PROVIDER_PRIORITY_APPLICATION);
+
         settings = new GLib.Settings ("org.gnome.SwellFoop");
 
         add_action_entries (win_actions, this);
@@ -87,11 +93,8 @@ private class SwellFoopWindow : ApplicationWindow
         add_action (settings.create_action ("zealous"));
         settings.changed ["zealous"].connect ((_settings, _key_name) => { view.set_is_zealous 
(_settings.get_boolean (_key_name)); });
 
-        string theme = settings.get_string ("theme");
-        if (theme != "colors" && theme != "shapesandcolors" && theme != "boringshapes")
-            theme = "shapesandcolors";
-        SimpleAction theme_action = (SimpleAction) lookup_action ("change-theme");
-        theme_action.set_state (new Variant.@string (theme));
+        settings.changed ["theme"].connect (load_theme);
+        load_theme (settings, "theme");
 
         int32 colors = settings.get_int ("colors"); // 2 <= colors <= 4, per schema file
         SimpleAction colors_action = (SimpleAction) lookup_action ("change-colors");
@@ -131,12 +134,6 @@ private class SwellFoopWindow : ApplicationWindow
 
     private inline Stack build_first_run_stack ()
     {
-        CssProvider css_provider = new CssProvider ();
-        css_provider.load_from_resource ("/org/gnome/SwellFoop/ui/swell-foop.css");
-        Gdk.Display? gdk_display = Gdk.Display.get_default ();
-        if (gdk_display != null) // else..?
-            StyleContext.add_provider_for_display ((!) gdk_display, css_provider, 
STYLE_PROVIDER_PRIORITY_APPLICATION);
-
         Builder builder = new Builder.from_resource ("/org/gnome/SwellFoop/ui/first-run-stack.ui");
         var stack = (Stack) builder.get_object ("first_run_stack");
         var tip_label = (Label) builder.get_object ("tip_label");
@@ -220,7 +217,6 @@ private class SwellFoopWindow : ApplicationWindow
         game.started.connect (started_cb);
 
         /* Initialize the themes needed by actors */
-        view.set_theme_name (settings.get_string ("theme"));
         view.set_is_zealous (settings.get_boolean ("zealous"));
         view.set_game ((!) game);
 
@@ -257,7 +253,6 @@ private class SwellFoopWindow : ApplicationWindow
     {
         string new_theme = ((!) variant).get_string ();
         action.set_state ((!) variant);
-        view.set_theme_name (new_theme);
         if (settings.get_string ("theme") != new_theme)
             settings.set_string ("theme", new_theme);
     }
@@ -511,4 +506,40 @@ private class SwellFoopWindow : ApplicationWindow
         motion_controller.leave.connect (view.board_left_cb);
         view.add_controller (motion_controller);
     }
+
+    /*\
+    * * theme
+    \*/
+
+    private bool icon_theme_added = false;
+
+    private void load_theme (GLib.Settings _settings, string _key_name)
+    {
+        string theme = _settings.get_string (_key_name);
+        if (theme != "colors" && theme != "shapesandcolors" && theme != "boringshapes")
+            theme = "shapesandcolors";
+        set_game_theme (theme);
+        SimpleAction theme_action = (SimpleAction) lookup_action ("change-theme");
+        theme_action.set_state (new Variant.@string (theme));
+    }
+
+    private inline void set_game_theme (string theme_name)
+    {
+        string theme_path = Path.build_filename (Config.DATADIR, "themes", theme_name);
+
+        if (!icon_theme_added)
+        {
+            IconTheme.get_for_display (Gdk.Display.get_default ()).add_search_path (theme_path);
+            icon_theme_added = true;
+        }
+        else
+        {
+            IconTheme icon_theme = IconTheme.get_for_display (Gdk.Display.get_default ());
+            string[] icon_search_path = icon_theme.get_search_path ();
+            icon_search_path[icon_search_path.length - 1] = theme_path;
+            icon_theme.set_search_path (icon_search_path);
+        }
+
+        queue_draw ();
+    }
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]