[gnome-games/wip/exalm/tnum: 7/24] ui: Introduce CollectionActionWindow




commit 72849f9161fce357fb1e72d332d21d4c5ba65335
Author: Neville <nevilleantony98 gmail com>
Date:   Thu Aug 6 23:52:46 2020 +0530

    ui: Introduce CollectionActionWindow
    
    This will be used to present actions on collections such as:
      - Add games to collections
      - Create a collection
    
    a single filter. This makes hiding/showing the "+" row easier.
    
    Also add pill-button style class

 data/gtk-style.css                   |   6 +
 data/org.gnome.Games.gresource.xml   |   1 +
 data/ui/collection-action-window.ui  | 316 +++++++++++++++++++++++++++++++++++
 src/meson.build                      |   1 +
 src/ui/collection-action-window.vala | 259 ++++++++++++++++++++++++++++
 5 files changed, 583 insertions(+)
---
diff --git a/data/gtk-style.css b/data/gtk-style.css
index 7d6bc0fe8..fb6f932f5 100644
--- a/data/gtk-style.css
+++ b/data/gtk-style.css
@@ -35,6 +35,12 @@
        border-right: none;
 }
 
+.pill-button {
+       border-radius: 9999px;
+       -gtk-outline-radius: 9999px;
+       padding: 6px 32px;
+}
+
 gamescollectionsmainpage grid {
        background: mix(@theme_base_color, @theme_bg_color, 0.5);
        min-width: 116px;
diff --git a/data/org.gnome.Games.gresource.xml b/data/org.gnome.Games.gresource.xml
index 6e5cd1e57..0f6a64217 100644
--- a/data/org.gnome.Games.gresource.xml
+++ b/data/org.gnome.Games.gresource.xml
@@ -12,6 +12,7 @@
     <file preprocess="xml-stripblanks">gesture/stick-symbolic.svg</file>
     <file preprocess="xml-stripblanks">ui/application-window.ui</file>
     <file preprocess="xml-stripblanks">ui/checkmark-item.ui</file>
+    <file preprocess="xml-stripblanks">ui/collection-action-window.ui</file>
     <file preprocess="xml-stripblanks">ui/collection-empty.ui</file>
     <file preprocess="xml-stripblanks">ui/collection-icon-view.ui</file>
     <file preprocess="xml-stripblanks">ui/collection-list-item.ui</file>
diff --git a/data/ui/collection-action-window.ui b/data/ui/collection-action-window.ui
new file mode 100644
index 000000000..36d07f1b9
--- /dev/null
+++ b/data/ui/collection-action-window.ui
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.24"/>
+  <template class="GamesCollectionActionWindow" parent="HdyWindow">
+    <property name="title" translatable="yes">Add to Collection</property>
+    <property name="default-height">550</property>
+    <property name="default-width">400</property>
+    <property name="window-position">center-on-parent</property>
+    <signal name="key-press-event" after="yes" handler="on_key_pressed"/>
+    <child>
+      <object class="HdyDeck" id="deck">
+        <property name="visible">True</property>
+        <property name="can-swipe-back" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only" bind-flags="invert-boolean|sync-create"/>
+        <signal name="notify::visible-child" handler="on_visible_child_changed"/>
+        <child>
+          <object class="GtkBox" id="add_to_collection_page">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="HdyHeaderBar">
+                <property name="visible">True</property>
+                <property name="title" translatable="yes">Add to Collection</property>
+                <property name="show-close-button">False</property>
+                <child>
+                  <object class="GtkButton" id="cancel_button">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">_Cancel</property>
+                    <property name="use_underline">True</property>
+                    <property name="action-name">collection-action.go-back</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="add_button">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">_Add</property>
+                    <property name="use_underline">True</property>
+                    <property name="sensitive" bind-source="GamesCollectionActionWindow" 
bind-property="is-user-collections-empty" bind-flags="invert-boolean|sync-create"/>
+                    <property name="action-name">collection-action.add-to-collection</property>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="pack_type">end</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkToggleButton">
+                    <property name="visible">True</property>
+                    <property name="valign">center</property>
+                    <property name="active" bind-source="GamesCollectionActionWindow" 
bind-property="is-search-mode" bind-flags="bidirectional"/>
+                    <property name="sensitive" bind-source="GamesCollectionActionWindow" 
bind-property="is-user-collections-empty" bind-flags="invert-boolean|sync-create"/>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="icon-name">edit-find-symbolic</property>
+                      </object>
+                    </child>
+                    <child internal-child="accessible">
+                      <object class="AtkObject">
+                        <property name="accessible-name" translatable="yes">Search</property>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="image-button"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="pack-type">end</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="HdySearchBar" id="search_bar">
+                <property name="visible">True</property>
+                <property name="search-mode-enabled" bind-source="GamesCollectionActionWindow" 
bind-property="is-search-mode" bind-flags="sync-create|bidirectional"/>
+                <child>
+                  <object class="HdyClamp">
+                    <property name="visible">True</property>
+                    <property name="maximum-size">400</property>
+                    <child>
+                      <object class="GtkSearchEntry" id="search_entry">
+                        <property name="visible">True</property>
+                        <signal name="search-changed" handler="on_search_text_notify"/>
+                        <signal name="activate" handler="create_collection"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStack" id="user_collections_page_stack">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkScrolledWindow" id="list_page">
+                    <property name="visible">True</property>
+                    <property name="hscrollbar-policy">never</property>
+                    <child>
+                      <object class="HdyClamp">
+                        <property name="visible">True</property>
+                        <property name="maximum-size">400</property>
+                        <property name="margin">18</property>
+                        <child>
+                          <object class="GtkListBox" id="list_box">
+                            <property name="visible">True</property>
+                            <property name="activate-on-single-click">True</property>
+                            <property name="selection-mode">none</property>
+                            <signal name="row-activated" handler="on_listbox_row_activated"/>
+                            <style>
+                              <class name="content"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="empty_page">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="vexpand">True</property>
+                        <property name="valign">center</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="pixel-size">96</property>
+                            <property name="icon-name">folder-symbolic</property>
+                            <property name="margin">18</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">No collections found</property>
+                            <property name="justify">center</property>
+                            <property name="wrap">True</property>
+                            <property name="margin">6</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                              <attribute name="scale" value="2"/>
+                            </attributes>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Add a new collection to add games to 
it.</property>
+                            <property name="justify">center</property>
+                            <property name="wrap">True</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Create Collection</property>
+                        <property name="halign">center</property>
+                        <property name="margin">24</property>
+                        <property name="action-name">collection-action.new-collection</property>
+                        <child internal-child="accessible">
+                          <object class="AtkObject">
+                            <property name="accessible-name" translatable="yes">Create a 
collection</property>
+                          </object>
+                        </child>
+                        <style>
+                          <class name="suggested-action"/>
+                          <class name="pill-button"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="pack-type">end</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox" id="create_collection_page">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="HdyHeaderBar">
+                <property name="visible">True</property>
+                <property name="title" translatable="yes">Add a Collection</property>
+                <property name="show-close-button" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only"/>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible" bind-source="GamesCollectionActionWindow" 
bind-property="create-collection-page-only" bind-flags="invert-boolean|sync-create"/>
+                    <property name="action-name">collection-action.go-back</property>
+                    <child internal-child="accessible">
+                      <object class="AtkObject">
+                        <property name="accessible-name" translatable="yes">Back</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="icon-name">go-previous-symbolic</property>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="image-button"/>
+                    </style>
+                  </object>
+                </child>
+                <style>
+                    <class name="titlebar"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="valign">center</property>
+                    <property name="pixel-size">96</property>
+                    <property name="icon-name">folder-new-symbolic</property>
+                    <property name="vexpand">True</property>
+                    <property name="margin-top">18</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Enter a collection name</property>
+                    <property name="valign">end</property>
+                    <property name="margin-top">18</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="name_entry">
+                    <property name="visible">True</property>
+                    <property name="width-request">300</property>
+                    <property name="height-request">10</property>
+                    <property name="halign">center</property>
+                    <property name="margin">12</property>
+                    <signal name="notify::text" handler="on_collection_name_entry_changed"/>
+                    <signal name="activate" handler="create_collection"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="error_label">
+                    <property name="visible">True</property>
+                    <property name="valign">end</property>
+                    <property name="margin-bottom">12</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">C_reate</property>
+                    <property name="use-underline">True</property>
+                    <property name="sensitive" bind-source="GamesCollectionActionWindow" 
bind-property="is-collection-name-valid" bind-flags="sync-create"/>
+                    <property name="margin">24</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                    <property name="action-name">collection-action.create-collection</property>
+                    <child internal-child="accessible">
+                      <object class="AtkObject">
+                        <property name="accessible-name" translatable="yes">Create collection</property>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="suggested-action"/>
+                      <class name="pill-button"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkListBoxRow" id="add_row">
+    <property name="visible">True</property>
+    <property name="activatable">True</property>
+    <child>
+      <object class="GtkImage">
+        <property name="visible">True</property>
+        <property name="icon-name">list-add-symbolic</property>
+        <property name="margin">12</property>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/src/meson.build b/src/meson.build
index 67b9c9098..0d44a3f2c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -113,6 +113,7 @@ vala_sources = [
   'ui/application.vala',
   'ui/application-window.vala',
   'ui/checkmark-item.vala',
+  'ui/collection-action-window.vala',
   'ui/collection-empty.vala',
   'ui/collection-icon-view.vala',
   'ui/collection-list-item.vala',
diff --git a/src/ui/collection-action-window.vala b/src/ui/collection-action-window.vala
new file mode 100644
index 000000000..75d371121
--- /dev/null
+++ b/src/ui/collection-action-window.vala
@@ -0,0 +1,259 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/ui/collection-action-window.ui")]
+private class Games.CollectionActionWindow : Hdy.Window {
+       public signal void confirmed (Collection[] collections);
+
+       [GtkChild]
+       private Hdy.Deck deck;
+       [GtkChild]
+       private Gtk.Box add_to_collection_page;
+       [GtkChild]
+       private Gtk.Box create_collection_page;
+       [GtkChild]
+       private Gtk.Stack user_collections_page_stack;
+       [GtkChild]
+       private Gtk.ScrolledWindow list_page;
+       [GtkChild]
+       private Gtk.Box empty_page;
+       [GtkChild]
+       private Gtk.Entry name_entry;
+       [GtkChild]
+       private Gtk.Label error_label;
+       [GtkChild]
+       private Gtk.ListBox list_box;
+       [GtkChild]
+       private Hdy.SearchBar search_bar;
+       [GtkChild]
+       private Gtk.SearchEntry search_entry;
+       [GtkChild]
+       private Gtk.ListBoxRow add_row;
+
+       private CollectionManager collection_manager;
+       private SimpleActionGroup action_group;
+       private const ActionEntry[] action_entries = {
+               { "go-back",           go_back },
+               { "new-collection",    new_collection },
+               { "create-collection", create_collection },
+               { "add-to-collection", add_to_collection }
+       };
+
+       public string[] filtering_terms;
+       private string filtering_text {
+               set {
+                       if (value == null)
+                               filtering_terms = null;
+                       else
+                               filtering_terms = value.split (" ");
+
+                       list_box.invalidate_filter ();
+               }
+       }
+
+       private CollectionModel _collection_model;
+       public CollectionModel collection_model {
+               get { return _collection_model; }
+               set {
+                       _collection_model = value;
+
+                       list_box.bind_model (collection_model, add_collection_row);
+                       list_box.set_filter_func (list_box_filter);
+                       list_box.invalidate_filter ();
+               }
+       }
+
+       private bool _is_user_collections_empty;
+       public bool is_user_collections_empty {
+               get { return _is_user_collections_empty; }
+               set {
+                       _is_user_collections_empty = value;
+
+                       if (is_user_collections_empty)
+                               user_collections_page_stack.visible_child = empty_page;
+                       else
+                               user_collections_page_stack.visible_child = list_page;
+               }
+       }
+
+       public bool is_search_mode { get; set; }
+       public bool is_collection_name_valid { get; set; }
+       public bool create_collection_page_only { get; construct; }
+       public Collection insensitive_collection { get; construct; }
+
+       construct {
+               if (create_collection_page_only)
+                       deck.visible_child = create_collection_page;
+               else
+                       deck.visible_child = add_to_collection_page;
+
+               collection_manager = Application.get_default ().get_collection_manager ();
+
+               is_user_collections_empty = collection_manager.n_user_collections == 0;
+               collection_manager.collection_added.connect ((collection) => {
+                       is_user_collections_empty = collection_manager.n_user_collections == 0;
+               });
+
+               action_group = new SimpleActionGroup ();
+               action_group.add_action_entries (action_entries, this);
+               insert_action_group ("collection-action", action_group);
+
+               search_bar.connect_entry (search_entry);
+       }
+
+       public CollectionActionWindow (bool create_collection_page_only = true, Collection? collection = 
null) {
+               Object (create_collection_page_only : create_collection_page_only, insensitive_collection : 
collection);
+       }
+
+       private Gtk.Widget add_collection_row (Object object) {
+               var collection = object as Collection;
+               if (collection.get_collection_type () == Collection.CollectionType.PLACEHOLDER)
+                       return add_row;
+
+               var row = new CollectionListItem (collection);
+               row.sensitive = collection != insensitive_collection;
+               row.show ();
+
+               return row;
+       }
+
+       private bool list_box_filter (Gtk.ListBoxRow row) {
+               bool show_row;
+
+               if (row is CollectionListItem) {
+                       var list_item = row as CollectionListItem;
+                       var collection = list_item.collection;
+                       var type = collection.get_collection_type ();
+
+                       show_row = (type == Collection.CollectionType.USER) &&
+                                      ((is_search_mode && collection.matches_search_terms (filtering_terms)) 
||
+                                       (!is_search_mode));
+               }
+               else
+                       show_row = !is_search_mode || filtering_terms.length == 0;
+
+               row.visible = show_row;
+               return show_row;
+       }
+
+       private void add_to_collection () {
+               Collection[] collections = {};
+
+               foreach (var child in list_box.get_children ()) {
+                       var row = child as CollectionListItem;
+                       if (row == null || row.collection.get_collection_type () != 
Collection.CollectionType.USER)
+                               continue;
+
+                       var check_button = row.activatable_widget as Gtk.CheckButton;
+                       if (check_button.active)
+                               collections += row.collection;
+               }
+
+               confirmed (collections);
+               close ();
+       }
+
+       private void new_collection () {
+               deck.navigate (Hdy.NavigationDirection.FORWARD);
+       }
+
+       private void go_back () {
+               if (create_collection_page_only || !deck.navigate (Hdy.NavigationDirection.BACK))
+                       close ();
+       }
+
+       [GtkCallback]
+       private void create_collection () {
+               if (!is_collection_name_valid)
+                       return;
+
+               collection_manager.create_user_collection (name_entry.text.strip ());
+               go_back ();
+       }
+
+       [GtkCallback]
+       private void on_listbox_row_activated (Gtk.ListBoxRow row) {
+               if (row == add_row)
+                       new_collection ();
+       }
+
+       [GtkCallback]
+       public bool on_key_pressed (Gdk.EventKey event) {
+               var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
+
+               uint keyval;
+               var keymap = Gdk.Keymap.get_for_display (get_display ());
+               keymap.translate_keyboard_state (event.hardware_keycode, event.state,
+                                                event.group, out keyval, null, null, null);
+
+               if (keyval == Gdk.Key.Escape) {
+                       if (deck.visible_child == create_collection_page) {
+                               go_back ();
+                               return Gdk.EVENT_STOP;
+                       }
+
+                       if (is_search_mode) {
+                               is_search_mode = false;
+                               return Gdk.EVENT_STOP;
+                       }
+
+                       go_back ();
+                       return Gdk.EVENT_STOP;
+               }
+
+               if (((event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) &&
+                  (((get_direction () == Gtk.TextDirection.LTR) && keyval == Gdk.Key.Left) ||
+                   ((get_direction () == Gtk.TextDirection.RTL) && keyval == Gdk.Key.Right)) &&
+                   !create_collection_page_only && deck.navigate (Hdy.NavigationDirection.BACK))
+                       return Gdk.EVENT_STOP;
+
+               if (is_user_collections_empty)
+                       return Gdk.EVENT_PROPAGATE;
+
+               if (deck.visible_child == add_to_collection_page &&
+                   (keyval == Gdk.Key.f || keyval == Gdk.Key.F) &&
+                   (event.state & default_modifiers) == Gdk.ModifierType.CONTROL_MASK) {
+                       is_search_mode = !is_search_mode;
+                       return Gdk.EVENT_STOP;
+               }
+
+               return search_bar.handle_event (event);
+       }
+
+       [GtkCallback]
+       private void on_collection_name_entry_changed () {
+               var name = name_entry.text.strip ();
+               if (name == "") {
+                       error_label.label = _("Collection name cannot be empty");
+                       name_entry.get_style_context ().add_class ("error");
+                       is_collection_name_valid = false;
+                       return;
+               }
+
+               if (collection_manager.does_collection_title_exist (name)) {
+                       error_label.label = _("A collection with this name already exists");
+                       name_entry.get_style_context ().add_class ("error");
+                       is_collection_name_valid = false;
+                       return;
+               }
+
+               name_entry.get_style_context ().remove_class ("error");
+               error_label.label = null;
+               is_collection_name_valid = true;
+       }
+
+       [GtkCallback]
+       private void on_visible_child_changed () {
+               if (deck.visible_child == create_collection_page) {
+                       name_entry.text = "";
+                       error_label.label = "";
+                       name_entry.get_style_context ().remove_class ("error");
+                       name_entry.grab_focus_without_selecting ();
+               }
+       }
+
+       [GtkCallback]
+       private void on_search_text_notify () {
+               filtering_text = search_entry.text;
+               search_entry.grab_focus_without_selecting ();
+       }
+}


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