[gnome-shell/eos3.8: 77/255] viewSelector: Add the search entry and results widgets to ViewsDisplay
- From: Matthew Leeds <mwleeds src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/eos3.8: 77/255] viewSelector: Add the search entry and results widgets to ViewsDisplay
- Date: Wed, 10 Jun 2020 19:04:25 +0000 (UTC)
commit baa4b6113345883ad398f2c76362ed33e3c5647c
Author: Mario Sanchez Prada <mario endlessm com>
Date: Thu Jun 15 15:04:08 2017 -0700
viewSelector: Add the search entry and results widgets to ViewsDisplay
Add the relevant elements to ViewsDisplay and ViewsDisplayContainer, and
override the vfunc_allocate() function in the custom layout manager to
properly assign the right allocation to every actor in the desktop: the
icon grid, the search entry and the search results panel.
* 2020-03-16:
+ Partially squash with bf01b1b9f
+ Rename SearchResults to SearchResultsView
js/ui/overview.js | 4 +-
js/ui/viewSelector.js | 253 +++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 242 insertions(+), 15 deletions(-)
---
diff --git a/js/ui/overview.js b/js/ui/overview.js
index 71ee8f6673..2805dc42c0 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -164,7 +164,7 @@ class OverviewActor extends St.BoxLayout {
init() {
this.add_constraint(new LayoutManager.MonitorConstraint({ primary: true }));
- this._controls = new OverviewControls.ControlsManager(this._searchEntry);
+ this._controls = new OverviewControls.ControlsManager();
// Add our same-line elements after the search entry
this.add_child(this._controls);
@@ -180,7 +180,7 @@ class OverviewActor extends St.BoxLayout {
}
get searchEntry() {
- return this._searchEntry;
+ return this._controls.viewSelector.searchEntry;
}
get viewSelector() {
diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js
index 2a0bb2539a..ea8c6301c5 100644
--- a/js/ui/viewSelector.js
+++ b/js/ui/viewSelector.js
@@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ViewSelector */
-const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi;
+const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;
const AppDisplay = imports.ui.appDisplay;
@@ -18,13 +18,16 @@ const IconGrid = imports.ui.iconGrid;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
var PINCH_GESTURE_THRESHOLD = 0.7;
+const SEARCH_ACTIVATION_TIMEOUT = 50;
+
var ViewPage = {
WINDOWS: 1,
APPS: 2,
};
const ViewsDisplayPage = {
- APP_GRID: 1
+ APP_GRID: 1,
+ SEARCH: 2,
};
var FocusTrap = GObject.registerClass(
@@ -126,26 +129,133 @@ var ShowOverviewAction = GObject.registerClass({
}
});
-var ViewsDisplayLayout = GObject.registerClass(
-class ViewsDisplayLayout extends Clutter.BoxLayout {
- _init(appDisplayActor) {
+var ViewsDisplayLayout = GObject.registerClass({
+ Properties: {
+ 'expansion': GObject.ParamSpec.float(
+ 'expansion',
+ 'expansion',
+ 'expansion',
+ GObject.ParamFlags.READWRITE,
+ 0, 1, 0),
+ },
+}, class ViewsDisplayLayout extends Clutter.BoxLayout {
+ _init(entry, appDisplay, searchResultsActor) {
super._init();
- this._appDisplayActor = appDisplayActor;
- this._appDisplayActor.connect('style-changed', this._onStyleChanged.bind(this));
+ this._entry = entry;
+ this._appDisplay = appDisplay;
+ this._searchResultsActor = searchResultsActor;
+
+ this._entry.connect('style-changed', this._onStyleChanged.bind(this));
+ this._appDisplay.connect('style-changed', this._onStyleChanged.bind(this));
+
+ this._heightAboveEntry = 0;
+ this.expansion = 0;
}
_onStyleChanged() {
this.layout_changed();
}
+ _centeredHeightAbove(height, availHeight) {
+ return Math.max(0, Math.floor((availHeight - height) / 2));
+ }
+
+ _computeAppDisplayPlacement(viewHeight, entryHeight, availHeight) {
+ // If we have the space for it, we add some padding to the top of the
+ // all view when calculating its centered position. This is to offset
+ // the icon labels at the bottom of the icon grid, so the icons
+ // themselves appears centered.
+ let themeNode = this._appDisplay.get_theme_node();
+ let topPadding = themeNode.get_length('-natural-padding-top');
+ let heightAbove = this._centeredHeightAbove(viewHeight + topPadding, availHeight);
+ let leftover = Math.max(availHeight - viewHeight - heightAbove, 0);
+ heightAbove += Math.min(topPadding, leftover);
+ // Always leave enough room for the search entry at the top
+ heightAbove = Math.max(entryHeight, heightAbove);
+ return heightAbove;
+ }
+
+ _computeChildrenAllocation(allocation) {
+ let availWidth = allocation.x2 - allocation.x1;
+ let availHeight = allocation.y2 - allocation.y1;
+
+ // Entry height
+ let entryHeight = this._entry.get_preferred_height(availWidth)[1];
+ let themeNode = this._entry.get_theme_node();
+ let entryMinPadding = themeNode.get_length('-minimum-vpadding');
+ let entryTopMargin = themeNode.get_length('margin-top');
+ entryHeight += entryMinPadding * 2;
+
+ // AppDisplay height
+ // Step 1: pre pre-allocate the grid with the maximum available size
+ this._appDisplay.adaptToSize(availWidth, availHeight - entryHeight);
+
+ // Use the maximum preferred size for now
+ let appDisplayHeight = this._appDisplay.get_preferred_height(availWidth)[1];
+
+ let heightAboveGrid = this._computeAppDisplayPlacement(appDisplayHeight, entryHeight, availHeight);
+ this._heightAboveEntry = this._centeredHeightAbove(entryHeight, heightAboveGrid);
+
+ let entryBox = allocation.copy();
+ entryBox.y1 = this._heightAboveEntry + entryTopMargin;
+ entryBox.y2 = entryBox.y1 + entryHeight;
+
+ let appDisplayBox = allocation.copy();
+ // The grid container box should have the dimensions of this container but start
+ // after the search entry and according to the calculated xplacement policies
+ appDisplayBox.y1 = heightAboveGrid;
+
+ let searchResultsBox = allocation.copy();
+
+ // The views clone does not have a searchResultsActor
+ if (this._searchResultsActor) {
+ let searchResultsHeight = availHeight - entryHeight;
+ searchResultsBox.x1 = allocation.x1;
+ searchResultsBox.x2 = allocation.x2;
+ searchResultsBox.y1 = entryBox.y2;
+ searchResultsBox.y2 = searchResultsBox.y1 + searchResultsHeight;
+ }
+
+ // Step 2: pre-allocate to a smaller, but realistic, size
+ this._appDisplay.adaptToSize(availWidth, appDisplayBox.get_height());
+
+ return [entryBox, appDisplayBox, searchResultsBox];
+ }
+
vfunc_allocate(actor, box, flags) {
- let availWidth = box.x2 - box.x1;
- let availHeight = box.y2 - box.y1;
+ let [entryBox, appDisplayBox, searchResultsBox] = this._computeChildrenAllocation(box);
+
+ this._entry.allocate(entryBox, flags);
- this._appDisplayActor.adaptToSize(availWidth, availHeight);
+ // Step 3: actually allocate the grid
+ this._appDisplay.allocate(appDisplayBox, flags);
- super.vfunc_allocate(actor, box, flags);
+ if (this._searchResultsActor)
+ this._searchResultsActor.allocate(searchResultsBox, flags);
+ }
+
+ set expansion(v) {
+ if (v === this._expansion || !this._searchResultsActor)
+ return;
+
+ this._appDisplay.visible = v !== 1;
+ this._searchResultsActor.visible = v !== 0;
+
+ this._appDisplay.opacity = (1 - v) * 255;
+ this._searchResultsActor.opacity = v * 255;
+
+ let entryTranslation = -this._heightAboveEntry * v;
+ this._entry.translation_y = entryTranslation;
+
+ this._searchResultsActor.translation_y = entryTranslation;
+
+ this._expansion = v;
+ this.notify('expansion');
+ }
+
+ get expansion() {
+ return this._expansion;
}
});
@@ -164,24 +274,137 @@ class ViewsDisplayConstraint extends LayoutManager.MonitorConstraint {
var ViewsDisplay = GObject.registerClass(
class ViewsDisplay extends St.Widget {
_init() {
+ this._enterSearchTimeoutId = 0;
this._activePage = ViewsDisplayPage.APP_GRID;
this._appDisplay = new AppDisplay.AppDisplay();
+ this._searchResults = new Search.SearchResultsView();
+ this._searchResults.connect('search-progress-updated', this._updateSpinner.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
+ // include the entry in the keynav tab path
+ this._focusTrap = new FocusTrap({ can_focus: true });
+ this._focusTrap.connect('key-focus-in', () => {
+ this._entry.grab_key_focus();
+ });
+ this._searchResults.add_actor(this._focusTrap);
+
+ global.focus_manager.add_group(this._searchResults);
+
+ this._entry = new ShellEntry.OverviewEntry();
+ this._entry.connect('search-activated', this._onSearchActivated.bind(this));
+ this._entry.connect('search-active-changed', this._onSearchActiveChanged.bind(this));
+ this._entry.connect('search-navigate-focus', this._onSearchNavigateFocus.bind(this));
+ this._entry.connect('search-terms-changed', this._onSearchTermsChanged.bind(this));
+
+ this._entry.clutter_text.connect('key-focus-in', () => {
+ this._searchResults.highlightDefault(true);
+ });
+ this._entry.clutter_text.connect('key-focus-out', () => {
+ this._searchResults.highlightDefault(false);
+ });
+
+ // Clicking on any empty area should exit search and get back to the desktop.
+ let clickAction = new Clutter.ClickAction();
+ clickAction.connect('clicked', this._resetSearch.bind(this));
+ Main.overview.addAction(clickAction, false);
+ this._searchResults.bind_property('mapped', clickAction,
+ 'enabled', GObject.BindingFlags.SYNC_CREATE);
+
super._init({
- layout_manager: new ViewsDisplayLayout(this._appDisplay),
+ layout_manager: new ViewsDisplayLayout(this._entry, this._appDisplay, this._searchResults),
x_expand: true,
y_expand: true,
});
+ this.add_child(this._entry);
this.add_actor(this._appDisplay);
+ this.add_child(this._searchResults);
}
- showPage(page) {
+ showPage(page, doAnimation) {
if (this._activePage === page)
return;
this._activePage = page;
+
+ let tweenTarget = page === ViewsDisplayPage.SEARCH ? 1 : 0;
+ if (doAnimation) {
+ this._searchResults.isAnimating = true;
+ this.ease_property('@layout.expansion', tweenTarget, {
+ duration: 250,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => {
+ this._searchResults.isAnimating = false;
+ },
+ });
+ } else {
+ this.layout_manager.expansion = tweenTarget;
+ }
+ }
+
+ _updateSpinner() {
+ this._entry.setSpinning(this._searchResults.searchInProgress);
+ }
+
+ _enterSearch() {
+ if (this._enterSearchTimeoutId > 0)
+ return;
+
+ // We give a very short time for search results to populate before
+ // triggering the animation, unless an animation is already in progress
+ if (this._searchResults.isAnimating) {
+ this.showPage(ViewsDisplayPage.SEARCH, true);
+ return;
+ }
+
+ this._enterSearchTimeoutId = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ SEARCH_ACTIVATION_TIMEOUT, () => {
+ this._enterSearchTimeoutId = 0;
+ this.showPage(ViewsDisplayPage.SEARCH, true);
+
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+
+ _leaveSearch() {
+ if (this._enterSearchTimeoutId > 0) {
+ GLib.source_remove(this._enterSearchTimeoutId);
+ this._enterSearchTimeoutId = 0;
+ }
+ this.showPage(ViewsDisplayPage.APP_GRID, true);
+ }
+
+ _onSearchActivated() {
+ this._searchResults.activateDefault();
+ this._resetSearch();
+ }
+
+ _onSearchActiveChanged() {
+ if (this._entry.active)
+ this._enterSearch();
+ else
+ this._leaveSearch();
+ }
+
+ _onSearchNavigateFocus(entry, direction) {
+ this._searchResults.navigateFocus(direction);
+ }
+
+ _onSearchTermsChanged() {
+ let terms = this._entry.getSearchTerms();
+ this._searchResults.setTerms(terms);
+ }
+
+ _resetSearch() {
+ this._entry.resetSearch();
+ }
+
+ get entry() {
+ return this._entry;
}
get appDisplay() {
@@ -477,4 +700,8 @@ var ViewSelector = GObject.registerClass({
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
+
+ get searchEntry() {
+ return this._viewsDisplay.entry;
+ }
});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]