[gnome-shell/eos3.8: 94/255] Implement the desktop search's results panel



commit 9dc242cd4e39d6c1bf6b26e1bf8116d8c8ddb21d
Author: Mario Sanchez Prada <mario endlessm com>
Date:   Wed Feb 7 15:45:18 2018 +0000

    Implement the desktop search's results panel
    
    This commit implements the panel, with the following considerations:
    
      - Display a max of 8 results in a grid result
      - Show separator between search results (but not at the end)
      - Center search results horizontally and vertically
      - Add a close button in the top right corner to close the panel
      - Adapt style of icons, text... according to Endless' design specs
    
    https://phabricator.endlessm.com/T4606
    https://phabricator.endlessm.com/T17659
    
     * 13-03-2019: Squashed with 38090bc34
        viewSelector: Don't set the spinner on when there are nothing to search
    
        The spinner is being set whenever the search-progress-updated signal is
        received, and then uses the .searchInProgress property to determine whether
        to spin or not, but the problem is that this property is being set from
        multiple places, so sometimes we can end in a race condition that ends up
        with the callback being called with no text in the entry but still the
        searchResults object reporting that it's searching.
    
        When this happens, the spinner enters a state where it's constantly
        spinning, consuming lots of CPU for no reason unless some user initiated
        action takes it out of that state.
    
        Probably not a real fix, but let's at least prevent this situation from
        happening by double checking that there's something to search before
        setting the spinner on fire.
    
        https://phabricator.endlessm.com/T17973
    
     * 13-03-2019: Squashed with 38090bc34
        shellEntry: ensure that search spinner stops
    
        Make sure the spinner is stopped whenever the search is stopped.
        Previously, it was easy to get into a state where the spinner
        would keep running and cause CPU churn.
    
        https://phabricator.endlessm.com/T17855

 data/theme/gnome-shell-sass/_endless.scss | 176 ++++++++++++++++++++++++++++++
 js/ui/appDisplay.js                       |  15 ++-
 js/ui/search.js                           | 135 ++++++++++++++---------
 js/ui/shellEntry.js                       |   1 +
 js/ui/viewSelector.js                     |   7 +-
 5 files changed, 278 insertions(+), 56 deletions(-)
---
diff --git a/data/theme/gnome-shell-sass/_endless.scss b/data/theme/gnome-shell-sass/_endless.scss
index bb4be54852..69d876aef8 100644
--- a/data/theme/gnome-shell-sass/_endless.scss
+++ b/data/theme/gnome-shell-sass/_endless.scss
@@ -461,3 +461,179 @@ popup-separator-menu-item {
          @extend %overview-icon;
     }
 }
+
+// Desktop Search (results)
+
+%search-label-main {
+    font-size: 11pt;
+    font-weight: bold;
+}
+
+%search-label-color {
+    color: #444444;
+}
+
+#searchResults {
+    background-color: rgba(255,255,255,0.92);
+    border-radius: 4px;
+    padding: 10px;
+    max-width: 875px;
+    margin-left: 50px;
+    margin-right: 50px;
+    margin-bottom: 10px;
+
+    .overview-icon {
+        @extend %overview-icon;
+        border-radius: 4px;
+    }
+
+    .overview-icon-label {
+        @extend %search-label-main;
+        @extend %search-label-color;
+        height: 20px;
+        text-shadow: none;
+    }
+
+    #searchResultsContent {
+        spacing: 0px;
+
+        .search-section {
+            padding: 0px 10px;
+
+            .search-provider-icon-label {
+                @extend %search-label-main;
+                @extend %search-label-color;
+                text-shadow: none;
+                width: 100px;
+            }
+
+            .search-provider-icon-label {
+                margin-top: 6px;
+            }
+
+            .search-provider-icon {
+                border-radius: 4px;
+                transition-duration: 100ms;
+                width: 100px;
+            }
+
+            .search-section-separator {
+                -gradient-height: 1px;
+                -gradient-start: rgba(208,208,208,0.9);
+                -gradient-end: rgba(208,208,208,0.9);
+                -margin-horizontal: 0em;
+                height: 1px;
+            }
+
+            .search-section-content {
+                background-color: white;
+                padding-top: 5px;
+                padding-bottom: 10px;
+                spacing: 25px; /* space between provider icon and results container */
+            }
+
+            .icon-grid {
+                spacing: 20px;
+                padding-top: 10px;
+                padding-bottom: 15px;
+                -shell-grid-horizontal-item-size: 104px;
+                -shell-grid-vertical-item-size: 104px;
+            }
+        }
+
+        .list-search-results {
+            spacing: 0px;
+
+            .list-search-result {
+                padding: 5px 10px 10px 10px;
+                transition-duration: 100ms;
+
+                .list-search-result-content {
+                    spacing: 10px;
+                    padding: 0px;
+                    min-height: 32px;
+
+                    .list-search-result-title {
+                        @extend %search-label-main;
+                        @extend %search-label-color;
+                        font-size: 13pt;
+                    }
+
+                    .list-search-result-description {
+                        @extend %search-label-color;
+                    }
+
+                    .list-search-result-arrow-icon {
+                        icon-size: 16px;
+                        color: transparent;
+                    }
+                }
+
+                &:first-child {
+                    border-radius-topright: 4px;
+                    border-radius-topleft: 4px;
+                }
+
+                &:last-child {
+                    border-radius-bottomright: 4px;
+                    border-radius-bottomleft: 4px;
+                }
+
+                &:hover, &:focus, &:selected {
+                    .list-search-result-arrow-icon { color: #444444; }
+                }
+            }
+        }
+    }
+
+    #searchResultsCloseButton StIcon {
+        @extend %search-label-color;
+        icon-size: 14px;
+    }
+
+    #searchResultsCloseButton:hover StIcon {
+        color: #888888;
+    }
+
+    #searchResultsCloseButton:active StIcon {
+        @extend %search-label-color;
+    }
+
+    .search-display > StBoxLayout {
+        padding: 0px;
+    }
+
+    .search-statustext {
+        color: #242424;
+        font-size: 2em;
+        font-weight: bold;
+    }
+}
+
+%search-result-hover { background-color: rgba(226,226,226,0.9); }
+%search-result-selected { background-color: rgba(208,208,208,0.9); }
+%search-result-active { background-color: rgba(192,192,192,0.9); }
+
+.list-search-result {
+    &:hover { @extend %search-result-hover; }
+    &:focus, &:selected { @extend %search-result-selected; }
+    &:active { @extend %search-result-active; }
+}
+
+.grid-search-result {
+    border-radius: 4px;
+    transition-duration: 100ms;
+
+    &:hover { @extend %search-result-hover; }
+    &:focus, &:selected { @extend %search-result-selected; }
+    &:active {
+        @extend %search-result-active;
+        .overview-icon { box-shadow: none; }
+    }
+}
+
+.search-provider-icon {
+    &:hover { @extend %search-result-hover; }
+    &:focus, &:selected { @extend %search-result-selected; }
+    &:active { @extend %search-result-active; }
+}
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 562bcefd44..b33ac7befe 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -997,10 +997,17 @@ var AppSearchProvider = class AppSearchProvider {
         this.getInitialResultSet(terms, callback, cancellable);
     }
 
+    activateResult(appId) {
+        let event = Clutter.get_current_event();
+        let app = this._appSys.lookup_app(appId);
+        let activationContext = new AppActivation.AppActivationContext(app);
+        activationContext.activate(event);
+    }
+
     createResultObject(resultMeta) {
-        if (resultMeta.id.endsWith('.desktop'))
-            return new AppIcon(this._appSys.lookup_app(resultMeta['id']));
-        else
+        // We only use this code path for SystemActions which, from the point
+        // of view of this method, are those NOT referenced with desktop IDs.
+        if (!resultMeta.id.endsWith('.desktop'))
             return new SystemActionIcon(this, resultMeta);
     }
 };
@@ -2238,6 +2245,6 @@ var SystemActionIcon = GObject.registerClass(
 class SystemActionIcon extends Search.GridSearchResult {
     activate() {
         SystemActions.getDefault().activateAction(this.metaInfo['id']);
-        Main.overview.hide();
+        Main.overview.viewSelector.show(2);
     }
 });
diff --git a/js/ui/search.js b/js/ui/search.js
index 9706361d37..3aca685684 100644
--- a/js/ui/search.js
+++ b/js/ui/search.js
@@ -14,6 +14,7 @@ const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
 
 var MAX_LIST_SEARCH_RESULTS_ROWS = 5;
 var MAX_GRID_SEARCH_RESULTS_ROWS = 1;
+var MAX_GRID_SEARCH_RESULTS_COLS = 8;
 
 var MaxWidthBox = GObject.registerClass(
 class MaxWidthBox extends St.BoxLayout {
@@ -58,7 +59,25 @@ class SearchResult extends St.Button {
             St.Clipboard.get_default().set_text(
                 St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText);
         }
-        Main.overview.toggle();
+        Main.overview.hide();
+    }
+});
+
+var ListDescriptionBox = GObject.registerClass(
+class ListDescriptionBox extends St.BoxLayout {
+    vfunc_get_preferred_height(forWidth) {
+        // This container requests space for the title and description
+        // regardless of visibility, but allocates normally to visible actors.
+        // This allows us have a constant sized box, but still center the title
+        // label when the description is not present.
+        let min = 0, nat = 0;
+        let children = this.get_children();
+        for (let child of children) {
+            let [childMin, childNat] = child.get_preferred_height(forWidth);
+            min += childMin;
+            nat += childNat;
+        }
+        return [min, nat];
     }
 });
 
@@ -72,7 +91,6 @@ class ListSearchResult extends SearchResult {
         let content = new St.BoxLayout({
             style_class: 'list-search-result-content',
             vertical: false,
-            x_align: Clutter.ActorAlign.START,
             x_expand: true,
             y_expand: true,
         });
@@ -80,23 +98,25 @@ class ListSearchResult extends SearchResult {
 
         this._termsChangedId = 0;
 
-        let titleBox = new St.BoxLayout({
-            style_class: 'list-search-result-title',
-            y_align: Clutter.ActorAlign.CENTER,
-        });
-
-        content.add_child(titleBox);
-
         // An icon for, or thumbnail of, content
         let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
         if (icon)
-            titleBox.add(icon);
+            content.add_child(icon);
+
+        let details = new ListDescriptionBox({
+            vertical: true,
+            x_align: Clutter.ActorAlign.START,
+            y_align: Clutter.ActorAlign.CENTER,
+        });
+        content.add_child(details);
 
         let title = new St.Label({
+            style_class: 'list-search-result-title',
             text: this.metaInfo['name'],
-            y_align: Clutter.ActorAlign.CENTER,
+            x_align: Clutter.ActorAlign.START,
+            y_align: Clutter.ActorAlign.START,
         });
-        titleBox.add_child(title);
+        details.add_child(title);
 
         this.label_actor = title;
 
@@ -105,7 +125,7 @@ class ListSearchResult extends SearchResult {
                 style_class: 'list-search-result-description',
                 y_align: Clutter.ActorAlign.CENTER,
             });
-            content.add_child(this._descriptionLabel);
+            details.add_child(this._descriptionLabel);
 
             this._termsChangedId =
                 this._resultsView.connect('terms-changed',
@@ -114,6 +134,14 @@ class ListSearchResult extends SearchResult {
             this._highlightTerms();
         }
 
+        let hoverIcon = new St.Icon({
+            style_class: 'list-search-result-arrow-icon',
+            icon_name: 'go-next-symbolic',
+            x_expand: true,
+            x_align: Clutter.ActorAlign.END,
+        });
+        content.add_child(hoverIcon);
+
         this.connect('destroy', this._onDestroy.bind(this));
     }
 
@@ -215,9 +243,6 @@ var SearchResultsBase = GObject.registerClass({
         this.notify('focus-child');
     }
 
-    _setMoreCount(_count) {
-    }
-
     _ensureResultActors(results, callback) {
         let metasNeeded = results.filter(
             resultId => this._resultDisplays[resultId] === undefined
@@ -270,7 +295,6 @@ var SearchResultsBase = GObject.registerClass({
             let results = maxResults > -1
                 ? this.provider.filterResults(providerResults, maxResults)
                 : providerResults;
-            let moreCount = Math.max(providerResults.length - results.length, 0);
 
             this._ensureResultActors(results, successful => {
                 if (!successful) {
@@ -287,7 +311,6 @@ var SearchResultsBase = GObject.registerClass({
                 results.forEach(resultId => {
                     this._addItem(this._resultDisplays[resultId]);
                 });
-                this._setMoreCount(this.provider.canLaunchSearch ? moreCount : 0);
                 this.show();
                 callback();
             });
@@ -315,16 +338,13 @@ class ListSearchResults extends SearchResultsBase {
             style_class: 'list-search-results',
             vertical: true,
             x_expand: true,
+            y_align: Clutter.ActorAlign.CENTER,
         });
         this._container.add_child(this._content);
 
         this._resultDisplayBin.set_child(this._container);
     }
 
-    _setMoreCount(count) {
-        this.providerInfo.setMoreCount(count);
-    }
-
     _getMaxDisplayedResults() {
         return MAX_LIST_SEARCH_RESULTS_ROWS;
     }
@@ -356,7 +376,7 @@ class GridSearchResults extends SearchResultsBase {
         super._init(provider, resultsView);
 
         this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
-                                             xAlign: St.Align.START });
+                                             xAlign: St.Align.MIDDLE });
 
         this._bin = new St.Bin({ x_align: Clutter.ActorAlign.CENTER });
         this._bin.set_child(this._grid);
@@ -397,12 +417,7 @@ class GridSearchResults extends SearchResultsBase {
     }
 
     _getMaxDisplayedResults() {
-        let width = this.allocation.get_width();
-        if (width == 0)
-            return -1;
-
-        let nCols = this._grid.columnsForWidth(width);
-        return nCols * this._grid.getRowLimit();
+        return MAX_GRID_SEARCH_RESULTS_ROWS * MAX_GRID_SEARCH_RESULTS_COLS;
     }
 
     _clearResultDisplay() {
@@ -430,11 +445,27 @@ var SearchResultsView = GObject.registerClass({
     Signals: {
         'terms-changed': {},
         'search-progress-updated': {},
+        'search-close-clicked': {},
     },
 }, class SearchResultsView extends St.BoxLayout {
     _init() {
         super._init({ name: 'searchResults', vertical: true });
 
+        let closeIcon = new St.Icon({ icon_name: 'window-close-symbolic' });
+        let closeButton = new St.Button({
+            name: 'searchResultsCloseButton',
+            child: closeIcon,
+            x_expand: true,
+            x_align: Clutter.ActorAlign.END,
+            y_expand: false,
+            y_align: Clutter.ActorAlign.START,
+        });
+        closeButton.connect('clicked', () => {
+            this.emit('search-close-clicked');
+        });
+
+        this.add_child(closeButton);
+
         this._content = new MaxWidthBox({
             name: 'searchResultsContent',
             vertical: true,
@@ -498,6 +529,21 @@ var SearchResultsView = GObject.registerClass({
         this._reloadRemoteProviders();
     }
 
+    vfunc_allocate(box, flags) {
+        let themeNode = this.get_theme_node();
+        let maxWidth = themeNode.get_max_width();
+        let availWidth = box.x2 - box.x1;
+        let adjustedBox = box;
+
+        if (availWidth > maxWidth) {
+            let excessWidth = availWidth - maxWidth;
+            adjustedBox.x1 += Math.floor(excessWidth / 2);
+            adjustedBox.x2 -= Math.floor(excessWidth / 2);
+        }
+
+        super.vfunc_allocate(adjustedBox, flags);
+    }
+
     get terms() {
         return this._terms;
     }
@@ -819,32 +865,24 @@ class ProviderInfo extends St.Button {
             y_align: Clutter.ActorAlign.START,
         });
 
-        this._content = new St.BoxLayout({ vertical: false,
-                                           style_class: 'list-search-provider-content' });
-        this.set_child(this._content);
-
         let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
                                  gicon: provider.appInfo.get_icon() });
 
-        let detailsBox = new St.BoxLayout({ style_class: 'list-search-provider-details',
-                                            vertical: true,
-                                            x_expand: true });
-
-        let nameLabel = new St.Label({ text: provider.appInfo.get_name(),
-                                       x_align: Clutter.ActorAlign.START });
-
-        this._moreLabel = new St.Label({ x_align: Clutter.ActorAlign.START });
+        this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+        this._content.add_actor(icon);
 
-        detailsBox.add_actor(nameLabel);
-        detailsBox.add_actor(this._moreLabel);
+        let box = new St.BoxLayout({ vertical: true, x_expand: false });
+        this.set_child(box);
 
+        box.add_actor(this._content);
 
-        this._content.add_actor(icon);
-        this._content.add_actor(detailsBox);
+        let label = new St.Label({ text: provider.appInfo.get_name(),
+                                   style_class: 'search-provider-icon-label' });
+        box.add_actor(label);
     }
 
     get PROVIDER_ICON_SIZE() {
-        return 32;
+        return 64;
     }
 
     animateLaunch() {
@@ -853,9 +891,4 @@ class ProviderInfo extends St.Button {
         if (app.state == Shell.AppState.STOPPED)
             IconGrid.zoomOutActor(this._content);
     }
-
-    setMoreCount(count) {
-        this._moreLabel.text = ngettext("%d more", "%d more", count).format(count);
-        this._moreLabel.visible = count > 0;
-    }
 });
diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js
index bf055f8d35..3190ebd85a 100644
--- a/js/ui/shellEntry.js
+++ b/js/ui/shellEntry.js
@@ -310,6 +310,7 @@ var OverviewEntry = GObject.registerClass({
 
     _stopSearch() {
         global.stage.set_key_focus(null);
+        this.setSpinning(false);
     }
 
     _startSearch(event) {
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index 542465240d..83636c66a8 100644
--- a/js/ui/viewSelector.js
+++ b/js/ui/viewSelector.js
@@ -409,6 +409,7 @@ class ViewsDisplay extends St.Widget {
 
         this._searchResults = new Search.SearchResultsView();
         this._searchResults.connect('search-progress-updated', this._updateSpinner.bind(this));
+        this._searchResults.connect('search-close-clicked', this._resetSearch.bind(this));
 
         // Since the entry isn't inside the results container we install this
         // dummy widget as the last results container child so that we can
@@ -488,7 +489,11 @@ class ViewsDisplay extends St.Widget {
     }
 
     _updateSpinner() {
-        this._entry.setSpinning(this._searchResults.searchInProgress);
+        // Make sure we never set the spinner on when there's nothing to search,
+        // regardless of the reported current state, as it can be out of date.
+        let searchTerms = this.entry.text.trim();
+        let spinning = (searchTerms.length > 0) && this._searchResults.searchInProgress;
+        this._entry.setSpinning(spinning);
     }
 
     _enterSearch() {


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