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
This commit is contained in:
Tanner Doshier 2012-08-01 20:48:10 -05:00 committed by Cosimo Cecchi
parent 575ab0d609
commit c5d8484e19
5 changed files with 217 additions and 99 deletions

View File

@ -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,11 +768,22 @@ 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-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-content {
/* This is the space between the provider icon and the results container */
spacing: 32px;
}
.search-statustext {
@ -781,16 +792,8 @@ StScrollBar StButton#vhandle:active {
font-weight: bold;
}
.search-section-results {
padding: 6px;
}
.search-section-list-results {
spacing: 4px;
}
.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 {
width: 16px;
height: 16px;
@ -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);
}

View File

@ -403,12 +403,6 @@ const SettingsSearchProvider = new Lang.Class({
pref.activate();
},
createResultActor: function (resultMeta, terms) {
let app = resultMeta['id'];
let icon = new AppWellIcon(app);
return icon.actor;
},
launchSearch: function(terms) {
// FIXME: this should be a remote search provider
this.appInfo.launch([], global.create_app_launch_context());

View File

@ -154,9 +154,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;

View File

@ -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_LIST_SEARCH_RESULTS_ROWS = 3;
const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
const SearchResult = new Lang.Class({
Name: 'SearchResult',
@ -23,21 +24,91 @@ const SearchResult = new Lang.Class({
this.provider = provider;
this.metaInfo = metaInfo;
this.terms = terms;
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, this.terms);
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';
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);
@ -47,10 +118,8 @@ const SearchResult = new Lang.Class({
if (content._delegate && 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',
@ -72,22 +141,6 @@ const SearchResult = new Lang.Class({
this._dragActorSource = dragSource;
},
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, this.terms);
Main.overview.toggle();
},
_onResultClicked: function(actor) {
this.activate();
},
getDragActorSource: function() {
return this._dragActorSource;
},
@ -104,6 +157,77 @@ 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: 'search-section-content' });
this.providerIcon = new ProviderIcon(provider);
this.providerIcon.connect('clicked', Lang.bind(this,
function() {
provider.launchSearch(this._terms);
Main.overview.toggle();
}));
this.actor.add(this.providerIcon, { x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
this._content = new St.BoxLayout({ style_class: 'list-search-results',
vertical: true });
this.actor.add_actor(this._content);
this._notDisplayedResult = [];
this._terms = [];
this._pendingClear = false;
},
getResultsForDisplay: function() {
let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount();
let canDisplay = MAX_LIST_SEARCH_RESULTS_ROWS - alreadyVisible;
let newResults = this._notDisplayedResult.splice(0, canDisplay);
return newResults;
},
getVisibleResultCount: function() {
return this._content.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._content.add_actor(display.actor);
}
},
clear: function () {
this._content.destroy_all_children();
this._pendingClear = false;
},
getFirstResult: function() {
if (this.getVisibleResultCount() > 0)
return this._content.get_child_at_index(0)._delegate;
else
return null;
}
});
const GridSearchResults = new Lang.Class({
Name: 'GridSearchResults',
@ -112,22 +236,12 @@ 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 });
this.actor = new St.Bin({ x_align: St.Align.MIDDLE });
this.actor.set_child(this._grid.actor);
this._width = 0;
this.actor.connect('notify::width', Lang.bind(this, function() {
this._width = this.actor.width;
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;
@ -135,12 +249,11 @@ const GridSearchResults = new Lang.Class({
getResultsForDisplay: function() {
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit()
let canDisplay = this._grid.childrenInRow(this.actor.width) * this._grid.getRowLimit()
- alreadyVisible;
let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
return this._notDisplayedResult.splice(0, numResults);
let newResults = this._notDisplayedResult.splice(0, canDisplay);
return newResults;
},
getVisibleResultCount: function() {
@ -160,7 +273,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);
}
},
@ -229,29 +342,25 @@ 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 providerIcon = null;
let resultDisplay = null;
if (provider.appInfo) {
providerIcon = new ProviderIcon(provider);
providerIcon.connect('clicked', Lang.bind(this,
function() {
provider.launchSearch(this._searchSystem.getTerms());
Main.overview.toggle();
}));
providerBox.add(providerIcon, { x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.START });
resultDisplay = new ListSearchResults(provider);
providerIcon = resultDisplay.providerIcon;
} 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);
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
providerBox.add(separator.actor);
this._providerMeta.push({ provider: provider,
actor: providerBox,
@ -412,11 +521,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);
}
}
});

View File

@ -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);