[gnome-games] ui: Use the SavestatesList widget



commit a9b47cd3f8c4a566692d086998528c664923b247
Author: Yetizone <andreii lisita gmail com>
Date:   Wed Aug 7 19:10:23 2019 +0300

    ui: Use the SavestatesList widget

 data/ui/display-box.ui         |  13 ++++-
 data/ui/display-header-bar.ui  | 121 ++++++++++++++++++++++++++++++++++++-----
 src/ui/display-box.vala        |  28 +++++++++-
 src/ui/display-header-bar.vala |  71 +++++++++++++++++++++---
 src/ui/display-view.vala       |  35 +++++++++---
 5 files changed, 237 insertions(+), 31 deletions(-)
---
diff --git a/data/ui/display-box.ui b/data/ui/display-box.ui
index db028f54..7cef4bdf 100644
--- a/data/ui/display-box.ui
+++ b/data/ui/display-box.ui
@@ -25,8 +25,19 @@
               </packing>
             </child>
             <child>
-              <object class="GtkEventBox" id="display_bin">
+              <object class="GtkBox" id="display_box">
                 <property name="visible">True</property>
+                <child>
+                  <object class="GtkEventBox" id="display_bin">
+                    <property name="visible">True</property>
+                    <property name="hexpand">True</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GamesSavestatesList" id="savestates_list">
+                    <property name="visible">True</property>
+                  </object>
+                </child>
               </object>
               <packing>
                 <property name="name">display</property>
diff --git a/data/ui/display-header-bar.ui b/data/ui/display-header-bar.ui
index 61514a69..8474e6af 100644
--- a/data/ui/display-header-bar.ui
+++ b/data/ui/display-header-bar.ui
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="GamesDisplayHeaderBar" parent="GtkBin">
+  <template class="GamesDisplayHeaderBar" parent="GtkStack">
     <property name="visible">True</property>
     <signal name="notify::is-fullscreen" handler="on_fullscreen_changed"/>
     <signal name="notify::can-fullscreen" handler="on_fullscreen_changed"/>
     <child>
-      <object class="GtkHeaderBar" id="header_bar">
+      <object class="GtkHeaderBar" id="ingame_header_bar">
         <property name="visible">True</property>
         <property name="title" translatable="yes">Games</property>
         <property name="show_close_button">True</property>
@@ -35,24 +35,28 @@
           </object>
         </child>
         <child>
-          <object class="GtkButton" id="fullscreen">
+          <object class="GtkButton" id="restore">
             <property name="visible">False</property>
             <property name="can_focus">False</property>
             <property name="valign">center</property>
             <property name="use-underline">True</property>
-            <signal name="clicked" handler="on_fullscreen_clicked"/>
+            <property name="margin-left">5</property>
+            <signal name="clicked" handler="on_restore_clicked"/>
             <style>
               <class name="image-button"/>
             </style>
+            <style>
+              <class name="flat"/>
+            </style>
             <child internal-child="accessible">
-              <object class="AtkObject" id="a11y-fullscreen">
-                <property name="accessible-name" translatable="yes">Fullscreen</property>
+              <object class="AtkObject" id="a11y-restore">
+                <property name="accessible-name" translatable="yes">Restore</property>
               </object>
             </child>
             <child>
-              <object class="GtkImage" id="fullscreen_image">
+              <object class="GtkImage" id="restore_image">
                 <property name="visible">True</property>
-                <property name="icon-name">view-fullscreen-symbolic</property>
+                <property name="icon-name">view-restore-symbolic</property>
                 <property name="icon-size">1</property>
               </object>
             </child>
@@ -62,24 +66,46 @@
           </packing>
         </child>
         <child>
-          <object class="GtkButton" id="restore">
+          <object class="GtkMenuButton" id="secondary_menu_button">
+            <property name="popover">secondary_menu</property>
+            <property name="use-underline">True</property>
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="can-focus">False</property>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">view-more-symbolic</property>
+                <property name="icon-size">1</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="fullscreen">
             <property name="visible">False</property>
             <property name="can_focus">False</property>
             <property name="valign">center</property>
             <property name="use-underline">True</property>
-            <signal name="clicked" handler="on_restore_clicked"/>
+            <signal name="clicked" handler="on_fullscreen_clicked"/>
             <style>
               <class name="image-button"/>
             </style>
             <child internal-child="accessible">
-              <object class="AtkObject" id="a11y-restore">
-                <property name="accessible-name" translatable="yes">Restore</property>
+              <object class="AtkObject" id="a11y-fullscreen">
+                <property name="accessible-name" translatable="yes">Fullscreen</property>
               </object>
             </child>
             <child>
-              <object class="GtkImage" id="restore_image">
+              <object class="GtkImage" id="fullscreen_image">
                 <property name="visible">True</property>
-                <property name="icon-name">view-restore-symbolic</property>
+                <property name="icon-name">view-fullscreen-symbolic</property>
                 <property name="icon-size">1</property>
               </object>
             </child>
@@ -104,5 +130,72 @@
         </child>
       </object>
     </child>
+    <child>
+      <object class="GtkHeaderBar" id="savestates_header_bar">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkButton" id="load">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Load</property>
+            <signal name="clicked" handler="on_savestates_load_clicked"/>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="delete">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Delete</property>
+            <signal name="clicked" handler="on_savestates_delete_clicked"/>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Cancel</property>
+            <signal name="clicked" handler="on_savestates_cancel_clicked"/>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+      </object>
+    </child>
   </template>
+  <object class="GtkPopoverMenu" id="secondary_menu">
+    <child>
+      <object class="GtkBox" id="secondary_menu_box">
+        <property name="visible">True</property>
+        <property name="margin">6</property>
+        <child>
+          <object class="GtkModelButton" id="savestates_menu_button">
+            <property name="visible">True</property>
+            <property name="text" translatable="yes">_Savestates</property>
+            <signal name="clicked" handler="on_secondary_menu_savestates_clicked"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkSizeGroup">
+    <property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property>
+    <widgets>
+      <widget name="load"/>
+      <widget name="delete"/>
+      <widget name="cancel"/>
+    </widgets>
+  </object>
 </interface>
diff --git a/src/ui/display-box.vala b/src/ui/display-box.vala
index 1162066c..f268d8ea 100644
--- a/src/ui/display-box.vala
+++ b/src/ui/display-box.vala
@@ -10,11 +10,21 @@ private class Games.DisplayBox : Gtk.Bin {
                get { return fullscreen_header_bar; }
        }
 
+       public SavestatesListState savestates_list_state {
+               get { return savestates_list.state; }
+               set {
+                       value.notify["is-revealed"].connect (on_savestates_list_state_changed);
+
+                       savestates_list.state = value;
+                       fullscreen_header_bar.savestates_list_state = value;
+               }
+       }
+
        private Runner _runner;
        public Runner runner {
                get { return _runner; }
                set {
-                       stack.visible_child = display_bin;
+                       stack.visible_child = display_box;
 
                        _runner = value;
                        remove_display ();
@@ -25,6 +35,8 @@ private class Games.DisplayBox : Gtk.Bin {
 
                        var display = runner.get_display ();
                        set_display (display);
+
+                       savestates_list.runner = value;
                }
        }
 
@@ -35,11 +47,15 @@ private class Games.DisplayBox : Gtk.Bin {
        [GtkChild]
        private ErrorDisplay error_display;
        [GtkChild]
+       private Gtk.Box display_box;
+       [GtkChild]
        private Gtk.EventBox display_bin;
        [GtkChild]
        private DisplayHeaderBar fullscreen_header_bar;
-       private Binding fullscreen_binding;
+       [GtkChild]
+       private SavestatesList savestates_list;
 
+       private Binding fullscreen_binding;
        private long timeout_id;
 
        construct {
@@ -49,6 +65,10 @@ private class Games.DisplayBox : Gtk.Bin {
                timeout_id = -1;
        }
 
+       public DisplayBox (SavestatesListState savestates_list_state) {
+               Object (savestates_list_state: savestates_list_state);
+       }
+
        public void display_running_game_failed (Game game, string error_message) {
                stack.visible_child = error_display;
                error_display.running_game_failed (game, error_message);
@@ -94,4 +114,8 @@ private class Games.DisplayBox : Gtk.Bin {
 
                return runner.gamepad_button_press_event (button);
        }
+
+       public void on_savestates_list_state_changed () {
+               fullscreen_box.autohide = !savestates_list.state.is_revealed;
+       }
 }
diff --git a/src/ui/display-header-bar.vala b/src/ui/display-header-bar.vala
index 7e572fc4..9d1e9939 100644
--- a/src/ui/display-header-bar.vala
+++ b/src/ui/display-header-bar.vala
@@ -1,18 +1,32 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
 [GtkTemplate (ui = "/org/gnome/Games/ui/display-header-bar.ui")]
-private class Games.DisplayHeaderBar : Gtk.Bin {
+private class Games.DisplayHeaderBar : Gtk.Stack {
        public signal void back ();
 
        [GtkChild]
        private MediaMenuButton media_button;
 
+       private SavestatesListState _savestates_list_state;
+       public SavestatesListState savestates_list_state {
+               get { return _savestates_list_state; }
+               set {
+                       _savestates_list_state = value;
+
+                       if (value != null)
+                               value.notify["is-revealed"].connect (on_savestates_list_state_changed);
+               }
+       }
+
        public string game_title {
-               set { header_bar.title = value; }
+               set {
+                       ingame_header_bar.title = value;
+                       savestates_header_bar.title = value;
+               }
        }
 
        public bool show_title_buttons {
-               set { header_bar.show_close_button = value; }
+               set { ingame_header_bar.show_close_button = value; }
        }
 
        public bool can_fullscreen { get; set; }
@@ -31,8 +45,12 @@ private class Games.DisplayHeaderBar : Gtk.Bin {
                        _runner = value;
                        input_mode_switcher.runner = value;
 
-                       if (runner != null)
+                       if (runner != null) {
                                extra_widget = runner.get_extra_widget ();
+
+                               secondary_menu_button.sensitive = runner.supports_savestates;
+                               secondary_menu_button.visible = runner.can_support_savestates;
+                       }
                        else
                                extra_widget = null;
                }
@@ -46,28 +64,40 @@ private class Games.DisplayHeaderBar : Gtk.Bin {
                                return;
 
                        if (extra_widget != null)
-                               header_bar.remove (extra_widget);
+                               ingame_header_bar.remove (extra_widget);
 
                        _extra_widget = value;
 
                        if (extra_widget != null)
-                               header_bar.pack_end (extra_widget);
+                               ingame_header_bar.pack_end (extra_widget);
                }
        }
 
        [GtkChild]
-       private Gtk.HeaderBar header_bar;
+       private Gtk.HeaderBar ingame_header_bar;
        [GtkChild]
        private Gtk.Button fullscreen;
        [GtkChild]
        private Gtk.Button restore;
+       [GtkChild]
+       private Gtk.MenuButton secondary_menu_button;
+       [GtkChild]
+       private Gtk.HeaderBar savestates_header_bar;
 
        private Settings settings;
 
+       public DisplayHeaderBar (SavestatesListState savestates_list_state) {
+               Object (savestates_list_state: savestates_list_state);
+       }
+
        construct {
                settings = new Settings ("org.gnome.Games");
        }
 
+       public void hide_secondary_menu_button () {
+               secondary_menu_button.visible = false;
+       }
+
        [GtkCallback]
        private void on_fullscreen_changed () {
                fullscreen.visible = can_fullscreen && !is_fullscreen;
@@ -90,4 +120,31 @@ private class Games.DisplayHeaderBar : Gtk.Bin {
                is_fullscreen = false;
                settings.set_boolean ("fullscreen", false);
        }
+
+       [GtkCallback]
+       private void on_secondary_menu_savestates_clicked () {
+               savestates_list_state.is_revealed = true;
+       }
+
+       [GtkCallback]
+       private void on_savestates_load_clicked () {
+               savestates_list_state.load_clicked ();
+       }
+
+       [GtkCallback]
+       private void on_savestates_delete_clicked () {
+               savestates_list_state.delete_clicked ();
+       }
+
+       [GtkCallback]
+       private void on_savestates_cancel_clicked () {
+               savestates_list_state.is_revealed = false;
+       }
+
+       private void on_savestates_list_state_changed () {
+               if (savestates_list_state.is_revealed)
+                       set_visible_child (savestates_header_bar);
+               else
+                       set_visible_child (ingame_header_bar);
+       }
 }
diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala
index d6102db3..8b542cfa 100644
--- a/src/ui/display-view.vala
+++ b/src/ui/display-view.vala
@@ -52,6 +52,8 @@ private class Games.DisplayView : Object, UiView {
        private ResumeFailedDialog resume_failed_dialog;
        private QuitDialog quit_dialog;
 
+       private SavestatesListState savestates_list_state;
+
        private long focus_out_timeout_id;
 
        public DisplayView (Gtk.Window window) {
@@ -59,14 +61,16 @@ private class Games.DisplayView : Object, UiView {
        }
 
        construct {
-               box = new DisplayBox ();
-               header_bar = new DisplayHeaderBar ();
+               savestates_list_state = new SavestatesListState ();
+               box = new DisplayBox (savestates_list_state);
+               header_bar = new DisplayHeaderBar (savestates_list_state);
 
                box.back.connect (on_display_back);
                header_bar.back.connect (on_display_back);
 
                settings = new Settings ("org.gnome.Games");
 
+               // Bind the is_fullscreen property between the header_bar and the box
                box_fullscreen_binding = bind_property ("is-fullscreen", box, "is-fullscreen",
                                                        BindingFlags.BIDIRECTIONAL);
                header_bar_fullscreen_binding = bind_property ("is-fullscreen", header_bar,
@@ -94,14 +98,15 @@ private class Games.DisplayView : Object, UiView {
 
                if ((event.keyval == Gdk.Key.f || event.keyval == Gdk.Key.F) &&
                    (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK &&
-                   header_bar.can_fullscreen) {
+                   header_bar.can_fullscreen && !savestates_list_state.is_revealed) {
                        is_fullscreen = !is_fullscreen;
                        settings.set_boolean ("fullscreen", is_fullscreen);
 
                        return true;
                }
 
-               if (event.keyval == Gdk.Key.F11 && header_bar.can_fullscreen) {
+               if (event.keyval == Gdk.Key.F11 && header_bar.can_fullscreen &&
+                   !savestates_list_state.is_revealed) {
                        is_fullscreen = !is_fullscreen;
                        settings.set_boolean ("fullscreen", is_fullscreen);
 
@@ -148,7 +153,7 @@ private class Games.DisplayView : Object, UiView {
 
                switch (button) {
                case EventCode.BTN_MODE:
-                       back ();
+                       on_display_back ();
 
                        return true;
                default:
@@ -165,6 +170,9 @@ private class Games.DisplayView : Object, UiView {
        }
 
        private void on_display_back () {
+               if (savestates_list_state.is_revealed)
+                       return;
+
                back ();
        }
 
@@ -349,6 +357,15 @@ private class Games.DisplayView : Object, UiView {
 
                box.runner.pause ();
 
+               if (!box.runner.can_support_savestates) {
+                       // Game does not and will not support savestates (e.g. Steam games)
+                       // => Progress cannot be saved so game can be quit safely
+                       box.runner.stop ();
+                       return true;
+               }
+
+               box.runner.capture_current_state_pixbuf ();
+
                if (box.runner.try_create_savestate (true) != null) {
                        // Progress saved => can quit game safely
                        box.runner.stop ();
@@ -394,6 +411,8 @@ private class Games.DisplayView : Object, UiView {
        }
 
        private void reset_display_page () {
+               header_bar.hide_secondary_menu_button ();
+
                header_bar.can_fullscreen = false;
                box.header_bar.can_fullscreen = false;
                header_bar.runner = null;
@@ -411,8 +430,10 @@ private class Games.DisplayView : Object, UiView {
                if (!can_update_pause ())
                        return;
 
-               if (window.is_active)
-                       box.runner.resume ();
+               if (window.is_active) {
+                       if (!savestates_list_state.is_revealed)
+                               box.runner.resume ();
+               }
                else if (with_delay)
                        focus_out_timeout_id = Timeout.add (FOCUS_OUT_DELAY_MILLISECONDS, 
on_focus_out_delay_elapsed);
                else


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