[gnome-2048] Introduce GameHeaderBar.



commit 0b2eb15555570a99ef2cb4cd52948bcbfd84fe59
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Thu Feb 7 18:59:28 2019 +0100

    Introduce GameHeaderBar.

 data/game-headerbar.ui                       |  70 +++++++++++
 data/mainwindow.ui                           |  49 +-------
 po/POTFILES.in                               |   2 +
 po/POTFILES.skip                             |   1 +
 src/game-headerbar.vala                      | 181 +++++++++++++++++++++++++++
 src/game-window.vala                         | 174 ++++++-------------------
 src/meson.build                              |   1 +
 src/org.gnome.TwentyFortyEight.gresource.xml |   1 +
 8 files changed, 292 insertions(+), 187 deletions(-)
---
diff --git a/data/game-headerbar.ui b/data/game-headerbar.ui
new file mode 100644
index 0000000..e01573a
--- /dev/null
+++ b/data/game-headerbar.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  This file is part of GNOME 2048
+
+  GNOME 2048 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 3 of the License, or
+  (at your option) any later version.
+
+  GNOME 2048 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 GNOME 2048.  If not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <template class="GameHeaderBar" parent="GtkHeaderBar">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <!-- Translators: title of the window, displayed in the headerbar -->
+    <property name="title" translatable="yes">GNOME 2048</property>
+    <property name="show-close-button">True</property>
+    <child>
+      <object class="GtkMenuButton" id="_new_game_button">
+        <!-- Translators: button in the headerbar (with a mnemonic that appears pressing Alt) -->
+        <property name="label" translatable="yes">_New Game</property>
+        <property name="visible">True</property>
+        <property name="valign">center</property>
+        <property name="can-focus">True</property>
+        <property name="receives-default">True</property>
+        <property name="use-underline">True</property>
+        <property name="focus-on-click">False</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkMenuButton" id="_hamburger_button">
+        <property name="visible">True</property>
+        <property name="halign">end</property>
+        <property name="valign">center</property>
+        <property name="focus-on-click">False</property>
+        <style>
+          <class name="image-button"/>
+        </style>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon-name">open-menu-symbolic</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="pack-type">end</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="_score">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="label">0</property>
+      </object>
+      <packing>
+        <property name="pack-type">end</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/data/mainwindow.ui b/data/mainwindow.ui
index 6fe7fd4..18d725f 100644
--- a/data/mainwindow.ui
+++ b/data/mainwindow.ui
@@ -28,55 +28,8 @@
     <signal name="size-allocate"        handler="size_allocate_cb"/>
     <signal name="window-state-event"   handler="state_event_cb"/>
     <child type="titlebar">
-      <object class="GtkHeaderBar" id="_header_bar">
+      <object class="GameHeaderBar" id="_header_bar">
         <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <!-- Translators: title of the window, displayed in the headerbar -->
-        <property name="title" translatable="yes">GNOME 2048</property>
-        <property name="show-close-button">True</property>
-        <child>
-          <object class="GtkMenuButton" id="_new_game_button">
-            <!-- Translators: button in the headerbar (with a mnemonic that appears pressing Alt) -->
-            <property name="label" translatable="yes">_New Game</property>
-            <property name="visible">True</property>
-            <property name="valign">center</property>
-            <property name="can-focus">True</property>
-            <property name="receives-default">True</property>
-            <property name="use-underline">True</property>
-            <property name="focus-on-click">False</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkMenuButton" id="_hamburger_button">
-            <property name="visible">True</property>
-            <property name="halign">end</property>
-            <property name="valign">center</property>
-            <property name="focus-on-click">False</property>
-            <style>
-              <class name="image-button"/>
-            </style>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">open-menu-symbolic</property>
-                <property name="icon-size">1</property>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkLabel" id="_score">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="label">0</property>
-          </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
-        </child>
       </object>
     </child>
     <child>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b4aa7de..422667c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,9 +1,11 @@
 # List of source files containing translatable strings.
 # Please keep this file in alphabetical order.
 data/congrats.ui
+data/game-headerbar.ui
 data/help-overlay.ui
 data/mainwindow.ui
 data/org.gnome.TwentyFortyEight.appdata.xml.in
 data/org.gnome.TwentyFortyEight.desktop.in
 data/org.gnome.TwentyFortyEight.gschema.xml
+src/game-headerbar.vala
 src/game-window.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index a2a2067..67dc020 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1 +1,2 @@
+src/game-headerbar.c
 src/game-window.c
diff --git a/src/game-headerbar.vala b/src/game-headerbar.vala
new file mode 100644
index 0000000..bc076e5
--- /dev/null
+++ b/src/game-headerbar.vala
@@ -0,0 +1,181 @@
+/* Copyright (C) 2014-2015 Juan R. García Blanco <juanrgar gmail com>
+ * Copyright (C) 2016-2019 Arnaud Bonatti <arnaud bonatti gmail com>
+ *
+ * This file is part of GNOME 2048.
+ *
+ * GNOME 2048 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GNOME 2048 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 GNOME 2048; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/TwentyFortyEight/ui/game-headerbar.ui")]
+private class GameHeaderBar : HeaderBar
+{
+    [GtkChild] private Label        _score;
+    [GtkChild] private MenuButton   _new_game_button;
+    [GtkChild] private MenuButton   _hamburger_button;
+
+    /*\
+    * * popovers
+    \*/
+
+    internal signal void popover_closed ();
+
+    construct
+    {
+        _hamburger_button.notify ["active"].connect (test_popover_closed);
+        _new_game_button.notify ["active"].connect (test_popover_closed);
+    }
+
+    private void test_popover_closed ()
+    {
+        if (!has_popover ())
+            popover_closed ();
+    }
+
+    internal bool has_popover ()
+    {
+        return _hamburger_button.active || _new_game_button.active;
+    }
+
+    /*\
+    * * texts
+    \*/
+
+    internal void clear_subtitle ()
+    {
+        set_subtitle (null);
+        set_has_subtitle (false);
+    }
+
+    internal void finished ()
+    {
+        set_has_subtitle (true);
+        /* Translators: subtitle of the headerbar, when the user cannot move anymore */
+        subtitle = _("Game Over");
+    }
+
+    internal void set_score (Object game, ParamSpec unused)
+    {
+        _score.label = ((Game) game).score.to_string ();
+    }
+
+    /*\
+    * * hamburger menu
+    \*/
+
+    internal void _update_hamburger_menu (bool allow_undo)
+    {
+        GLib.Menu menu = new GLib.Menu ();
+
+        if (allow_undo)
+            _append_undo_section (ref menu);
+        _append_scores_section (ref menu);
+        _append_app_actions_section (ref menu);
+
+        menu.freeze ();
+        _hamburger_button.set_menu_model ((MenuModel) menu);
+    }
+
+    private static inline void _append_undo_section (ref GLib.Menu menu)
+    {
+        GLib.Menu section = new GLib.Menu ();
+
+        /* Translators: entry in the hamburger menu, if the "Allow undo" option is set to true */
+        section.append (_("Undo"), "ui.undo");
+
+        section.freeze ();
+        menu.append_section (null, section);
+    }
+
+    private static inline void _append_scores_section (ref GLib.Menu menu)
+    {
+        GLib.Menu section = new GLib.Menu ();
+
+        /* Translators: entry in the hamburger menu; opens a window showing best scores */
+        section.append (_("Scores"), "ui.scores");
+
+        section.freeze ();
+        menu.append_section (null, section);
+    }
+
+    private static inline void _append_app_actions_section (ref GLib.Menu menu)
+    {
+        GLib.Menu section = new GLib.Menu ();
+
+        /* Translators: usual menu entry of the hamburger menu */
+        section.append (_("Keyboard Shortcuts"), "win.show-help-overlay");
+
+        /* Translators: entry in the hamburger menu */
+        section.append (_("About 2048"), "ui.about");
+
+        section.freeze ();
+        menu.append_section (null, section);
+    }
+
+    internal void toggle_hamburger_menu ()
+    {
+        _hamburger_button.active = !_hamburger_button.active;
+    }
+
+    /*\
+    * * new-game menu
+    \*/
+
+    internal void _update_new_game_menu (int rows, int cols)
+    {
+        GLib.Menu menu = new GLib.Menu ();
+
+        /* Translators: on main window, entry of the menu when clicking on the "New Game" button; to change 
grid size to 3 × 3 */
+        _append_new_game_item (_("3 × 3"),
+                    /* rows */ 3,
+                    /* cols */ 3,
+                           ref menu);
+
+        /* Translators: on main window, entry of the menu when clicking on the "New Game" button; to change 
grid size to 4 × 4 */
+        _append_new_game_item (_("4 × 4"),
+                    /* rows */ 4,
+                    /* cols */ 4,
+                           ref menu);
+
+        /* Translators: on main window, entry of the menu when clicking on the "New Game" button; to change 
grid size to 5 × 5 */
+        _append_new_game_item (_("5 × 5"),
+                    /* rows */ 5,
+                    /* cols */ 5,
+                           ref menu);
+
+        bool is_square = rows == cols;
+        bool disallowed_grid = GameWindow.is_disallowed_grid_size (ref rows, ref cols);
+        if (disallowed_grid && !is_square)
+            /* Translators: command-line warning displayed if the user manually sets a invalid grid size */
+            warning (_("Grids of size 1 by 2 are disallowed."));
+
+        if (!disallowed_grid && (!is_square || (is_square && rows != 4 && rows != 3 && rows != 5)))
+            /* Translators: on main window, entry of the menu when clicking on the "New Game" button; 
appears only if the user has set rows and cols manually */
+            _append_new_game_item (_("Custom"), /* rows */ rows, /* cols */ cols, ref menu);
+
+        menu.freeze ();
+        _new_game_button.set_menu_model ((MenuModel) menu);
+    }
+    private static void _append_new_game_item (string label, int rows, int cols, ref GLib.Menu menu)
+    {
+        Variant variant = new Variant ("(ii)", rows, cols);
+        menu.append (label, "ui.new-game-sized(" + variant.print (/* annotate types */ true) + ")");
+    }
+
+    internal void toggle_new_game ()
+    {
+        _new_game_button.active = !_new_game_button.active;
+    }
+}
diff --git a/src/game-window.vala b/src/game-window.vala
index b0eb751..3621aa2 100644
--- a/src/game-window.vala
+++ b/src/game-window.vala
@@ -33,10 +33,7 @@ private class GameWindow : ApplicationWindow
     private bool _window_maximized;
     private bool _window_is_tiled;
 
-    [GtkChild] private HeaderBar        _header_bar;
-    [GtkChild] private Label            _score;
-    [GtkChild] private MenuButton       _new_game_button;
-    [GtkChild] private MenuButton       _hamburger_button;
+    [GtkChild] private GameHeaderBar    _header_bar;
     [GtkChild] private GtkClutter.Embed _embed;
 
     private Game _game;
@@ -95,6 +92,20 @@ private class GameWindow : ApplicationWindow
         { "about",              about_cb                    }
     };
 
+    /*\
+    * * menus
+    \*/
+
+    private void toggle_new_game_cb (/* SimpleAction action, Variant? variant */)
+    {
+        _header_bar.toggle_new_game ();
+    }
+
+    private void toggle_hamburger_menu (/* SimpleAction action, Variant? variant */)
+    {
+        _header_bar.toggle_hamburger_menu ();
+    }
+
     /*\
     * * game
     \*/
@@ -102,13 +113,9 @@ private class GameWindow : ApplicationWindow
     private void _init_game ()
     {
         _game = new Game (ref _settings);
-        _game.notify ["score"].connect ((s, p) => {
-                _score.label = _game.score.to_string ();
-            });
+        _game.notify ["score"].connect (_header_bar.set_score);
         _game.finished.connect ((s) => {
-                _header_bar.set_has_subtitle (true);
-                /* Translators: subtitle of the headerbar, when the user cannot move anymore */
-                _header_bar.subtitle = _("Game Over");
+                _header_bar.finished ();
 
                 if (!_game_restored)
                     _show_best_scores ();
@@ -130,19 +137,16 @@ private class GameWindow : ApplicationWindow
         if (_settings.get_boolean ("window-maximized"))
             maximize ();
 
-        _hamburger_button.notify ["active"].connect (() => {
-                if (!_hamburger_button.active)
-                    _embed.grab_focus ();
-            });
+        _header_bar.popover_closed.connect (() => _embed.grab_focus ());
         _settings.changed.connect ((settings, key_name) => {
                 switch (key_name)
                 {
                     case "cols":
                     case "rows":
-                        _update_new_game_menu ();
+                        _header_bar._update_new_game_menu (_settings.get_int ("rows"), _settings.get_int 
("cols"));
                         return;
                     case "allow-undo":
-                        _update_hamburger_menu ();
+                        _header_bar._update_hamburger_menu (_settings.get_boolean ("allow-undo"));
                         _game.load_settings (ref _settings);
                         return;
                     case "allow-undo-max":
@@ -151,8 +155,8 @@ private class GameWindow : ApplicationWindow
                         return;
                 }
             });
-        _update_new_game_menu ();
-        _update_hamburger_menu ();
+        _header_bar._update_new_game_menu (_settings.get_int ("rows"), _settings.get_int ("cols"));
+        _header_bar._update_hamburger_menu (_settings.get_boolean ("allow-undo"));
         _game.load_settings (ref _settings);
 
         _game.view = _embed.get_stage ();
@@ -166,78 +170,22 @@ private class GameWindow : ApplicationWindow
     }
 
     /*\
-    * * hamburger menu (and undo action) callbacks
+    * * undo action
     \*/
 
-    private void _update_hamburger_menu ()
-    {
-        GLib.Menu menu = new GLib.Menu ();
-
-        if (_settings.get_boolean ("allow-undo"))
-            _append_undo_section (ref menu);
-        _append_scores_section (ref menu);
-        _append_app_actions_section (ref menu);
-
-        menu.freeze ();
-        _hamburger_button.set_menu_model ((MenuModel) menu);
-    }
-
-    private static inline void _append_undo_section (ref GLib.Menu menu)
-    {
-        GLib.Menu section = new GLib.Menu ();
-
-        /* Translators: entry in the hamburger menu, if the "Allow undo" option is set to true */
-        section.append (_("Undo"), "ui.undo");
-
-        section.freeze ();
-        menu.append_section (null, section);
-    }
-
-    private static inline void _append_scores_section (ref GLib.Menu menu)
-    {
-        GLib.Menu section = new GLib.Menu ();
-
-        /* Translators: entry in the hamburger menu; opens a window showing best scores */
-        section.append (_("Scores"), "ui.scores");
-
-        section.freeze ();
-        menu.append_section (null, section);
-    }
-
-    private static inline void _append_app_actions_section (ref GLib.Menu menu)
-    {
-        GLib.Menu section = new GLib.Menu ();
-
-        /* Translators: usual menu entry of the hamburger menu */
-        section.append (_("Keyboard Shortcuts"), "win.show-help-overlay");
-
-        /* Translators: entry in the hamburger menu */
-        section.append (_("About 2048"), "ui.about");
-
-        section.freeze ();
-        menu.append_section (null, section);
-    }
-
-    private void toggle_hamburger_menu (/* SimpleAction action, Variant? variant */)
-    {
-        _hamburger_button.active = !_hamburger_button.active;
-    }
-
     private void undo_cb (/* SimpleAction action, Variant? variant */)
     {
         if (!_settings.get_boolean ("allow-undo"))   // for the keyboard shortcut
             return;
 
-        _header_bar.set_subtitle (null);
-        _header_bar.set_has_subtitle (false);
+        _header_bar.clear_subtitle ();
 
         _game.undo ();
     }
 
     private void new_game_cb (/* SimpleAction action, Variant? variant */)
     {
-        _header_bar.set_subtitle (null);
-        _header_bar.set_has_subtitle (false);
+        _header_bar.clear_subtitle ();
         _game_restored = false;
 
         _game.new_game (ref _settings);
@@ -245,11 +193,6 @@ private class GameWindow : ApplicationWindow
         _embed.grab_focus ();
     }
 
-    private void toggle_new_game_cb (/* SimpleAction action, Variant? variant */)
-    {
-        _new_game_button.active = !_new_game_button.active;
-    }
-
     private void new_game_sized_cb (SimpleAction action, Variant? variant)
         requires (variant != null)
     {
@@ -288,62 +231,6 @@ private class GameWindow : ApplicationWindow
                            null);
     }
 
-    /*\
-    * * new-game menu
-    \*/
-
-    private void _update_new_game_menu ()
-    {
-        GLib.Menu menu = new GLib.Menu ();
-
-        /* Translators: on main window, entry of the menu when clicking on the "New Game" button; to change 
grid size to 3 × 3 */
-        _append_new_game_item (_("3 × 3"),
-                    /* rows */ 3,
-                    /* cols */ 3,
-                           ref menu);
-
-        /* Translators: on main window, entry of the menu when clicking on the "New Game" button; to change 
grid size to 4 × 4 */
-        _append_new_game_item (_("4 × 4"),
-                    /* rows */ 4,
-                    /* cols */ 4,
-                           ref menu);
-
-        /* Translators: on main window, entry of the menu when clicking on the "New Game" button; to change 
grid size to 5 × 5 */
-        _append_new_game_item (_("5 × 5"),
-                    /* rows */ 5,
-                    /* cols */ 5,
-                           ref menu);
-
-        int rows = _settings.get_int ("rows");
-        int cols = _settings.get_int ("cols");
-        bool is_square = rows == cols;
-        bool disallowed_grid = is_disallowed_grid_size (ref rows, ref cols);
-        if (disallowed_grid && !is_square)
-            /* Translators: command-line warning displayed if the user manually sets a invalid grid size */
-            warning (_("Grids of size 1 by 2 are disallowed."));
-
-        if (!disallowed_grid && (!is_square || (is_square && rows != 4 && rows != 3 && rows != 5)))
-            /* Translators: on main window, entry of the menu when clicking on the "New Game" button; 
appears only if the user has set rows and cols manually */
-            _append_new_game_item (_("Custom"), /* rows */ rows, /* cols */ cols, ref menu);
-
-        menu.freeze ();
-        _new_game_button.set_menu_model ((MenuModel) menu);
-    }
-    private static void _append_new_game_item (string label, int rows, int cols, ref GLib.Menu menu)
-    {
-        Variant variant = new Variant ("(ii)", rows, cols);
-        menu.append (label, "ui.new-game-sized(" + variant.print (/* annotate types */ true) + ")");
-    }
-
-    internal static bool is_disallowed_grid_size (ref int rows, ref int cols)
-        requires (rows >= 1)
-        requires (rows <= 9)
-        requires (cols >= 1)
-        requires (cols <= 9)
-    {
-        return (rows == 1 && cols == 1) || (rows == 1 && cols == 2) || (rows == 2 && cols == 1);
-    }
-
     /*\
     * * window management callbacks
     \*/
@@ -356,7 +243,7 @@ private class GameWindow : ApplicationWindow
     [GtkCallback]
     private bool key_press_event_cb (Widget widget, Gdk.EventKey event)
     {
-        if (_hamburger_button.active || (((Window) widget).focus_visible && !_embed.is_focus))
+        if (_header_bar.has_popover () || (((Window) widget).focus_visible && !_embed.is_focus))
             return false;
         if (_game.cannot_move ())
             return false;
@@ -525,6 +412,15 @@ private class GameWindow : ApplicationWindow
             });
     }
 
+    internal static bool is_disallowed_grid_size (ref int rows, ref int cols)
+        requires (rows >= 1)
+        requires (rows <= 9)
+        requires (cols >= 1)
+        requires (cols <= 9)
+    {
+        return (rows == 1 && cols == 1) || (rows == 1 && cols == 2) || (rows == 2 && cols == 1);
+    }
+
     /*\
     * * gesture
     \*/
diff --git a/src/meson.build b/src/meson.build
index 83d3302..d378c6b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -8,6 +8,7 @@ gnome_2048_sources = [
   'application.vala',
   'config.vapi',
   'game.vala',
+  'game-headerbar.vala',
   'game-window.vala',
   'grid.vala',
   'view.vala',
diff --git a/src/org.gnome.TwentyFortyEight.gresource.xml b/src/org.gnome.TwentyFortyEight.gresource.xml
index fbb336b..b2cf713 100644
--- a/src/org.gnome.TwentyFortyEight.gresource.xml
+++ b/src/org.gnome.TwentyFortyEight.gresource.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/org/gnome/TwentyFortyEight/ui">
     <file preprocess="xml-stripblanks" alias="congrats.ui">../data/congrats.ui</file>
+    <file preprocess="xml-stripblanks" alias="game-headerbar.ui">../data/game-headerbar.ui</file>
     <file preprocess="xml-stripblanks" alias="game-window.ui">../data/mainwindow.ui</file>
     <!-- file>data/style.css</file -->
   </gresource>


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