[gnome-games/wip/exalm/views: 3/4] application-window: Extract CollectionView and DisplayView



commit 4b9eb2d51b9a09d454724ad6d1b8fcf29cb6524c
Author: Alexander Mikhaylenko <exalm7659 gmail com>
Date:   Mon Feb 25 17:01:13 2019 +0500

    application-window: Extract CollectionView and DisplayView
    
    Introduce CollectionView and DisplayView containing the relevant code from
    ApplicationWindow. Make ApplicationWindow create them the two views.

 data/ui/application-window.ui  |  35 ---
 src/meson.build                |   2 +
 src/ui/application-window.vala | 554 +++++------------------------------------
 src/ui/collection-box.vala     |   2 +-
 src/ui/collection-view.vala    | 143 +++++++++++
 src/ui/display-view.vala       | 443 ++++++++++++++++++++++++++++++++
 6 files changed, 645 insertions(+), 534 deletions(-)
---
diff --git a/data/ui/application-window.ui b/data/ui/application-window.ui
index b134363a..98a05970 100644
--- a/data/ui/application-window.ui
+++ b/data/ui/application-window.ui
@@ -14,24 +14,6 @@
     <child>
       <object class="GtkStack" id="content_box">
         <property name="visible">True</property>
-        <child>
-          <object class="GamesCollectionBox" id="collection_box">
-            <property name="visible">True</property>
-            <signal name="game-activated" handler="on_game_activated"/>
-          </object>
-          <packing>
-            <property name="name">collection</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GamesDisplayBox" id="display_box">
-            <property name="visible">True</property>
-            <signal name="back" handler="on_display_back"/>
-          </object>
-          <packing>
-            <property name="name">display</property>
-          </packing>
-        </child>
       </object>
     </child>
     <child type="titlebar">
@@ -40,23 +22,6 @@
         <child>
           <object class="GtkStack" id="header_bar">
             <property name="visible">True</property>
-            <child>
-              <object class="GamesCollectionHeaderBar" id="collection_header_bar">
-                <property name="visible">True</property>
-              </object>
-              <packing>
-                <property name="name">collection</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GamesDisplayHeaderBar" id="display_header_bar">
-                <property name="visible">True</property>
-                <signal name="back" handler="on_display_back"/>
-              </object>
-              <packing>
-                <property name="name">display</property>
-              </packing>
-            </child>
           </object>
         </child>
       </object>
diff --git a/src/meson.build b/src/meson.build
index eea378ec..87aae655 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -131,10 +131,12 @@ vala_sources = [
   'ui/collection-box.vala',
   'ui/collection-icon-view.vala',
   'ui/collection-header-bar.vala',
+  'ui/collection-view.vala',
   'ui/developer-list-item.vala',
   'ui/developers-view.vala',
   'ui/display-box.vala',
   'ui/display-header-bar.vala',
+  'ui/display-view.vala',
   'ui/dummy-display.vala',
   'ui/empty-collection.vala',
   'ui/error-display.vala',
diff --git a/src/ui/application-window.vala b/src/ui/application-window.vala
index da427886..f2dc7952 100644
--- a/src/ui/application-window.vala
+++ b/src/ui/application-window.vala
@@ -3,45 +3,27 @@
 [GtkTemplate (ui = "/org/gnome/Games/ui/application-window.ui")]
 private class Games.ApplicationWindow : Gtk.ApplicationWindow {
        private const uint WINDOW_SIZE_UPDATE_DELAY_MILLISECONDS = 500;
-       private const uint FOCUS_OUT_DELAY_MILLISECONDS = 500;
 
-       private const string CONTRIBUTE_URI = "https://wiki.gnome.org/Apps/Games/Contribute";;
-
-       private UiState _ui_state;
-       public UiState ui_state {
-               get { return _ui_state; }
+       private UiView _current_view;
+       public UiView current_view {
+               get { return _current_view; }
                set {
-                       if (value == ui_state)
+                       if (value == current_view)
                                return;
 
-                       _ui_state = value;
-
-                       switch (ui_state) {
-                       case UiState.COLLECTION:
-                               content_box.visible_child = collection_box;
-                               header_bar.visible_child = collection_header_bar;
-
-                               is_fullscreen = false;
-
-                               if (display_box.runner != null) {
-                                       display_box.runner.stop ();
-                                       display_box.runner = null;
-                               }
+                       if (current_view != null)
+                               current_view.is_view_active = false;
 
-                               break;
-                       case UiState.DISPLAY:
-                               content_box.visible_child = display_box;
-                               header_bar.visible_child = display_header_bar;
+                       _current_view = value;
 
-                               search_mode = false;
+                       content_box.visible_child = current_view.content_box;
+                       header_bar.visible_child = current_view.title_bar;
 
-                               break;
-                       }
+                       if (current_view != null)
+                               current_view.is_view_active = true;
 
                        assert (application is Application);
-                       (application as Application).set_pause_loading (ui_state != UiState.COLLECTION);
-
-                       konami_code.reset ();
+                       (application as Application).set_pause_loading (current_view != collection_view);
                }
        }
 
@@ -49,7 +31,7 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
        public bool is_fullscreen {
                get { return _is_fullscreen; }
                set {
-                       _is_fullscreen = value && (ui_state == UiState.DISPLAY);
+                       _is_fullscreen = value && (current_view == display_view);
 
                        if (_is_fullscreen)
                                fullscreen ();
@@ -58,67 +40,32 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                }
        }
 
-       private bool _search_mode;
-       public bool search_mode {
-               get { return _search_mode; }
-               set { _search_mode = value && (ui_state == UiState.COLLECTION); }
-       }
-
-       public bool is_collection_empty { get; set; }
-
        public bool loading_notification { get; set; }
 
        [GtkChild]
        private Gtk.Stack content_box;
-       [GtkChild]
-       private CollectionBox collection_box;
-       [GtkChild]
-       private DisplayBox display_box;
-
        [GtkChild]
        private Gtk.Stack header_bar;
-       [GtkChild]
-       private CollectionHeaderBar collection_header_bar;
-       [GtkChild]
-       private DisplayHeaderBar display_header_bar;
+
+       private CollectionView collection_view;
+       private DisplayView display_view;
 
        private Settings settings;
 
-       private Binding box_search_binding;
-       private Binding box_fullscreen_binding;
-       private Binding box_empty_collection_binding;
-       private Binding header_bar_search_binding;
-       private Binding header_bar_fullscreen_binding;
-       private Binding header_bar_empty_collection_binding;
+       private Binding fullscreen_binding;
        private Binding loading_notification_binding;
 
-       private Cancellable run_game_cancellable;
-       private Cancellable quit_game_cancellable;
-
-       private ResumeDialog resume_dialog;
-       private ResumeFailedDialog resume_failed_dialog;
-       private QuitDialog quit_dialog;
-
        private long window_size_update_timeout;
-       private long focus_out_timeout_id;
 
        private uint inhibit_cookie;
        private Gtk.ApplicationInhibitFlags inhibit_flags;
 
-       private KonamiCode konami_code;
-
-       public ListModel collection {
-               construct {
-                       collection_box.collection = value;
-                       value.items_changed.connect (() => {
-                               is_collection_empty = value.get_n_items () == 0;
-                       });
-                       is_collection_empty = value.get_n_items () == 0;
-               }
-       }
+       public ListModel collection { get; construct; }
 
        public ApplicationWindow (Application application, ListModel collection) {
                Object (application: application, collection: collection);
+
+               current_view = collection_view;
        }
 
        construct {
@@ -136,30 +83,27 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                if (settings.get_boolean ("window-maximized"))
                        maximize ();
 
-               box_search_binding = bind_property ("search-mode", collection_box, "search-mode",
-                                                   BindingFlags.BIDIRECTIONAL);
-               loading_notification_binding = bind_property ("loading-notification", collection_box, 
"loading-notification",
-                                                             BindingFlags.DEFAULT);
-               header_bar_search_binding = bind_property ("search-mode", collection_header_bar, 
"search-mode",
-                                                          BindingFlags.BIDIRECTIONAL);
+               collection_view = new CollectionView (this, collection);
+               display_view = new DisplayView (this);
 
-               box_fullscreen_binding = bind_property ("is-fullscreen", display_box, "is-fullscreen",
-                                                       BindingFlags.BIDIRECTIONAL);
-               header_bar_fullscreen_binding = bind_property ("is-fullscreen", display_header_bar, 
"is-fullscreen",
-                                                              BindingFlags.BIDIRECTIONAL);
+               content_box.add (collection_view.content_box);
+               content_box.add (display_view.content_box);
+               header_bar.add (collection_view.title_bar);
+               header_bar.add (display_view.title_bar);
 
-               box_empty_collection_binding = bind_property ("is-collection-empty", collection_box, 
"is-collection-empty",
-                                                             BindingFlags.BIDIRECTIONAL);
-               header_bar_empty_collection_binding = bind_property ("is-collection-empty", 
collection_header_bar, "is-collection-empty",
-                                                                    BindingFlags.BIDIRECTIONAL);
+               collection_view.game_activated.connect (on_game_activated);
+               display_view.back.connect (on_display_back);
 
-               konami_code = new KonamiCode (this);
-               konami_code.code_performed.connect (on_konami_code_performed);
+               loading_notification_binding = bind_property ("loading-notification",
+                                                             collection_view,
+                                                             "loading-notification",
+                                                             BindingFlags.DEFAULT);
 
-               collection_header_bar.viewstack = collection_box.viewstack;
+               fullscreen_binding = bind_property ("is-fullscreen", display_view,
+                                                   "is-fullscreen",
+                                                   BindingFlags.BIDIRECTIONAL);
 
                window_size_update_timeout = -1;
-               focus_out_timeout_id = -1;
                inhibit_cookie = 0;
                inhibit_flags = 0;
 
@@ -172,25 +116,12 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
        }
 
        public void show_error (string error_message) {
-               collection_box.reveal_error_info_bar (error_message);
+               collection_view.show_error (error_message);
        }
 
        public void run_game (Game game) {
-               // If there is a game already running we have to quit it first
-               if (display_box.runner != null && !quit_game())
-                       return;
-
-               if (run_game_cancellable != null)
-                       run_game_cancellable.cancel ();
-
-               var cancellable = new Cancellable ();
-               run_game_cancellable = cancellable;
-
-               run_game_with_cancellable (game, cancellable);
-
-               // Only reset the cancellable if another one didn't replace it.
-               if (run_game_cancellable == cancellable)
-                       run_game_cancellable = null;
+               current_view = display_view;
+               display_view.run_game (game);
 
                inhibit (Gtk.ApplicationInhibitFlags.IDLE | Gtk.ApplicationInhibitFlags.LOGOUT);
        }
@@ -201,22 +132,7 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                if (!visible)
                        return true;
 
-               if (run_game_cancellable != null)
-                       run_game_cancellable.cancel ();
-
-               if (quit_game_cancellable != null)
-                       quit_game_cancellable.cancel ();
-
-               var cancellable = new Cancellable ();
-               quit_game_cancellable = cancellable;
-
-               var result = quit_game_with_cancellable (cancellable);
-
-               // Only reset the cancellable if another one didn't replace it.
-               if (quit_game_cancellable == cancellable)
-                       quit_game_cancellable = null;
-
-               return result;
+               return display_view.quit_game ();
        }
 
        public override void size_allocate (Gtk.Allocation allocation) {
@@ -245,21 +161,12 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                        return true;
                }
 
-               return handle_collection_key_event (event) || handle_display_key_event (event);
+               return current_view.on_key_pressed (event);
        }
 
        [GtkCallback]
        public bool on_button_pressed (Gdk.EventButton event) {
-               // Mouse button 8 is the navigation previous button
-               if (event.button == 8) {
-                       if (ui_state != UiState.DISPLAY)
-                               return false;
-
-                       on_display_back ();
-                       return true;
-               }
-
-               return false;
+               return current_view.on_button_pressed (event);
        }
 
        [GtkCallback]
@@ -268,13 +175,14 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                settings.set_boolean ("window-maximized", is_maximized);
 
                is_fullscreen = (bool) (event.new_window_state & Gdk.WindowState.FULLSCREEN);
-               update_pause (false);
+               if (current_view == display_view)
+                       display_view.update_pause (false);
 
                if (!(bool) (event.changed_mask & Gdk.WindowState.FOCUSED))
                        return false;
 
                var focused = (bool) (event.new_window_state & Gdk.WindowState.FOCUSED);
-               var playing = (ui_state == UiState.DISPLAY);
+               var playing = (current_view == display_view);
 
                if (focused && playing)
                        inhibit (Gtk.ApplicationInhibitFlags.IDLE);
@@ -286,260 +194,38 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
        }
 
        public bool gamepad_button_press_event (Manette.Event event) {
-               switch (ui_state) {
-               case UiState.COLLECTION:
-                       return is_active && collection_box.gamepad_button_press_event (event);
-               case UiState.DISPLAY:
-                       if (resume_dialog != null)
-                               return resume_dialog.is_active && resume_dialog.gamepad_button_press_event 
(event);
-
-                       if (resume_failed_dialog != null)
-                               return resume_failed_dialog.is_active && 
resume_failed_dialog.gamepad_button_press_event (event);
-
-                       if (quit_dialog != null)
-                               return quit_dialog.is_active && quit_dialog.gamepad_button_press_event 
(event);
-
-                       if (!is_active || !get_mapped ())
-                               return false;
-
-                       uint16 button;
-                       if (!event.get_button (out button))
-                               return false;
-
-                       switch (button) {
-                       case EventCode.BTN_MODE:
-                               ui_state = UiState.COLLECTION;
-
-                               return true;
-                       default:
-                               return false;
-                       }
-               default:
-                       return false;
-               }
+               return current_view.gamepad_button_press_event (event);
        }
 
        public bool gamepad_button_release_event (Manette.Event event) {
-               switch (ui_state) {
-               case UiState.COLLECTION:
-                       return is_active && collection_box.gamepad_button_release_event (event);
-               default:
-                       return false;
-               }
+               if (current_view == collection_view)
+                       return collection_view.gamepad_button_release_event (event);
+
+               return false;
        }
 
        public bool gamepad_absolute_axis_event (Manette.Event event) {
-               switch (ui_state) {
-               case UiState.COLLECTION:
-                       return is_active && collection_box.gamepad_absolute_axis_event (event);
-               default:
-                       return false;
-               }
+               if (current_view == collection_view)
+                       return collection_view.gamepad_absolute_axis_event (event);
+
+               return false;
        }
 
-       [GtkCallback]
        private void on_game_activated (Game game) {
                run_game (game);
        }
 
-       [GtkCallback]
        private void on_display_back () {
                if (quit_game ())
-                       ui_state = UiState.COLLECTION;
+                       current_view = collection_view;
 
                uninhibit (Gtk.ApplicationInhibitFlags.IDLE | Gtk.ApplicationInhibitFlags.LOGOUT);
        }
 
-       private void run_game_with_cancellable (Game game, Cancellable cancellable) {
-               display_header_bar.game_title = game.name;
-               display_box.header_bar.game_title = game.name;
-               ui_state = UiState.DISPLAY;
-
-               // Reset the UI parts depending on the runner to avoid an
-               // inconsistent state is case we couldn't retrieve it.
-               reset_display_page ();
-
-               var runner = try_get_runner (game);
-               if (runner == null)
-                       return;
-
-               display_header_bar.can_fullscreen = runner.can_fullscreen;
-               display_box.header_bar.can_fullscreen = runner.can_fullscreen;
-               display_header_bar.runner = runner;
-               display_box.runner = runner;
-               display_header_bar.media_set = runner.media_set;
-               display_box.header_bar.media_set = runner.media_set;
-
-               is_fullscreen = settings.get_boolean ("fullscreen") && runner.can_fullscreen;
-
-               if (!runner.can_resume) {
-                       try_run_with_cancellable (runner, false, cancellable);
-                       return;
-               }
-
-               var response = Gtk.ResponseType.NONE;
-               if (runner.can_resume)
-                       response = prompt_resume_with_cancellable (cancellable);
-
-               if (response != Gtk.ResponseType.NONE) {
-                       var resume = (response == Gtk.ResponseType.ACCEPT);
-
-                       if (!try_run_with_cancellable (runner, resume, cancellable))
-                               prompt_resume_fail_with_cancellable (runner, cancellable);
-               }
-       }
-
-       private Runner? try_get_runner (Game game) {
-               try {
-                       var runner = game.get_runner ();
-                       string error_message;
-                       if (runner.check_is_valid (out error_message))
-                               return runner;
-
-                       reset_display_page ();
-                       display_box.display_running_game_failed (game, error_message);
-
-                       return null;
-               }
-               catch (Error e) {
-                       warning (e.message);
-                       reset_display_page ();
-                       display_box.display_running_game_failed (game, _("An unexpected error occurred."));
-
-                       return null;
-               }
-       }
-
-       private Gtk.ResponseType prompt_resume_with_cancellable (Cancellable cancellable) {
-               if (resume_dialog != null)
-                       return Gtk.ResponseType.NONE;
-
-               resume_dialog = new ResumeDialog ();
-               resume_dialog.transient_for = this;
-
-               cancellable.cancelled.connect (() => {
-                       resume_dialog.destroy ();
-                       resume_dialog = null;
-               });
-
-               var response = resume_dialog.run ();
-
-               // The null check is necessary because the dialog could already
-               // be canceled by this point
-               if (resume_dialog != null) {
-                       resume_dialog.destroy ();
-                       resume_dialog = null;
-               }
-
-               return (Gtk.ResponseType) response;
-       }
-
-       private bool try_run_with_cancellable (Runner runner, bool resume, Cancellable cancellable) {
-               try {
-                       if (resume)
-                               display_box.runner.resume ();
-                       else
-                               runner.start ();
-
-                       return true;
-               }
-               catch (Error e) {
-                       warning (e.message);
-
-                       return false;
-               }
-       }
-
-       private void prompt_resume_fail_with_cancellable (Runner runner, Cancellable cancellable) {
-               if (resume_failed_dialog != null)
-                       return;
-
-               resume_failed_dialog = new ResumeFailedDialog ();
-               resume_failed_dialog.transient_for = this;
-
-               cancellable.cancelled.connect (() => {
-                       resume_failed_dialog.destroy ();
-                       resume_failed_dialog = null;
-               });
-
-               var response = resume_failed_dialog.run ();
-               resume_failed_dialog.destroy ();
-               resume_failed_dialog = null;
-
-               if (cancellable.is_cancelled ())
-                       response = Gtk.ResponseType.CANCEL;
-
-               if (response == Gtk.ResponseType.CANCEL) {
-                       display_box.runner = null;
-                       ui_state = UiState.COLLECTION;
-
-                       return;
-               }
-
-               try {
-                       runner.start ();
-               }
-               catch (Error e) {
-                       warning (e.message);
-               }
-       }
-
-       public bool quit_game_with_cancellable (Cancellable cancellable) {
-               if (display_box.runner == null)
-                       return true;
-
-               display_box.runner.pause ();
-
-               if (display_box.runner.can_quit_safely) {
-                       display_box.runner.stop();
-
-                       return true;
-               }
-
-               if (quit_dialog != null)
-                       return false;
-
-               quit_dialog = new QuitDialog ();
-               quit_dialog.transient_for = this;
-
-               cancellable.cancelled.connect (() => {
-                       quit_dialog.destroy ();
-                       quit_dialog = null;
-               });
-
-               var response = quit_dialog.run ();
-
-               // The null check is necessary because the dialog could already
-               // be canceled by this point
-               if (quit_dialog != null) {
-                       quit_dialog.destroy ();
-                       quit_dialog = null;
-               }
-
-               if (cancellable.is_cancelled ())
-                       return cancel_quitting_game ();
-
-               if (response == Gtk.ResponseType.ACCEPT)
-                       return true;
-
-               return cancel_quitting_game ();
-       }
-
-       private bool cancel_quitting_game () {
-               if (display_box.runner != null)
-                       try {
-                               display_box.runner.resume ();
-                       }
-                       catch (Error e) {
-                               warning (e.message);
-                       }
-
-               return false;
-       }
-
        [GtkCallback]
        private void on_active_changed () {
-               update_pause (true);
+               if (current_view == display_view)
+                       display_view.update_pause (true);
        }
 
        private Gdk.Rectangle? get_geometry () {
@@ -579,113 +265,6 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                return false;
        }
 
-       private void update_pause (bool with_delay) {
-               if (focus_out_timeout_id != -1) {
-                       Source.remove ((uint) focus_out_timeout_id);
-                       focus_out_timeout_id = -1;
-               }
-
-               if (!can_update_pause ())
-                       return;
-
-               if (is_active)
-                       try {
-                               display_box.runner.resume ();
-                       }
-                       catch (Error e) {
-                               warning (e.message);
-                       }
-               else if (with_delay)
-                       focus_out_timeout_id = Timeout.add (FOCUS_OUT_DELAY_MILLISECONDS, 
on_focus_out_delay_elapsed);
-               else
-                       display_box.runner.pause ();
-       }
-
-       private bool on_focus_out_delay_elapsed () {
-               focus_out_timeout_id = -1;
-
-               if (!can_update_pause ())
-                       return false;
-
-               if (!is_active)
-                       display_box.runner.pause ();
-
-               return false;
-       }
-
-       private bool can_update_pause () {
-               if (ui_state != UiState.DISPLAY)
-                       return false;
-
-               if (display_box.runner == null)
-                       return false;
-
-               if (run_game_cancellable != null)
-                       return false;
-
-               if (quit_game_cancellable != null)
-                       return false;
-
-               return true;
-       }
-
-       private bool handle_collection_key_event (Gdk.EventKey event) {
-               if (ui_state != UiState.COLLECTION)
-                       return false;
-
-               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
-
-               if ((event.keyval == Gdk.Key.f || event.keyval == Gdk.Key.F) &&
-                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK) {
-                       if (!search_mode)
-                               search_mode = true;
-
-                       return true;
-               }
-
-               return collection_box.search_bar_handle_event (event);
-       }
-
-       private bool handle_display_key_event (Gdk.EventKey event) {
-               if (ui_state != UiState.DISPLAY)
-                       return false;
-
-               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
-
-               if ((event.keyval == Gdk.Key.f || event.keyval == Gdk.Key.F) &&
-                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK &&
-                   display_header_bar.can_fullscreen) {
-                       is_fullscreen = !is_fullscreen;
-                       settings.set_boolean ("fullscreen", is_fullscreen);
-
-                       return true;
-               }
-
-               if (event.keyval == Gdk.Key.F11 && display_header_bar.can_fullscreen) {
-                       is_fullscreen = !is_fullscreen;
-                       settings.set_boolean ("fullscreen", is_fullscreen);
-
-                       return true;
-               }
-
-               if (event.keyval == Gdk.Key.Escape && display_header_bar.can_fullscreen) {
-                       is_fullscreen = false;
-                       settings.set_boolean ("fullscreen", false);
-
-                       return true;
-               }
-
-               if (((event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) &&
-                   (((get_direction () == Gtk.TextDirection.LTR) && event.keyval == Gdk.Key.Left) ||
-                    ((get_direction () == Gtk.TextDirection.RTL) && event.keyval == Gdk.Key.Right))) {
-                       on_display_back ();
-
-                       return true;
-               }
-
-               return false;
-       }
-
        private void inhibit (Gtk.ApplicationInhibitFlags flags) {
                if ((inhibit_flags & flags) == flags)
                        return;
@@ -716,25 +295,4 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                inhibit_cookie = new_cookie;
                inhibit_flags = new_flags;
        }
-
-       private void reset_display_page () {
-               display_header_bar.can_fullscreen = false;
-               display_box.header_bar.can_fullscreen = false;
-               display_header_bar.runner = null;
-               display_box.runner = null;
-               display_header_bar.media_set = null;
-               display_box.header_bar.media_set = null;
-       }
-
-       private void on_konami_code_performed () {
-               if (ui_state != UiState.COLLECTION)
-                       return;
-
-               try {
-                       Gtk.show_uri_on_window (this, CONTRIBUTE_URI, Gtk.get_current_event_time ());
-               }
-               catch (Error e) {
-                       critical (e.message);
-               }
-       }
 }
diff --git a/src/ui/collection-box.vala b/src/ui/collection-box.vala
index 0d91bef3..181d7386 100644
--- a/src/ui/collection-box.vala
+++ b/src/ui/collection-box.vala
@@ -66,7 +66,7 @@ private class Games.CollectionBox : Gtk.Box {
                                                              BindingFlags.DEFAULT);
        }
 
-       public void reveal_error_info_bar (string error_message) {
+       public void show_error (string error_message) {
                error_info_bar.message = error_message;
                error_info_bar.revealed = true;
        }
diff --git a/src/ui/collection-view.vala b/src/ui/collection-view.vala
new file mode 100644
index 00000000..95c8cb5a
--- /dev/null
+++ b/src/ui/collection-view.vala
@@ -0,0 +1,143 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.CollectionView : Object, UiView {
+       private const string CONTRIBUTE_URI = "https://wiki.gnome.org/Apps/Games/Contribute";;
+
+       public signal void game_activated (Game game);
+
+       private CollectionBox box;
+       private CollectionHeaderBar header_bar;
+
+       public Gtk.Widget content_box {
+               get { return box; }
+       }
+
+       public Gtk.Widget title_bar {
+               get { return header_bar; }
+       }
+
+       private bool _is_view_active;
+       public bool is_view_active {
+               get { return _is_view_active; }
+               set {
+                       if (is_view_active == value)
+                               return;
+
+                       _is_view_active = value;
+
+                       if (!is_view_active)
+                               search_mode = false;
+
+                       konami_code.reset ();
+               }
+       }
+
+       public Gtk.Window window { get; construct set; }
+
+       private ListModel _collection;
+       public ListModel collection {
+               get { return _collection; }
+               construct set {
+                       _collection = value;
+
+                       collection.items_changed.connect (() => {
+                               is_collection_empty = collection.get_n_items () == 0;
+                       });
+                       is_collection_empty = collection.get_n_items () == 0;
+               }
+       }
+
+       public bool loading_notification { get; set; }
+       public bool search_mode { get; set; }
+       public bool is_collection_empty { get; set; }
+
+       private Binding loading_notification_binding;
+       private Binding box_search_binding;
+       private Binding box_empty_collection_binding;
+       private Binding header_bar_search_binding;
+       private Binding header_bar_empty_collection_binding;
+
+       private KonamiCode konami_code;
+
+       construct {
+               box = new CollectionBox (collection);
+               header_bar = new CollectionHeaderBar ();
+               box.game_activated.connect (game => {
+                       game_activated (game);
+               });
+
+               header_bar.viewstack = box.viewstack;
+               is_collection_empty = true;
+
+               loading_notification_binding = bind_property ("loading-notification", box,
+                                                             "loading-notification",
+                                                             BindingFlags.DEFAULT);
+
+               box_search_binding = bind_property ("search-mode", box, "search-mode",
+                                                   BindingFlags.BIDIRECTIONAL);
+               header_bar_search_binding = bind_property ("search-mode", header_bar,
+                                                          "search-mode",
+                                                          BindingFlags.BIDIRECTIONAL);
+
+               box_empty_collection_binding = bind_property ("is-collection-empty", box,
+                                                             "is-collection-empty",
+                                                             BindingFlags.BIDIRECTIONAL);
+               header_bar_empty_collection_binding = bind_property ("is-collection-empty",
+                                                                    header_bar,
+                                                                    "is-collection-empty",
+                                                                    BindingFlags.BIDIRECTIONAL);
+
+               konami_code = new KonamiCode (window);
+               konami_code.code_performed.connect (on_konami_code_performed);
+       }
+
+       public CollectionView (Gtk.Window window, ListModel collection) {
+               Object (window: window, collection: collection);
+       }
+
+       public void show_error (string error_message) {
+               box.show_error (error_message);
+       }
+
+       public bool on_button_pressed (Gdk.EventButton event) {
+               return false;
+       }
+
+       public bool on_key_pressed (Gdk.EventKey event) {
+               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
+
+               if ((event.keyval == Gdk.Key.f || event.keyval == Gdk.Key.F) &&
+                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK) {
+                       if (!search_mode)
+                               search_mode = true;
+
+                       return true;
+               }
+
+               return box.search_bar_handle_event (event);
+       }
+
+       public bool gamepad_button_press_event (Manette.Event event) {
+               return window.is_active && box.gamepad_button_press_event (event);
+       }
+
+       public bool gamepad_button_release_event (Manette.Event event) {
+               return window.is_active && box.gamepad_button_release_event (event);
+       }
+
+       public bool gamepad_absolute_axis_event (Manette.Event event) {
+               return window.is_active && box.gamepad_absolute_axis_event (event);
+       }
+
+       private void on_konami_code_performed () {
+               if (!is_view_active)
+                       return;
+
+               try {
+                       Gtk.show_uri_on_window (window, CONTRIBUTE_URI, Gtk.get_current_event_time ());
+               }
+               catch (Error e) {
+                       critical (e.message);
+               }
+       }
+}
diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala
new file mode 100644
index 00000000..ad008a4e
--- /dev/null
+++ b/src/ui/display-view.vala
@@ -0,0 +1,443 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.DisplayView : Object, UiView {
+       private const uint FOCUS_OUT_DELAY_MILLISECONDS = 500;
+
+       public signal void back ();
+
+       private DisplayBox box;
+       private DisplayHeaderBar header_bar;
+
+       public Gtk.Widget content_box {
+               get { return box; }
+       }
+
+       public Gtk.Widget title_bar {
+               get { return header_bar; }
+       }
+
+       private bool _is_view_active;
+       public bool is_view_active {
+               get { return _is_view_active; }
+               set {
+                       if (is_view_active == value)
+                               return;
+
+                       _is_view_active = value;
+
+                       if (!is_view_active) {
+                               is_fullscreen = false;
+
+                               if (box.runner != null) {
+                                       box.runner.stop ();
+                                       box.runner = null;
+                               }
+                       }
+               }
+       }
+
+       public Gtk.Window window { get; construct set; }
+
+       public bool is_fullscreen { get; set; }
+
+       private Settings settings;
+
+       private Binding box_fullscreen_binding;
+       private Binding header_bar_fullscreen_binding;
+
+       private Cancellable run_game_cancellable;
+       private Cancellable quit_game_cancellable;
+
+       private ResumeDialog resume_dialog;
+       private ResumeFailedDialog resume_failed_dialog;
+       private QuitDialog quit_dialog;
+
+       private long focus_out_timeout_id;
+
+       public DisplayView (Gtk.Window window) {
+               Object (window: window);
+       }
+
+       construct {
+               box = new DisplayBox ();
+               header_bar = new DisplayHeaderBar ();
+
+               box.back.connect (on_display_back);
+               header_bar.back.connect (on_display_back);
+
+               settings = new Settings ("org.gnome.Games");
+
+               box_fullscreen_binding = bind_property ("is-fullscreen", box, "is-fullscreen",
+                                                       BindingFlags.BIDIRECTIONAL);
+               header_bar_fullscreen_binding = bind_property ("is-fullscreen", header_bar,
+                                                              "is-fullscreen",
+                                                              BindingFlags.BIDIRECTIONAL);
+
+               focus_out_timeout_id = -1;
+       }
+
+       public bool on_button_pressed (Gdk.EventButton event) {
+               // Mouse button 8 is the navigation previous button
+               if (event.button == 8) {
+                       back ();
+                       return true;
+               }
+
+               return false;
+       }
+
+       public bool on_key_pressed (Gdk.EventKey event) {
+               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
+
+               if ((event.keyval == Gdk.Key.f || event.keyval == Gdk.Key.F) &&
+                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK &&
+                   header_bar.can_fullscreen) {
+                       is_fullscreen = !is_fullscreen;
+                       settings.set_boolean ("fullscreen", is_fullscreen);
+
+                       return true;
+               }
+
+               if (event.keyval == Gdk.Key.F11 && header_bar.can_fullscreen) {
+                       is_fullscreen = !is_fullscreen;
+                       settings.set_boolean ("fullscreen", is_fullscreen);
+
+                       return true;
+               }
+
+               if (event.keyval == Gdk.Key.Escape && header_bar.can_fullscreen) {
+                       is_fullscreen = false;
+                       settings.set_boolean ("fullscreen", false);
+
+                       return true;
+               }
+
+               if (((event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) &&
+                   (((window.get_direction () == Gtk.TextDirection.LTR) && event.keyval == Gdk.Key.Left) ||
+                    ((window.get_direction () == Gtk.TextDirection.RTL) && event.keyval == Gdk.Key.Right))) {
+                       on_display_back ();
+
+                       return true;
+               }
+
+               return false;
+       }
+
+       public bool gamepad_button_press_event (Manette.Event event) {
+               if (resume_dialog != null)
+                       return resume_dialog.is_active && resume_dialog.gamepad_button_press_event (event);
+
+               if (resume_failed_dialog != null)
+                       return resume_failed_dialog.is_active && 
resume_failed_dialog.gamepad_button_press_event (event);
+
+               if (quit_dialog != null)
+                       return quit_dialog.is_active && quit_dialog.gamepad_button_press_event (event);
+
+               if (!window.is_active || !window.get_mapped ())
+                       return false;
+
+               uint16 button;
+               if (!event.get_button (out button))
+                       return false;
+
+               switch (button) {
+               case EventCode.BTN_MODE:
+                       back ();
+
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       public bool gamepad_button_release_event (Manette.Event event) {
+               return false;
+       }
+
+       public bool gamepad_absolute_axis_event (Manette.Event event) {
+               return false;
+       }
+
+       private void on_display_back () {
+               back ();
+       }
+
+       public void run_game (Game game) {
+               // If there is a game already running we have to quit it first
+               if (box.runner != null && !quit_game ())
+                       return;
+
+               if (run_game_cancellable != null)
+                       run_game_cancellable.cancel ();
+
+               var cancellable = new Cancellable ();
+               run_game_cancellable = cancellable;
+
+               run_game_with_cancellable (game, cancellable);
+
+               // Only reset the cancellable if another one didn't replace it.
+               if (run_game_cancellable == cancellable)
+                       run_game_cancellable = null;
+       }
+
+       private void run_game_with_cancellable (Game game, Cancellable cancellable) {
+               header_bar.game_title = game.name;
+               box.header_bar.game_title = game.name;
+
+               // Reset the UI parts depending on the runner to avoid an
+               // inconsistent state is case we couldn't retrieve it.
+               reset_display_page ();
+
+               var runner = try_get_runner (game);
+               if (runner == null)
+                       return;
+
+               header_bar.can_fullscreen = runner.can_fullscreen;
+               box.header_bar.can_fullscreen = runner.can_fullscreen;
+               header_bar.runner = runner;
+               box.runner = runner;
+               header_bar.media_set = runner.media_set;
+               box.header_bar.media_set = runner.media_set;
+
+               is_fullscreen = settings.get_boolean ("fullscreen") && runner.can_fullscreen;
+
+               if (!runner.can_resume) {
+                       try_run_with_cancellable (runner, false, cancellable);
+                       return;
+               }
+
+               var response = Gtk.ResponseType.NONE;
+               if (runner.can_resume)
+                       response = prompt_resume_with_cancellable (cancellable);
+
+               if (response != Gtk.ResponseType.NONE) {
+                       var resume = (response == Gtk.ResponseType.ACCEPT);
+
+                       if (!try_run_with_cancellable (runner, resume, cancellable))
+                               prompt_resume_fail_with_cancellable (runner, cancellable);
+               }
+       }
+
+       private Runner? try_get_runner (Game game) {
+               try {
+                       var runner = game.get_runner ();
+                       string error_message;
+                       if (runner.check_is_valid (out error_message))
+                               return runner;
+
+                       reset_display_page ();
+                       box.display_running_game_failed (game, error_message);
+
+                       return null;
+               }
+               catch (Error e) {
+                       warning (e.message);
+                       reset_display_page ();
+                       box.display_running_game_failed (game, _("An unexpected error occurred."));
+
+                       return null;
+               }
+       }
+
+       private Gtk.ResponseType prompt_resume_with_cancellable (Cancellable cancellable) {
+               if (resume_dialog != null)
+                       return Gtk.ResponseType.NONE;
+
+               resume_dialog = new ResumeDialog ();
+               resume_dialog.transient_for = window;
+
+               cancellable.cancelled.connect (() => {
+                       resume_dialog.destroy ();
+                       resume_dialog = null;
+               });
+
+               var response = resume_dialog.run ();
+
+               // The null check is necessary because the dialog could already
+               // be canceled by this point
+               if (resume_dialog != null) {
+                       resume_dialog.destroy ();
+                       resume_dialog = null;
+               }
+
+               return (Gtk.ResponseType) response;
+       }
+
+       private bool try_run_with_cancellable (Runner runner, bool resume, Cancellable cancellable) {
+               try {
+                       if (resume)
+                               box.runner.resume ();
+                       else
+                               runner.start ();
+
+                       return true;
+               }
+               catch (Error e) {
+                       warning (e.message);
+
+                       return false;
+               }
+       }
+
+       private void prompt_resume_fail_with_cancellable (Runner runner, Cancellable cancellable) {
+               if (resume_failed_dialog != null)
+                       return;
+
+               resume_failed_dialog = new ResumeFailedDialog ();
+               resume_failed_dialog.transient_for = window;
+
+               cancellable.cancelled.connect (() => {
+                       resume_failed_dialog.destroy ();
+                       resume_failed_dialog = null;
+               });
+
+               var response = resume_failed_dialog.run ();
+               resume_failed_dialog.destroy ();
+               resume_failed_dialog = null;
+
+               if (cancellable.is_cancelled ())
+                       response = Gtk.ResponseType.CANCEL;
+
+               if (response == Gtk.ResponseType.CANCEL) {
+                       box.runner = null;
+                       back ();
+
+                       return;
+               }
+
+               try {
+                       runner.start ();
+               }
+               catch (Error e) {
+                       warning (e.message);
+               }
+       }
+
+       public bool quit_game () {
+               if (run_game_cancellable != null)
+                       run_game_cancellable.cancel ();
+
+               if (quit_game_cancellable != null)
+                       quit_game_cancellable.cancel ();
+
+               var cancellable = new Cancellable ();
+               quit_game_cancellable = cancellable;
+
+               var result = quit_game_with_cancellable (cancellable);
+
+               // Only reset the cancellable if another one didn't replace it.
+               if (quit_game_cancellable == cancellable)
+                       quit_game_cancellable = null;
+
+               return result;
+       }
+
+       public bool quit_game_with_cancellable (Cancellable cancellable) {
+               if (box.runner == null)
+                       return true;
+
+               box.runner.pause ();
+
+               if (box.runner.can_quit_safely) {
+                       box.runner.stop ();
+
+                       return true;
+               }
+
+               if (quit_dialog != null)
+                       return false;
+
+               quit_dialog = new QuitDialog ();
+               quit_dialog.transient_for = window;
+
+               cancellable.cancelled.connect (() => {
+                       quit_dialog.destroy ();
+                       quit_dialog = null;
+               });
+
+               var response = quit_dialog.run ();
+
+               // The null check is necessary because the dialog could already
+               // be canceled by this point
+               if (quit_dialog != null) {
+                       quit_dialog.destroy ();
+                       quit_dialog = null;
+               }
+
+               if (cancellable.is_cancelled ())
+                       return cancel_quitting_game ();
+
+               if (response == Gtk.ResponseType.ACCEPT)
+                       return true;
+
+               return cancel_quitting_game ();
+       }
+
+       private bool cancel_quitting_game () {
+               if (box.runner != null)
+                       try {
+                               box.runner.resume ();
+                       }
+                       catch (Error e) {
+                               warning (e.message);
+                       }
+
+               return false;
+       }
+
+       private void reset_display_page () {
+               header_bar.can_fullscreen = false;
+               box.header_bar.can_fullscreen = false;
+               header_bar.runner = null;
+               box.runner = null;
+               header_bar.media_set = null;
+               box.header_bar.media_set = null;
+       }
+
+       public void update_pause (bool with_delay) {
+               if (focus_out_timeout_id != -1) {
+                       Source.remove ((uint) focus_out_timeout_id);
+                       focus_out_timeout_id = -1;
+               }
+
+               if (!can_update_pause ())
+                       return;
+
+               if (window.is_active)
+                       try {
+                               box.runner.resume ();
+                       }
+                       catch (Error e) {
+                               warning (e.message);
+                       }
+               else if (with_delay)
+                       focus_out_timeout_id = Timeout.add (FOCUS_OUT_DELAY_MILLISECONDS, 
on_focus_out_delay_elapsed);
+               else
+                       box.runner.pause ();
+       }
+
+       private bool on_focus_out_delay_elapsed () {
+               focus_out_timeout_id = -1;
+
+               if (!can_update_pause ())
+                       return false;
+
+               if (!window.is_active)
+                       box.runner.pause ();
+
+               return false;
+       }
+
+       private bool can_update_pause () {
+               if (box.runner == null)
+                       return false;
+
+               if (run_game_cancellable != null)
+                       return false;
+
+               if (quit_game_cancellable != null)
+                       return false;
+
+               return true;
+       }
+}



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