[four-in-a-row/arnaudb/new-ui: 9/12] Introduce NewGameScreen.



commit 28bfea1ef82fce0011e7c042a2847cff5b089bf8
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Wed Dec 18 16:23:15 2019 +0100

    Introduce NewGameScreen.
    
    Copied from Iagno 3.34.

 data/four-in-a-row.css                   |  50 ++++-
 data/org.gnome.Four-in-a-row.gschema.xml |   3 +
 data/ui/adaptative-window.ui             |  27 +++
 data/ui/fiar-screens.ui                  |  91 +++++++-
 data/ui/four-in-a-row.ui                 |  20 +-
 po/POTFILES.in                           |   1 +
 po/POTFILES.skip                         |   1 +
 src/adaptative-window.vala               | 353 +++++++++++++++++++++++++++++++
 src/four-in-a-row.gresource.xml          |   1 +
 src/four-in-a-row.vala                   | 105 +++++----
 src/game-board-view.vala                 |   2 -
 src/game-window.vala                     |  62 ++----
 src/meson.build                          |   2 +
 src/new-game-screen.vala                 | 149 +++++++++++++
 14 files changed, 760 insertions(+), 107 deletions(-)
---
diff --git a/data/four-in-a-row.css b/data/four-in-a-row.css
index c57ab45..84699d2 100644
--- a/data/four-in-a-row.css
+++ b/data/four-in-a-row.css
@@ -25,4 +25,52 @@ GtkButtonBox {
   -GtkButtonBox-child-internal-pad-x:0;
 }
 
-.game-box { padding:1.5rem; }
+.extra-thin-window.thin-window button.new-game-button { padding-left:12px; padding-right:12px; transition: 
padding 0 ease;
+/* hack: fix the double spacing around the centerwidget box, on extra-thin window */
+                                                        margin-right:-12px; }
+/*\
+* * board generics; TODO move in game-window.css
+\*/
+
+                               .game-box                { transition:padding 0.3s; padding:1.5rem; }
+                  .thin-window .game-box                {                          padding:1.0rem; }
+.extra-thin-window.thin-window .game-box,
+                  .flat-window .game-box                {                          padding:0.5rem; }
+.extra-flat-window.flat-window .game-box                {                          padding:0.4rem; }
+
+/*\
+* * start-game button; TODO move in game-window.css
+\*/
+
+                               button.start-game-button { margin-top:1.5rem; margin-bottom:0.5rem;
+                                               transition:margin-top 0 ease, margin-bottom 0 ease; }
+                  .flat-window button.start-game-button { margin-top:0.5rem;                       }
+.extra-flat-window.flat-window button.start-game-button { margin-top:  0rem; margin-bottom:0.4rem; }
+
+.extra-thin-window             button.start-game-button { margin-top:1.0rem; }
+
+/*\
+* * options buttons
+\*/
+
+.extra-flat-window button.start-game-button {
+  min-height:2rem;
+}
+                   button.start-game-button {
+  min-height:3rem;
+  min-width:11rem;
+
+  transition:min-height          0.3s ease 0.01s;
+}
+
+/*\
+* * labels' tweaks
+\*/
+
+label.bold-label {
+  font-weight: bold;
+}
+
+/*\
+* * the end
+\*/
diff --git a/data/org.gnome.Four-in-a-row.gschema.xml b/data/org.gnome.Four-in-a-row.gschema.xml
index 4d33079..b98552d 100644
--- a/data/org.gnome.Four-in-a-row.gschema.xml
+++ b/data/org.gnome.Four-in-a-row.gschema.xml
@@ -63,6 +63,9 @@
       <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-drop' 
-->
       <description>Key press to drop a marble.</description>
     </key>
+  </schema>
+
+  <schema id="org.gnome.Four-in-a-row.Lib" gettext-domain="four-in-a-row">
     <key name="window-width" type="i">
       <default>675</default>
       <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/window-width' 
-->
diff --git a/data/ui/adaptative-window.ui b/data/ui/adaptative-window.ui
new file mode 100644
index 0000000..f9e31f2
--- /dev/null
+++ b/data/ui/adaptative-window.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  This file is part of GNOME Reversi
+
+  GNOME Reversi 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 Reversi 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 Reversi.  If not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <template class="AdaptativeWindow" parent="GtkApplicationWindow">
+    <property name="height-request">284</property>  <!-- 288px max for Purism Librem 5 landscape, for 720px 
width; update gschema also -->
+    <property name="width-request">490</property> <!-- 350</property>   <! - 360px max for Purism Librem 5 
portrait, for 648px height; update gschema also -->
+    <signal name="window-state-event"   handler="on_window_state_event"/>
+    <signal name="size-allocate"        handler="on_size_allocate"/>
+    <signal name="destroy"              handler="on_destroy"/>
+  </template>
+</interface>
diff --git a/data/ui/fiar-screens.ui b/data/ui/fiar-screens.ui
index 6c6de7b..3a599cd 100644
--- a/data/ui/fiar-screens.ui
+++ b/data/ui/fiar-screens.ui
@@ -19,17 +19,85 @@
 -->
 <interface>
   <requires lib="gtk+" version="3.12"/>
-  <object class="GtkBox" id="new-game-screen">
+  <template class="NewGameScreen" parent="GtkBox">
     <property name="orientation">vertical</property>
     <property name="visible">True</property>
     <property name="homogeneous">True</property>
     <property name="valign">fill</property>
     <property name="spacing">18</property>
-    <property name="margin-bottom">25</property><!-- TODO better -->
-    <property name="height-request">263</property>
-    <property name="width-request">400</property>
+    <property name="margin-bottom">22</property><!-- TODO better -->
+    <property name="margin-top">4</property>
     <child>
-      <object class="GtkBox">
+      <object class="GtkBox" id="infos_section">
+        <property name="orientation">vertical</property>
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="halign">start</property>
+            <!-- Translators: when configuring a new game, on a thin window, header of the row for choosing 
the number of players -->
+            <property name="label" translatable="yes">Game type</property>
+            <style>
+              <class name="bold-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="vexpand">True</property>
+            <property name="homogeneous">True</property>
+            <property name="spacing">0</property>
+            <property name="orientation">vertical</property>
+            <style>
+              <class name="linked"/>
+            </style>
+            <child>
+              <object class="GtkModelButton">
+                <property name="visible">True</property>
+                <property name="use_underline">True</property>
+                <!-- Translators: when configuring a new game, on a thin window, group "Game type", label of 
the button to choose to play first (with a mnemonic that appears pressing Alt) -->
+                <property name="text" translatable="yes">Play _first</property>
+                <property name="action-name">app.game-type</property>
+                <property name="action-target">'human'</property>
+                <property name="iconic">True</property>
+                <property name="centered">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkModelButton">
+                <property name="visible">True</property>
+                <property name="use_underline">True</property>
+                <!-- Translators: when configuring a new game, on a thin window, group "Game type", label of 
the button to choose to play second (with a mnemonic that appears pressing Alt) -->
+                <property name="text" translatable="yes">Play _second</property>
+                <property name="action-name">app.game-type</property>
+                <property name="action-target">'computer'</property>
+                <property name="iconic">True</property>
+                <property name="centered">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkModelButton">
+                <property name="visible">True</property>
+                <property name="use_underline">True</property>
+                <!-- Translators: when configuring a new game, on a thin window, group "Game type", label of 
the button to choose a two-players game (with a mnemonic that appears pressing Alt) -->
+                <property name="text" translatable="yes">_Two players</property>
+                <property name="action-name">app.game-type</property>
+                <property name="action-target">'two'</property>
+                <property name="iconic">True</property>
+                <property name="centered">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox" id="users_section">
         <property name="orientation">vertical</property>
         <property name="visible">True</property>
         <property name="spacing">6</property>
@@ -45,8 +113,9 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox">
+          <object class="GtkBox" id="users_box">
             <property name="visible">True</property>
+            <property name="vexpand">True</property>
             <property name="homogeneous">True</property>
             <property name="spacing">12</property>
             <child>
@@ -97,8 +166,9 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="difficulty-box">
+          <object class="GtkBox" id="level_box">
             <property name="visible">True</property>
+            <property name="vexpand">True</property>
             <property name="homogeneous">True</property>
             <property name="spacing">12</property>
             <child>
@@ -145,7 +215,7 @@
       </object>
     </child>
     <child>
-      <object class="GtkBox">
+      <object class="GtkBox" id="start_section">
         <property name="orientation">vertical</property>
         <property name="visible">True</property>
         <property name="spacing">6</property>
@@ -161,8 +231,9 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="start-box">
+          <object class="GtkBox" id="start_box">
             <property name="visible">True</property>
+            <property name="vexpand">True</property>
             <property name="homogeneous">True</property>
             <property name="spacing">12</property>
             <child>
@@ -196,5 +267,5 @@
         </child>
       </object>
     </child>
-  </object>
+  </template>
 </interface>
diff --git a/data/ui/four-in-a-row.ui b/data/ui/four-in-a-row.ui
index b4a7360..f480687 100644
--- a/data/ui/four-in-a-row.ui
+++ b/data/ui/four-in-a-row.ui
@@ -31,7 +31,7 @@
       </item>
     </section>
   </menu>
-  <template class="GameWindow" parent="GtkApplicationWindow">
+  <template class="GameWindow" parent="AdaptativeWindow">
     <!-- <initial-focus name="view"/> -->
     <child type="titlebar">
       <object class="GtkHeaderBar" id="headerbar">
@@ -129,15 +129,16 @@
             <property name="visible">True</property>
             <property name="homogeneous">True</property>
             <child>
-              <object class="GtkBox" id="new_game_box">
-                <property name="orientation">vertical</property>
+              <object class="GtkScrolledWindow">
                 <property name="visible">True</property>
-                <property name="halign">center</property>
-                <property name="valign">center</property>
-                <property name="margin">25</property>
-                <property name="width-request">350</property>
-                <property name="height-request">350</property>
-                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkBox" id="new_game_box">
+                    <property name="orientation">vertical</property>
+                    <property name="visible">True</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                  </object>
+                </child>
               </object>
               <packing>
                 <property name="name">start-box</property>
@@ -147,7 +148,6 @@
               <object class="GtkBox" id="game_box">
                 <property name="visible">True</property>
                 <property name="orientation">horizontal</property>
-                <property name="spacing">25</property>
                 <style>
                   <class name="game-box"/>
                 </style>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b19cd92..3301afb 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,6 +6,7 @@ data/org.gnome.Four-in-a-row.appdata.xml.in
 data/org.gnome.Four-in-a-row.desktop.in
 data/org.gnome.Four-in-a-row.gschema.xml
 src/four-in-a-row.vala
+src/new-game-screen.vala
 src/prefs.vala
 src/scorebox.vala
 src/theme.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index b54d3e5..d86310f 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,4 +1,5 @@
 src/four-in-a-row.c
+src/new-game-screen.c
 src/prefs.c
 src/scorebox.c
 src/theme.c
diff --git a/src/adaptative-window.vala b/src/adaptative-window.vala
new file mode 100644
index 0000000..47bd8be
--- /dev/null
+++ b/src/adaptative-window.vala
@@ -0,0 +1,353 @@
+/*
+  This file is part of GNOME Reversi
+
+  GNOME Reversi 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 Reversi 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 Reversi.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+using Gtk;
+
+private interface AdaptativeWidget : Object
+{ /*
+       ╎ extra ╎
+       ╎ thin  ╎
+  ╶╶╶╶ ┏━━━━━━━┳━━━━━━━┳━━━━━──╴
+ extra ┃ PHONE ┃ PHONE ┃ EXTRA
+ flat  ┃ _BOTH ┃ _HZTL ┃ _FLAT
+  ╶╶╶╶ ┣━━━━━━━╋━━━━━━━╋━━━━╾──╴
+       ┃ PHONE ┃       ┃
+       ┃ _VERT ┃       ┃
+       ┣━━━━━━━┫       ┃
+       ┃ EXTRA ┃ QUITE ╿ USUAL
+       ╿ _THIN │ _THIN │ _SIZE
+       ╵       ╵       ╵
+       ╎   quite thin  ╎
+                              */
+
+    internal enum WindowSize {
+        START_SIZE,
+        USUAL_SIZE,
+        QUITE_THIN,
+        PHONE_VERT,
+        PHONE_HZTL,
+        PHONE_BOTH,
+        EXTRA_THIN,
+        EXTRA_FLAT;
+
+        internal static inline bool is_phone_size (WindowSize window_size)
+        {
+            return (window_size == PHONE_BOTH) || (window_size == PHONE_VERT) || (window_size == PHONE_HZTL);
+        }
+
+        internal static inline bool is_extra_thin (WindowSize window_size)
+        {
+            return (window_size == PHONE_BOTH) || (window_size == PHONE_VERT) || (window_size == EXTRA_THIN);
+        }
+
+        internal static inline bool is_extra_flat (WindowSize window_size)
+        {
+            return (window_size == PHONE_BOTH) || (window_size == PHONE_HZTL) || (window_size == EXTRA_FLAT);
+        }
+
+        internal static inline bool is_quite_thin (WindowSize window_size)
+        {
+            return is_extra_thin (window_size) || (window_size == PHONE_HZTL) || (window_size == QUITE_THIN);
+        }
+    }
+
+    internal abstract void set_window_size (WindowSize new_size);
+}
+
+private const int LARGE_WINDOW_SIZE = 1042;
+
+[GtkTemplate (ui = "/org/gnome/Four-in-a-row/ui/adaptative-window.ui")]
+private abstract class AdaptativeWindow : ApplicationWindow
+{
+    [CCode (notify = false)] public string window_title
+    {
+        protected construct
+        {
+            string? _value = value;
+            if (_value == null)
+                assert_not_reached ();
+
+            title = value;
+        }
+    }
+
+    private StyleContext window_style_context;
+    [CCode (notify = false)] public string specific_css_class_or_empty
+    {
+        protected construct
+        {
+            string? _value = value;
+            if (_value == null)
+                assert_not_reached ();
+
+            window_style_context = get_style_context ();
+            if (value != "")
+                window_style_context.add_class (value);
+        }
+    }
+
+    construct
+    {
+        // window_style_context is created by specific_css_class_or_empty
+        window_style_context.add_class ("startup");
+
+        manage_high_contrast ();
+
+        load_window_state ();
+
+        Timeout.add (300, () => { window_style_context.remove_class ("startup"); return Source.REMOVE; });
+    }
+
+    /*\
+    * * callbacks
+    \*/
+
+    [GtkCallback]
+    private bool on_window_state_event (Widget widget, Gdk.EventWindowState event)
+    {
+        if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
+            window_is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
+
+        /* fullscreen: saved as maximized */
+        bool window_was_fullscreen = window_is_fullscreen;
+        if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
+            window_is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
+        if (window_was_fullscreen && !window_is_fullscreen)
+            on_unfullscreen ();
+        else if (!window_was_fullscreen && window_is_fullscreen)
+            on_fullscreen ();
+
+        /* tiled: not saved, but should not change saved window size */
+        Gdk.WindowState tiled_state = Gdk.WindowState.TILED
+                                    | Gdk.WindowState.TOP_TILED
+                                    | Gdk.WindowState.BOTTOM_TILED
+                                    | Gdk.WindowState.LEFT_TILED
+                                    | Gdk.WindowState.RIGHT_TILED;
+        if ((event.changed_mask & tiled_state) != 0)
+            window_is_tiled = (event.new_window_state & tiled_state) != 0;
+
+        return false;
+    }
+    protected abstract void on_fullscreen ();
+    protected abstract void on_unfullscreen ();
+
+    [GtkCallback]
+    private void on_size_allocate (Allocation allocation)
+    {
+        int height = allocation.height;
+        int width = allocation.width;
+
+        update_adaptative_children (ref width, ref height);
+        update_window_state ();
+    }
+
+    [GtkCallback]
+    private void on_destroy ()
+    {
+        before_destroy ();
+        save_window_state ();
+        base.destroy ();
+    }
+
+    protected virtual void before_destroy () {}
+
+    /*\
+    * * adaptative stuff
+    \*/
+
+    private AdaptativeWidget.WindowSize window_size = AdaptativeWidget.WindowSize.START_SIZE;
+
+    private List<AdaptativeWidget> adaptative_children = new List<AdaptativeWidget> ();
+    protected void add_adaptative_child (AdaptativeWidget child)
+    {
+        adaptative_children.append (child);
+    }
+
+    private void update_adaptative_children (ref int width, ref int height)
+    {
+        bool extra_flat = height < 400;
+        bool flat       = height < 500;
+
+        if (width < 590)
+        {
+            if (extra_flat)         change_window_size (AdaptativeWidget.WindowSize.PHONE_BOTH);
+            else if (height < 787)  change_window_size (AdaptativeWidget.WindowSize.PHONE_VERT);
+            else                    change_window_size (AdaptativeWidget.WindowSize.EXTRA_THIN);
+
+            set_style_classes (/* extra thin */ true, /* thin */ true, /* large */ false,
+                               /* extra flat */ extra_flat, /* flat */ flat);
+        }
+        else if (width < 787)
+        {
+            if (extra_flat)         change_window_size (AdaptativeWidget.WindowSize.PHONE_HZTL);
+            else                    change_window_size (AdaptativeWidget.WindowSize.QUITE_THIN);
+
+            set_style_classes (/* extra thin */ false, /* thin */ true, /* large */ false,
+                               /* extra flat */ extra_flat, /* flat */ flat);
+        }
+        else
+        {
+            if (extra_flat)         change_window_size (AdaptativeWidget.WindowSize.EXTRA_FLAT);
+            else                    change_window_size (AdaptativeWidget.WindowSize.USUAL_SIZE);
+
+            set_style_classes (/* extra thin */ false, /* thin */ false, /* large */ (width > 
LARGE_WINDOW_SIZE),
+                               /* extra flat */ extra_flat, /* flat */ flat);
+        }
+    }
+
+    private void change_window_size (AdaptativeWidget.WindowSize new_window_size)
+    {
+        if (window_size == new_window_size)
+            return;
+        window_size = new_window_size;
+        adaptative_children.@foreach ((adaptative_child) => adaptative_child.set_window_size 
(new_window_size));
+    }
+
+    /*\
+    * * manage style classes
+    \*/
+
+    private bool has_extra_thin_window_class = false;
+    private bool has_thin_window_class = false;
+    private bool has_large_window_class = false;
+    private bool has_extra_flat_window_class = false;
+    private bool has_flat_window_class = false;
+
+    private void set_style_classes (bool extra_thin_window, bool thin_window, bool large_window,
+                                    bool extra_flat_window, bool flat_window)
+    {
+        // for width
+        if (has_extra_thin_window_class && !extra_thin_window)
+            set_style_class ("extra-thin-window", false, ref has_extra_thin_window_class);
+        if (has_thin_window_class && !thin_window)
+            set_style_class ("thin-window", false, ref has_thin_window_class);
+
+        if (large_window != has_large_window_class)
+            set_style_class ("large-window", large_window, ref has_large_window_class);
+        if (thin_window != has_thin_window_class)
+            set_style_class ("thin-window", thin_window, ref has_thin_window_class);
+        if (extra_thin_window != has_extra_thin_window_class)
+            set_style_class ("extra-thin-window", extra_thin_window, ref has_extra_thin_window_class);
+
+        // for height
+        if (has_extra_flat_window_class && !extra_flat_window)
+            set_style_class ("extra-flat-window", false, ref has_extra_flat_window_class);
+
+        if (flat_window != has_flat_window_class)
+            set_style_class ("flat-window", flat_window, ref has_flat_window_class);
+        if (extra_flat_window != has_extra_flat_window_class)
+            set_style_class ("extra-flat-window", extra_flat_window, ref has_extra_flat_window_class);
+    }
+
+    private inline void set_style_class (string class_name, bool new_state, ref bool old_state)
+    {
+        old_state = new_state;
+        if (new_state)
+            window_style_context.add_class (class_name);
+        else
+            window_style_context.remove_class (class_name);
+    }
+
+    /*\
+    * * manage window state
+    \*/
+
+    [CCode (notify = false)] public string schema_path
+    {
+        protected construct
+        {
+            string? _value = value;
+            if (_value == null)
+                assert_not_reached ();
+
+            settings = new GLib.Settings.with_path ("org.gnome.Four-in-a-row.Lib", value);
+        }
+    }
+    private GLib.Settings settings;
+
+    private int window_width = 0;
+    private int window_height = 0;
+    private bool window_is_maximized = false;
+    private bool window_is_fullscreen = false;
+    private bool window_is_tiled = false;
+
+    private void load_window_state ()   // called on construct
+    {
+        if (settings.get_boolean ("window-is-maximized"))
+            maximize ();
+        set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height"));
+    }
+
+    private void update_window_state () // called on size-allocate
+    {
+        if (window_is_maximized || window_is_tiled || window_is_fullscreen)
+            return;
+        int? _window_width = null;
+        int? _window_height = null;
+        get_size (out _window_width, out _window_height);
+        if (_window_width == null || _window_height == null)
+            return;
+        window_width = (!) _window_width;
+        window_height = (!) _window_height;
+    }
+
+    private void save_window_state ()   // called on destroy
+    {
+        settings.delay ();
+        settings.set_int ("window-width", window_width);
+        settings.set_int ("window-height", window_height);
+        settings.set_boolean ("window-is-maximized", window_is_maximized || window_is_fullscreen);
+        settings.apply ();
+    }
+
+    /*\
+    * * manage high-constrast
+    \*/
+
+    internal signal void gtk_theme_changed ();
+
+    private void manage_high_contrast ()
+    {
+        Gtk.Settings? nullable_gtk_settings = Gtk.Settings.get_default ();
+        if (nullable_gtk_settings == null)
+            return;
+
+        Gtk.Settings gtk_settings = (!) nullable_gtk_settings;
+        gtk_settings.notify ["gtk-theme-name"].connect (update_highcontrast_state);
+        _update_highcontrast_state (gtk_settings.gtk_theme_name);
+    }
+
+    private void update_highcontrast_state (Object gtk_settings, ParamSpec unused)
+    {
+        _update_highcontrast_state (((Gtk.Settings) gtk_settings).gtk_theme_name);
+        gtk_theme_changed ();
+    }
+
+    private bool highcontrast_state = false;
+    private void _update_highcontrast_state (string theme_name)
+    {
+        bool highcontrast_new_state = "HighContrast" in theme_name;
+        if (highcontrast_new_state == highcontrast_state)
+            return;
+        highcontrast_state = highcontrast_new_state;
+
+        if (highcontrast_new_state)
+            window_style_context.add_class ("hc-theme");
+        else
+            window_style_context.remove_class ("hc-theme");
+    }
+}
diff --git a/src/four-in-a-row.gresource.xml b/src/four-in-a-row.gresource.xml
index 492aeed..2143cd1 100644
--- a/src/four-in-a-row.gresource.xml
+++ b/src/four-in-a-row.gresource.xml
@@ -10,6 +10,7 @@
   </gresource>
   <gresource prefix="/org/gnome/Four-in-a-row/ui">
     <file alias="four-in-a-row.css">../data/four-in-a-row.css</file>
+    <file preprocess="xml-stripblanks" compressed="true" 
alias="adaptative-window.ui">../data/ui/adaptative-window.ui</file>
     <file preprocess="xml-stripblanks" compressed="true" 
alias="fiar-screens.ui">../data/ui/fiar-screens.ui</file>
     <file preprocess="xml-stripblanks" compressed="true" 
alias="game-window.ui">../data/ui/four-in-a-row.ui</file>
   </gresource>
diff --git a/src/four-in-a-row.vala b/src/four-in-a-row.vala
index d5f2a02..d31bac0 100644
--- a/src/four-in-a-row.vala
+++ b/src/four-in-a-row.vala
@@ -77,6 +77,7 @@ private class FourInARow : Gtk.Application
     private GameBoardView game_board_view;
     private Board game_board;
     private GameWindow window;
+    private NewGameScreen new_game_screen;
 
     // game state
     private char vstr [53];
@@ -99,6 +100,7 @@ private class FourInARow : Gtk.Application
 
     private const GLib.ActionEntry app_entries [] =  // see also add_actions()
     {
+        { "game-type",      null,           "s", "'dark'", change_game_type },
         { "scores",         on_game_scores          },
         { "quit",           on_game_exit            },
         { "help",           on_help_contents        },
@@ -181,11 +183,13 @@ private class FourInARow : Gtk.Application
 
     private inline void add_actions ()
     {
-        add_action (Prefs.instance.settings.create_action ("sound"));
-        add_action (Prefs.instance.settings.create_action ("theme-id"));
-        add_action (Prefs.instance.settings.create_action ("num-players"));
-        add_action (Prefs.instance.settings.create_action ("first-player"));
-        add_action (Prefs.instance.settings.create_action ("opponent"));
+        GLib.Settings settings = Prefs.instance.settings;
+
+        add_action (settings.create_action ("sound"));
+        add_action (settings.create_action ("theme-id"));
+        add_action (settings.create_action ("num-players"));
+        add_action (settings.create_action ("first-player"));
+        add_action (settings.create_action ("opponent"));
 
         set_accels_for_action ("ui.new-game",           {        "<Primary>n"       });
         set_accels_for_action ("ui.start-game",         { "<Shift><Primary>n"       });
@@ -199,6 +203,63 @@ private class FourInARow : Gtk.Application
         set_accels_for_action ("app.about",             {          "<Shift>F1"      });
 
         add_action_entries (app_entries, this);
+
+        game_type_action = (SimpleAction) lookup_action ("game-type");
+
+        settings.changed ["first-player"].connect (() => {
+                if (settings.get_int ("num-players") == 2)
+                    return;
+                if (settings.get_string ("first-player") == "human")
+                    game_type_action.set_state (new Variant.string ("human"));
+                else
+                    game_type_action.set_state (new Variant.string ("computer"));
+            });
+
+        settings.changed ["num-players"].connect (() => {
+                bool solo = settings.get_int ("num-players") == 1;
+                new_game_screen.update_sensitivity (solo);
+                reset_score = true;
+                if (!solo)
+                    game_type_action.set_state (new Variant.string ("two"));
+                else if (settings.get_string ("first-player") == "human")
+                    game_type_action.set_state (new Variant.string ("human"));
+                else
+                    game_type_action.set_state (new Variant.string ("computer"));
+                if (solo)
+                    last_first_player = PlayerID.NOBODY;
+            });
+        bool solo = settings.get_int ("num-players") == 1;
+        new_game_screen.update_sensitivity (solo);
+
+        if (settings.get_int ("num-players") == 2)
+            game_type_action.set_state (new Variant.string ("two"));
+        else if (settings.get_string ("first-player") == "human")
+            game_type_action.set_state (new Variant.string ("human"));
+        else
+            game_type_action.set_state (new Variant.string ("computer"));
+
+        settings.changed ["opponent"].connect (() => {
+                if (settings.get_int ("num-players") != 1)
+                    return;
+                reset_score = true;
+            });
+    }
+
+    private SimpleAction game_type_action;
+    private void change_game_type (SimpleAction action, Variant? gvariant)
+        requires (gvariant != null)
+    {
+        string type = ((!) gvariant).get_string ();
+//        game_type_action.set_state ((!) gvariant);
+        switch (type)
+        {
+            case "human"    : Prefs.instance.settings.set_int    ("num-players", 1); 
new_game_screen.update_sensitivity (true);
+                              Prefs.instance.settings.set_string ("first-player", "human");                  
                    return;
+            case "computer" : Prefs.instance.settings.set_int    ("num-players", 1); 
new_game_screen.update_sensitivity (true);
+                              Prefs.instance.settings.set_string ("first-player", "computer");               
                    return;
+            case "two"      : Prefs.instance.settings.set_int    ("num-players", 2); 
new_game_screen.update_sensitivity (false); return;
+            default: assert_not_reached ();
+        }
     }
 
     private inline bool column_clicked_cb (int column)
@@ -277,12 +338,6 @@ private class FourInARow : Gtk.Application
         game_reset ();
     }
 
-    protected override void shutdown ()
-    {
-        window.shutdown (Prefs.instance.settings);
-        base.shutdown ();
-    }
-
     private void prompt_player ()
     {
         bool human = is_player_human ();
@@ -741,7 +796,8 @@ private class FourInARow : Gtk.Application
         base.startup ();
 
         /* UI parts */
-        Builder builder = new Builder.from_resource ("/org/gnome/Four-in-a-row/ui/fiar-screens.ui");
+        new_game_screen = new NewGameScreen ();
+        new_game_screen.show ();
 
         game_board_view = new GameBoardView (game_board);
         game_board_view.show ();
@@ -777,12 +833,9 @@ private class FourInARow : Gtk.Application
         /* Window */
         window = new GameWindow ("/org/gnome/Four-in-a-row/ui/four-in-a-row.css",
                                  PROGRAM_NAME,
-                                 Prefs.instance.settings.get_int ("window-width"),
-                                 Prefs.instance.settings.get_int ("window-height"),
-                                 Prefs.instance.settings.get_boolean ("window-is-maximized"),
                                  /* start_now */ true,
                                  GameWindowFlags.SHOW_START_BUTTON,
-                                 (Box) builder.get_object ("new-game-screen"),
+                                 (Box) new_game_screen,
                                  game_board_view,
                                  app_menu);
 
@@ -790,26 +843,6 @@ private class FourInARow : Gtk.Application
 
         add_actions ();
 
-        Widget level_box = (Widget) (!) builder.get_object ("difficulty-box");
-        Widget start_box = (Widget) (!) builder.get_object ("start-box");
-        Prefs.instance.settings.changed ["num-players"].connect (() => {
-                bool solo = Prefs.instance.settings.get_int ("num-players") == 1;
-                level_box.sensitive = solo;
-                start_box.sensitive = solo;
-                reset_score = true;
-                if (solo)
-                    last_first_player = PlayerID.NOBODY;
-            });
-        bool solo = Prefs.instance.settings.get_int ("num-players") == 1;
-        level_box.sensitive = solo;
-        start_box.sensitive = solo;
-
-        Prefs.instance.settings.changed ["opponent"].connect (() => {
-                if (Prefs.instance.settings.get_int ("num-players") != 1)
-                    return;
-                reset_score = true;
-            });
-
         /* various */
         game_board_view.column_clicked.connect (column_clicked_cb);
         window.key_press_event.connect (on_key_press);
diff --git a/src/game-board-view.vala b/src/game-board-view.vala
index 1f4e71c..59d80e1 100644
--- a/src/game-board-view.vala
+++ b/src/game-board-view.vala
@@ -31,8 +31,6 @@ private class GameBoardView : Gtk.DrawingArea {
     private Board game_board;
 
     internal GameBoardView(Board game_board) {
-        /* set a min size to avoid pathological behavior of gtk when scaling down */
-        set_size_request(350, 350);
         halign = Gtk.Align.FILL;
         valign = Gtk.Align.FILL;
 
diff --git a/src/game-window.vala b/src/game-window.vala
index bf9b5fd..f1f5df9 100644
--- a/src/game-window.vala
+++ b/src/game-window.vala
@@ -29,7 +29,7 @@ private enum GameWindowFlags {
 }
 
 [GtkTemplate (ui = "/org/gnome/Four-in-a-row/ui/game-window.ui")]
-private class GameWindow : ApplicationWindow
+private class GameWindow : AdaptativeWindow
 {
     /* settings */
     private bool window_is_tiled;
@@ -66,8 +66,12 @@ private class GameWindow : ApplicationWindow
  // internal signal void redo ();
     internal signal void hint ();
 
-    internal GameWindow (string? css_resource, string name, int width, int height, bool maximized, bool 
start_now, GameWindowFlags flags, Box new_game_screen, Widget _view, GLib.Menu app_menu)
+    internal GameWindow (string? css_resource, string name, bool start_now, GameWindowFlags flags, Box 
new_game_screen, Widget _view, GLib.Menu app_menu)
     {
+        Object (window_title: name,
+                specific_css_class_or_empty: "",
+                schema_path: "/org/gnome/Four-in-a-row/");
+
         if (css_resource != null)
         {
             CssProvider css_provider = new CssProvider ();
@@ -86,21 +90,16 @@ private class GameWindow : ApplicationWindow
         headerbar.set_title (name);
         info_button.set_menu_model (app_menu);
 
-        set_default_size (width, height);
-        if (maximized)
-            maximize ();
-
-        size_allocate.connect (size_allocate_cb);
-        window_state_event.connect (window_state_event_cb);
-
         /* add widgets */
         new_game_box.pack_start (new_game_screen, true, true, 0);
+        add_adaptative_child ((AdaptativeWidget) new_game_screen);
         if (GameWindowFlags.SHOW_START_BUTTON in flags)
         {
             /* Translators: when configuring a new game, label of the blue Start button (with a mnemonic 
that appears pressing Alt) */
             Button _start_game_button = new Button.with_mnemonic (_("_Start Game"));
-            _start_game_button.width_request = 222;
-            _start_game_button.height_request = 60;
+//            _start_game_button.width_request = 222;
+//            _start_game_button.height_request = 60;
+            _start_game_button.get_style_context ().add_class ("start-game-button");
             _start_game_button.halign = Align.CENTER;
             _start_game_button.set_action_name ("ui.start-game");
             /* Translators: when configuring a new game, tooltip text of the blue Start button */
@@ -168,47 +167,14 @@ private class GameWindow : ApplicationWindow
     * * Window events
     \*/
 
-    private void size_allocate_cb ()
-    {
-        if (window_is_maximized || window_is_tiled || window_is_fullscreen)
-            return;
-        get_size (out window_width, out window_height);
-    }
-
-    private bool window_state_event_cb (Gdk.EventWindowState event)
+    protected override void on_fullscreen ()
     {
-        if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
-            window_is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
-
-        /* fullscreen: saved as maximized */
-        bool window_was_fullscreen = window_is_fullscreen;
-        if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
-            window_is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
-        if (window_was_fullscreen && !window_is_fullscreen)
-            unfullscreen_button.hide ();
-        else if (!window_was_fullscreen && window_is_fullscreen)
-            unfullscreen_button.show ();
-
-        /* tiled: not saved, but should not change saved window size */
-        Gdk.WindowState tiled_state = Gdk.WindowState.TILED
-                                    | Gdk.WindowState.TOP_TILED
-                                    | Gdk.WindowState.BOTTOM_TILED
-                                    | Gdk.WindowState.LEFT_TILED
-                                    | Gdk.WindowState.RIGHT_TILED;
-        if ((event.changed_mask & tiled_state) != 0)
-            window_is_tiled = (event.new_window_state & tiled_state) != 0;
-
-        return false;
+        unfullscreen_button.show ();
     }
 
-    internal void shutdown (GLib.Settings settings)
+    protected override void on_unfullscreen ()
     {
-        settings.delay ();
-        settings.set_int ("window-width", window_width);
-        settings.set_int ("window-height", window_height);
-        settings.set_boolean ("window-is-maximized", window_is_maximized || window_is_fullscreen);
-        settings.apply ();
-        destroy ();
+        unfullscreen_button.hide ();
     }
 
     /*\
diff --git a/src/meson.build b/src/meson.build
index 9744346..dbb2617 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -18,11 +18,13 @@ test('four-in-a-row-tests',
 resources = gnome.compile_resources (meson.project_name(), 'four-in-a-row.gresource.xml')
 
 sources = files(
+    'adaptative-window.vala',
     'ai.vala',
     'four-in-a-row.vala',
     'game-board-view.vala',
     'game-board.vala',
     'game-window.vala',
+    'new-game-screen.vala',
     'prefs.vala',
     'scorebox.vala',
     'theme.vala',
diff --git a/src/new-game-screen.vala b/src/new-game-screen.vala
new file mode 100644
index 0000000..2c9f94f
--- /dev/null
+++ b/src/new-game-screen.vala
@@ -0,0 +1,149 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+   This file is part of GNOME Four-in-a-row.
+
+   Copyright 2010-2013 Robert Ancell
+   Copyright 2013-2014 Michael Catanzaro
+   Copyright 2014-2019 Arnaud Bonatti
+
+   GNOME Four-in-a-row 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 Four-in-a-row 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 Four-in-a-row.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Four-in-a-row/ui/fiar-screens.ui")]
+private class NewGameScreen : Box, AdaptativeWidget
+{
+    [GtkChild] private Box infos_section;
+    [GtkChild] private Box users_section;
+    [GtkChild] private Box start_section;
+
+    [GtkChild] private Box users_box;
+    [GtkChild] private Box level_box;
+    [GtkChild] private Box start_box;
+
+    internal void update_sensitivity (bool new_sensitivity)
+    {
+        level_box.sensitive = new_sensitivity;
+        start_box.sensitive = new_sensitivity;
+    }
+
+    private bool quite_thin = false;
+    private bool extra_thin = true;     // extra_thin && !quite_thin is impossible, so it will not return in 
next method the first time
+    private bool extra_flat = false;
+    private void set_window_size (AdaptativeWidget.WindowSize new_size)
+    {
+        bool _quite_thin = WindowSize.is_quite_thin (new_size);
+        bool _extra_thin = WindowSize.is_extra_thin (new_size);
+        bool _extra_flat = WindowSize.is_extra_flat (new_size);
+
+        if ((_quite_thin == quite_thin)
+         && (_extra_thin == extra_thin)
+         && (_extra_flat == extra_flat))
+            return;
+        quite_thin = _quite_thin;
+        extra_thin = _extra_thin;
+        extra_flat = _extra_flat;
+
+        if (extra_thin)
+        {
+            set_orientation (Orientation.VERTICAL);
+            spacing = 18;
+            homogeneous = false;
+            height_request = 360;
+            width_request = 250;
+            margin_bottom = 22;
+
+            users_section.hide ();
+            start_section.hide ();
+            infos_section.show ();
+
+            level_box.set_orientation (Orientation.VERTICAL);
+
+            users_box.set_spacing (0);
+            level_box.set_spacing (0);
+            start_box.set_spacing (0);
+
+            users_box.get_style_context ().add_class ("linked");
+            level_box.get_style_context ().add_class ("linked");
+            start_box.get_style_context ().add_class ("linked");
+        }
+        else if (extra_flat)
+        {
+            set_orientation (Orientation.HORIZONTAL);
+            homogeneous = true;
+            height_request = 113;
+            margin_bottom = 6;
+            if (quite_thin)
+            {
+                spacing = 21;
+                width_request = 420;
+            }
+            else
+            {
+                spacing = 24;
+                width_request = 450;
+            }
+
+            users_section.hide ();
+            start_section.hide ();
+            infos_section.show ();
+
+            level_box.set_orientation (Orientation.VERTICAL);
+
+            users_box.set_spacing (0);
+            level_box.set_spacing (0);
+            start_box.set_spacing (0);
+
+            users_box.get_style_context ().add_class ("linked");
+            level_box.get_style_context ().add_class ("linked");
+            start_box.get_style_context ().add_class ("linked");
+        }
+        else
+        {
+            set_orientation (Orientation.VERTICAL);
+            spacing = 18;
+            height_request = 263;
+            int boxes_spacing;
+            if (quite_thin)
+            {
+                boxes_spacing = 10;
+                width_request = 380;
+            }
+            else
+            {
+                boxes_spacing = 12;
+                width_request = 400;
+            }
+            margin_bottom = 22;
+
+            infos_section.hide ();
+            users_section.show ();
+            start_section.show ();
+
+            level_box.set_orientation (Orientation.HORIZONTAL);
+
+            users_box.get_style_context ().remove_class ("linked");
+            level_box.get_style_context ().remove_class ("linked");
+            start_box.get_style_context ().remove_class ("linked");
+
+            users_box.set_spacing (boxes_spacing);
+            level_box.set_spacing (boxes_spacing);
+            start_box.set_spacing (boxes_spacing);
+
+            homogeneous = true;
+        }
+        queue_allocate ();
+    }
+}



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