[gnome-shell/wip/re-search-v2: 6/28] searchDisplay: Add ListSearchResult and ListSearchResults



commit 69c807900f02481bad63a4943b786e9b97860150
Author: Tanner Doshier <doshitan gmail com>
Date:   Wed Aug 1 20:48:10 2012 -0500

    searchDisplay: Add ListSearchResult and ListSearchResults
    
    These are for all search results except apps (and Wanda).
    We also simplify a bit the packing of search results, which removes some
    ugly code in navigateFocus() where we needed to call
    st_widget_navigate_focus() twice, since the grid icon was composed by
    two nested boxes, both focusable.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=681797

 data/theme/gnome-shell.css |   70 +++++++++-----
 js/ui/search.js            |    3 -
 js/ui/searchDisplay.js     |  228 ++++++++++++++++++++++++++++++++++----------
 js/ui/viewSelector.js      |    2 +-
 js/ui/wanda.js             |    3 +-
 5 files changed, 227 insertions(+), 79 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index c59f51a..4df8530 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -49,7 +49,7 @@ stage {
 .switcher-list, 
 .app-well-app > .overview-icon,
 .show-apps > .overview-icon,
-.search-result-content > .overview-icon {
+.grid-search-result .overview-icon {
     font-size: 9pt;
     font-weight: bold;
 }
@@ -760,7 +760,7 @@ StScrollBar StButton#vhandle:active {
 
 #searchResultsContent {
     padding-right: 20px;
-    spacing: 36px;
+    spacing: 16px;
 }
 
 #searchResultsContent:rtl {
@@ -768,29 +768,32 @@ StScrollBar StButton#vhandle:active {
     padding-left: 20px;
 }
 
-.search-section-header {
-    padding: 4px 12px;
-    spacing: 4px;
-    color: #6f6f6f;
-    font-size: .8em;
+.search-section {
+    /* This should be equal to #searchResultsContent spacing */
+    spacing: 16px;
 }
 
-.search-statustext {
-    color: #efefef;
-    font-size: 2em;
-    font-weight: bold;
+.search-section-separator {
+    -gradient-height: 1px;
+    -gradient-start: rgba(255,255,255,0);
+    -gradient-end: rgba(255,255,255,0.5);
+    -margin-horizontal: 1.5em;
+    height: 1px;
 }
 
-.search-section-results {
-    padding: 6px;
+.search-section-content {
+    /* This is the space between the provider icon and the results container */
+    spacing: 32px;
 }
 
-.search-section-list-results {
-    spacing: 4px;
+.search-statustext {
+    color: #efefef;
+    font-size: 2em;
+    font-weight: bold;
 }
 
-.results-container {
-    spacing: 4px;
+.list-search-results {
+    spacing: 3px;
 }
 
 /* Text labels are an odd number of pixels tall. The uneven top and bottom
@@ -819,7 +822,7 @@ StScrollBar StButton#vhandle:active {
     -x-offset: 8px;
 }
 
-/* Application Launchers and Grid */
+/* Application Launchers, Grid and List results */
 
 .icon-grid {
     spacing: 36px;
@@ -871,6 +874,23 @@ StScrollBar StButton#vhandle:active {
     padding: 4px 8px;
 }
 
+.list-search-result-content {
+    spacing: 12px;
+    padding: 12px;
+}
+
+.list-search-result-title {
+    font-weight: bold;
+    font-size: 14pt;
+    color: white;
+}
+
+.list-search-result-description {
+    font-weight: bold;
+    font-size: 12pt;
+    color: #eeeeec;
+}
+
 .search-provider-icon-more {
     background-color: white;
     border-radius: 5px;
@@ -880,7 +900,8 @@ StScrollBar StButton#vhandle:active {
 .app-well-app > .overview-icon,
 .show-apps > .overview-icon,
 .search-provider-icon,
-.search-result-content > .overview-icon {
+.list-search-result,
+.grid-search-result .overview-icon {
     border-radius: 4px;
     padding: 3px;
     border: 1px rgba(0,0,0,0);
@@ -897,7 +918,8 @@ StScrollBar StButton#vhandle:active {
 .app-well-app:hover > .overview-icon,
 .show-apps:hover > .overview-icon,
 .search-provider-icon:hover,
-.search-result-content:hover > .overview-icon {
+.list-search-result:hover,
+.grid-search-result:hover .overview-icon {
     background-color: rgba(255,255,255,0.1);
     text-shadow: black 0px 2px 2px;
     transition-duration: 100;
@@ -932,12 +954,14 @@ StScrollBar StButton#vhandle:active {
 }
 
 .app-well-app:focus > .overview-icon,
-.search-result-content:focus > .overview-icon,
+.grid-search-result:focus .overview-icon,
 .show-apps:focus > .overview-icon,
 .search-provider-icon:focus,
+.list-search-result:focus,
 .app-well-app:selected > .overview-icon,
-.search-result-content:selected > .overview-icon,
-.search-provider-icon:selected {
+.grid-search-result:selected .overview-icon,
+.search-provider-icon:selected,
+.list-search-result:selected {
     background-color: rgba(255,255,255,0.33);
 }
 
diff --git a/js/ui/search.js b/js/ui/search.js
index d3fa3b1..a963143 100644
--- a/js/ui/search.js
+++ b/js/ui/search.js
@@ -151,9 +151,6 @@ const SearchProvider = new Lang.Class({
      * Search providers may optionally override this to render a
      * particular serch result in a custom fashion.  The default
      * implementation will show the icon next to the name.
-     *
-     * The actor should be an instance of St.Widget, with the style class
-     * 'search-result-content'.
      */
     createResultActor: function(resultMeta, terms) {
         return null;
diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js
index ff3ac8a..2463b05 100644
--- a/js/ui/searchDisplay.js
+++ b/js/ui/searchDisplay.js
@@ -11,10 +11,11 @@ const DND = imports.ui.dnd;
 const IconGrid = imports.ui.iconGrid;
 const Main = imports.ui.main;
 const Overview = imports.ui.overview;
+const Separator = imports.ui.separator;
 const Search = imports.ui.search;
 
-const MAX_SEARCH_RESULTS_ROWS = 1;
-
+const MAX_SEARCH_RESULTS_ROWS = 3;
+const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
 
 const SearchResult = new Lang.Class({
     Name: 'SearchResult',
@@ -22,33 +23,103 @@ const SearchResult = new Lang.Class({
     _init: function(provider, metaInfo, terms) {
         this.provider = provider;
         this.metaInfo = metaInfo;
-        this.actor = new St.Button({ style_class: 'search-result',
-                                     reactive: true,
+
+        this.actor = new St.Button({ reactive: true,
+                                     can_focus: true,
+                                     track_hover: true,
                                      x_align: St.Align.START,
                                      y_fill: true });
+
         this.actor._delegate = this;
+        this.actor.connect('clicked', Lang.bind(this, this.activate));
+    },
+
+    activate: function() {
+        this.provider.activateResult(this.metaInfo.id);
+        Main.overview.toggle();
+    },
+
+    setSelected: function(selected) {
+        if (selected)
+            this.actor.add_style_pseudo_class('selected');
+        else
+            this.actor.remove_style_pseudo_class('selected');
+    }
+});
+
+const ListSearchResult = new Lang.Class({
+    Name: 'ListSearchResult',
+    Extends: SearchResult,
+
+    ICON_SIZE: 64,
+
+    _init: function(provider, metaInfo, terms) {
+        this.parent(provider, metaInfo, terms);
+
+        this.actor.style_class = 'list-search-result';
+        this.actor.x_fill = true;
+
+        let content = new St.BoxLayout({ style_class: 'list-search-result-content',
+                                         vertical: false });
+        this.actor.set_child(content);
+
+        // An icon for, or thumbnail of, content
+        let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
+        if (icon) {
+            content.add(icon);
+        }
+
+        let details = new St.BoxLayout({ vertical: true });
+        content.add(details, { x_fill: true,
+                               y_fill: false,
+                               x_align: St.Align.START,
+                               y_align: St.Align.MIDDLE });
+
+        let title = new St.Label({ style_class: 'list-search-result-title',
+                                   text: this.metaInfo['name'] })
+        details.add(title, { x_fill: false,
+                             y_fill: false,
+                             x_align: St.Align.START,
+                             y_align: St.Align.START });
+
+        // TODO: should highlight terms in the description here
+        if (this.metaInfo['description']) {
+            let description = new St.Label({ style_class: 'list-search-result-description',
+                                             text: '"' + this.metaInfo['description'] + '"' });
+            details.add(description, { x_fill: false,
+                                       y_fill: false,
+                                       x_align: St.Align.START,
+                                       y_align: St.Align.END });
+        }
+    }
+});
+
+const GridSearchResult = new Lang.Class({
+    Name: 'GridSearchResult',
+    Extends: SearchResult,
+
+    _init: function(provider, metaInfo, terms) {
+        this.parent(provider, metaInfo, terms);
+
+        this.actor.style_class = 'grid-search-result';
         this._dragActorSource = null;
 
         let content = provider.createResultActor(metaInfo, terms);
+        let dragSource = null;
+
         if (content == null) {
-            content = new St.Bin({ style_class: 'search-result-content',
-                                   reactive: true,
-                                   can_focus: true,
-                                   track_hover: true,
-                                   accessible_role: Atk.Role.PUSH_BUTTON });
+            content = new St.Bin();
             let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
                                              { createIcon: this.metaInfo['createIcon'] });
             content.set_child(icon.actor);
-            this._dragActorSource = icon.icon;
             content.label_actor = icon.label;
+            dragSource = icon.icon;
         } else {
             if (content._delegate && content._delegate.getDragActorSource)
-                this._dragActorSource = content._delegate.getDragActorSource();
+                dragSource = content._delegate.getDragActorSource();
         }
-        this._content = content;
-        this.actor.set_child(content);
 
-        this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
+        this.actor.set_child(content);
 
         let draggable = DND.makeDraggable(this.actor);
         draggable.connect('drag-begin',
@@ -63,29 +134,16 @@ const SearchResult = new Lang.Class({
                           Lang.bind(this, function() {
                               Main.overview.endItemDrag(this);
                           }));
-    },
-
-    setSelected: function(selected) {
-        if (selected)
-            this._content.add_style_pseudo_class('selected');
-        else
-            this._content.remove_style_pseudo_class('selected');
-    },
 
-    activate: function() {
-        this.provider.activateResult(this.metaInfo.id);
-        Main.overview.toggle();
-    },
+        if (!dragSource)
+            // not exactly right, but alignment problems are hard to notice
+            dragSource = content;
 
-    _onResultClicked: function(actor) {
-        this.activate();
+        this._dragActorSource = dragSource;
     },
 
     getDragActorSource: function() {
-        if (this._dragActorSource)
-            return this._dragActorSource;
-        // not exactly right, but alignment problems are hard to notice
-        return this._content;
+        return this._dragActorSource;
     },
 
     getDragActor: function(stageX, stageY) {
@@ -100,6 +158,72 @@ const SearchResult = new Lang.Class({
     }
 });
 
+const ListSearchResults = new Lang.Class({
+    Name: 'ListSearchResults',
+    Extends: Search.SearchResultDisplay,
+
+    _init: function(provider) {
+        this.parent(provider);
+
+        this.actor = new St.BoxLayout({ style_class: 'list-search-results',
+                                        vertical: true });
+        this.actor.connect('notify::width', Lang.bind(this, function() {
+            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
+                let results = this.getResultsForDisplay();
+                if (results.length == 0)
+                    return;
+
+                provider.getResultMetas(results, Lang.bind(this, this.renderResults));
+            }));
+        }));
+        this._notDisplayedResult = [];
+        this._terms = [];
+        this._pendingClear = false;
+    },
+
+    getResultsForDisplay: function() {
+        let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount();
+        let canDisplay = MAX_SEARCH_RESULTS_ROWS - alreadyVisible;
+
+        let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
+
+        return this._notDisplayedResult.splice(0, numResults);
+    },
+
+    getVisibleResultCount: function() {
+        return this.actor.get_n_children();
+    },
+
+    hasMoreResults: function() {
+        return this._notDisplayedResult.length > 0;
+    },
+
+    setResults: function(results, terms) {
+        // copy the lists
+        this._notDisplayedResult = results.slice(0);
+        this._terms = terms.slice(0);
+        this._pendingClear = true;
+    },
+
+    renderResults: function(metas) {
+        for (let i = 0; i < metas.length; i++) {
+            let display = new ListSearchResult(this.provider, metas[i], this._terms);
+            this.actor.add_actor(display.actor);
+        }
+    },
+
+    clear: function () {
+        this.actor.destroy_all_children();
+        this._pendingClear = false;
+    },
+
+    getFirstResult: function() {
+        if (this.getVisibleResultCount() > 0)
+            return this.actor.get_child_at_index(0)._delegate;
+        else
+            return null;
+    }
+});
 
 const GridSearchResults = new Lang.Class({
     Name: 'GridSearchResults',
@@ -108,7 +232,7 @@ const GridSearchResults = new Lang.Class({
     _init: function(provider, grid) {
         this.parent(provider);
 
-        this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
+        this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
                                                      xAlign: St.Align.START });
         this.actor = new St.Bin({ x_align: St.Align.START });
 
@@ -156,7 +280,7 @@ const GridSearchResults = new Lang.Class({
 
     renderResults: function(metas) {
         for (let i = 0; i < metas.length; i++) {
-            let display = new SearchResult(this.provider, metas[i], this._terms);
+            let display = new GridSearchResult(this.provider, metas[i], this._terms);
             this._grid.addItem(display.actor);
         }
     },
@@ -174,8 +298,8 @@ const GridSearchResults = new Lang.Class({
     }
 });
 
-const SearchResults = new Lang.Class({
-    Name: 'SearchResults',
+const SearchDisplay = new Lang.Class({
+    Name: 'SearchDisplay',
 
     _init: function(searchSystem) {
         this._searchSystem = searchSystem;
@@ -225,10 +349,16 @@ const SearchResults = new Lang.Class({
     },
 
     createProviderMeta: function(provider) {
-        let providerBox = new St.BoxLayout({ style_class: 'search-section' });
+        let providerBox = new St.BoxLayout({ style_class: 'search-section',
+                                             vertical: true });
+        let providerBoxContent = new St.BoxLayout({ style_class: 'search-section-content' });
         let providerIcon = null;
+        let resultDisplay = null;
 
         if (provider.appInfo) {
+            let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
+            providerBox.add(separator.actor);
+
             providerIcon = new ProviderIcon(provider);
             providerIcon.connect('clicked', Lang.bind(this,
                 function() {
@@ -236,18 +366,21 @@ const SearchResults = new Lang.Class({
                     Main.overview.toggle();
                 }));
 
-            providerBox.add(providerIcon, { x_fill: false,
-                                            y_fill: false,
-                                            x_align: St.Align.START,
-                                            y_align: St.Align.START });
+            providerBoxContent.add(providerIcon, { x_fill: false,
+                                                   y_fill: false,
+                                                   x_align: St.Align.START,
+                                                   y_align: St.Align.START });
+
+            resultDisplay = new ListSearchResults(provider);
+        } else {
+            resultDisplay = new GridSearchResults(provider);
         }
 
-        let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
+        let resultDisplayBin = new St.Bin({ child: resultDisplay.actor,
                                             x_fill: true,
                                             y_fill: true });
-        providerBox.add(resultDisplayBin, { expand: true });
-        let resultDisplay = new GridSearchResults(provider);
-        resultDisplayBin.set_child(resultDisplay.actor);
+        providerBoxContent.add(resultDisplayBin, { expand: true });
+        providerBox.add(providerBoxContent);
 
         this._providerMeta.push({ provider: provider,
                                   actor: providerBox,
@@ -406,11 +539,6 @@ const SearchResults = new Lang.Class({
 
         let from = this._defaultResult ? this._defaultResult.actor : null;
         this.actor.navigate_focus(from, direction, false);
-        if (this._defaultResult) {
-            // The default result appears focused, so navigate directly to the
-            // next result.
-            this.actor.navigate_focus(global.stage.key_focus, direction, false);
-        }
     }
 });
 
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index 060c885..81c9275 100644
--- a/js/ui/viewSelector.js
+++ b/js/ui/viewSelector.js
@@ -90,7 +90,7 @@ const ViewSelector = new Lang.Class({
         this._appsPage = this._addPage(this._appDisplay.actor, null,
                                        _("Applications"), 'view-grid-symbolic');
 
-        this._searchResults = new SearchDisplay.SearchResults(this._searchSystem);
+        this._searchResults = new SearchDisplay.SearchDisplay(this._searchSystem);
         this._searchPage = this._addPage(this._searchResults.actor, this._entry,
                                          _("Search"), 'edit-find-symbolic');
 
diff --git a/js/ui/wanda.js b/js/ui/wanda.js
index 303d577..a0adf9f 100644
--- a/js/ui/wanda.js
+++ b/js/ui/wanda.js
@@ -71,8 +71,7 @@ const WandaIconBin = new Lang.Class({
     Name: 'WandaIconBin',
 
     _init: function(fish, label, params) {
-        this.actor = new St.Bin({ style_class: 'search-result-content',
-                                  reactive: true,
+        this.actor = new St.Bin({ reactive: true,
                                   track_hover: true });
         this.icon = new WandaIcon(fish, label, params);
 



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