From 690be611eee4c00b56acd6f7d0c94b98b662cabf Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Tue, 16 Feb 2010 03:50:36 +0300 Subject: [PATCH] Implement newer design for "more apps" view Replace the old GenericDisplay-based system with one which reuses the WellGrid class and uses a new scrolling container. https://bugzilla.gnome.org/show_bug.cgi?id=609015 --- data/Makefile.am | 1 + data/theme/gnome-shell.css | 28 +++++ js/ui/appDisplay.js | 248 +++++++++++++------------------------ js/ui/dash.js | 27 ++-- js/ui/overview.js | 13 +- 5 files changed, 130 insertions(+), 187 deletions(-) diff --git a/data/Makefile.am b/data/Makefile.am index ccd6263f6..c19108460 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -35,6 +35,7 @@ dist_theme_DATA = \ theme/scroll-vhandle.svg \ theme/section-back.svg \ theme/section-more.svg \ + theme/section-more-open.svg \ theme/single-view-active.svg \ theme/single-view.svg \ theme/ws-switch-arrow-left.svg \ diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 1420b1ed9..bf18b0533 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -307,6 +307,12 @@ StTooltip { height: 9px; } +.more-link-expander.open { + background-image: url("section-more-open.svg"); + width: 9px; + height: 9px; +} + .dash-pane { background-color: rgba(0,0,0,0.95); border: 1px solid #262626; @@ -383,11 +389,33 @@ StTooltip { /* Apps */ +.overview-pane { + width: 440px; +} + #dashAppWell { spacing: 6px; -shell-grid-item-size: 70px; } +.all-app { + border-radius: 10px; + background-color: rgba(0,0,0,0.95); + border: 1px solid #262626; + color: #ffffff; + height: 400px; +} + +.all-app-controls-panel { + height: 30px; +} + +.all-app-scroll-view { + padding-right: 10px; + padding-left: 10px; + padding-bottom: 10px; +} + .app-well-app { border: 1px solid #181818; border-radius: 4px; diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 026ec1102..c30085137 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -24,195 +24,110 @@ const Workspace = imports.ui.workspace; const APPICON_SIZE = 48; const WELL_MAX_COLUMNS = 8; -/* This class represents a single display item containing information about an application. - * - * appInfo - AppInfo object containing information about the application - */ -function AppDisplayItem(appInfo) { - this._init(appInfo); +function AllAppView() { + this._init(); } -AppDisplayItem.prototype = { - __proto__: GenericDisplay.GenericDisplayItem.prototype, - - _init : function(appInfo) { - GenericDisplay.GenericDisplayItem.prototype._init.call(this); - this._appInfo = appInfo; - - this._setItemInfo(appInfo.get_name(), appInfo.get_description()); +AllAppView.prototype = { + _init: function(apps) { + this.actor = new St.BoxLayout({ vertical: true }); + this._grid = new WellGrid(true); + this._appSystem = Shell.AppSystem.get_default(); + this.actor.add(this._grid.actor, { y_align: St.Align.START, expand: true }); }, - getId: function() { - return this._appInfo.get_id(); + _removeAll: function() { + this._grid.removeAll(); + this._apps = []; }, - //// Public method overrides //// + _addApp: function(app) { + let App = new AppWellIcon(this._appSystem.get_app(app.get_id())); + App.connect('launching', Lang.bind(this, function() { + this.emit('launching'); + })); + App._draggable.connect('drag-begin', Lang.bind(this, function() { + this.emit('drag-begin'); + })); - // Opens an application represented by this display item. - launch : function() { - let appSys = Shell.AppSystem.get_default(); - let app = appSys.get_app(this._appInfo.get_id()); - let windows = app.get_windows(); - if (windows.length > 0) { - let mostRecentWindow = windows[0]; - Main.activateWindow(mostRecentWindow); - } else { - this._appInfo.launch(); + this._grid.addItem(App.actor); + + this._apps.push(App); + }, + + refresh: function(apps) { + let ids = []; + for (let i in apps) + ids.push(i); + ids.sort(function(a, b) { + return apps[a].get_name().localeCompare(apps[b].get_name()); + }); + + this._removeAll(); + + for (let i = 0; i < ids.length; i++) { + this._addApp(apps[ids[i]]); } - }, - - //// Protected method overrides //// - - // Returns an icon for the item. - _createIcon : function() { - return this._appInfo.create_icon_texture(GenericDisplay.ITEM_DISPLAY_ICON_SIZE); - }, - - // Returns a preview icon for the item. - _createPreviewIcon : function() { - return this._appInfo.create_icon_texture(GenericDisplay.PREVIEW_ICON_SIZE); - }, - - shellWorkspaceLaunch: function() { - this.launch(); } }; +Signals.addSignalMethods(AllAppView.prototype); + /* This class represents a display containing a collection of application items. - * The applications are sorted based on their popularity by default, and based on - * their name if some search filter is applied. - * - * showPrefs - a boolean indicating if this AppDisplay should contain preference - * applets, rather than applications + * The applications are sorted based on their name. */ -function AppDisplay(showPrefs, flags) { - this._init(showPrefs, flags); +function AllAppDisplay() { + this._init(); } -AppDisplay.prototype = { - __proto__: GenericDisplay.GenericDisplay.prototype, - - _init : function(showPrefs, flags) { - GenericDisplay.GenericDisplay.prototype._init.call(this, flags); - - this._showPrefs = showPrefs; - - this._menus = []; - this._menuDisplays = []; - // map> - // We use a map of appIds instead of an array to ensure that we don't have duplicates and for easier lookup. - this._menuSearchAppMatches = {}; - +AllAppDisplay.prototype = { + _init: function() { this._appSystem = Shell.AppSystem.get_default(); - this._appsStale = true; - this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) { - this._appsStale = true; - this._redisplay(GenericDisplay.RedisplayFlags.NONE); + this._appSystem.connect('installed-changed', Lang.bind(this, function() { + Main.queueDeferredWork(this._workId); })); + + let bin = new St.BoxLayout({ style_class: 'all-app-controls-panel' }); + this.actor = new St.BoxLayout({ style_class: 'all-app', vertical: true }); + this.actor.hide(); + + let view = new St.ScrollView({ x_fill: true, y_fill: false, style_class: 'all-app-scroll-view' }); + this._scrollView = view; + this.actor.add(bin); + this.actor.add(view, { expand: true, y_fill: false, y_align: St.Align.START }); + + this._appView = new AllAppView(); + this._appView.connect('launching', Lang.bind(this, this.close)); + this._appView.connect('drag-begin', Lang.bind(this, this.close)); + this._scrollView.add_actor(this._appView.actor); + + this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS); + + this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); }, - //// Private //// + _redisplay: function() { + let apps = this._appSystem.get_flattened_apps().filter(function(app) { + return !app.get_is_nodisplay(); + }); - _addApp: function(appInfo) { - let appId = appInfo.get_id(); - this._allItems[appId] = appInfo; + this._appView.refresh(apps); }, - //// Protected method overrides //// + toggle: function() { + this.emit('open-state-changed', !this.actor.visible); - // Gets information about all applications by calling Gio.app_info_get_all(). - _refreshCache : function() { - if (!this._appsStale) - return true; - this._allItems = {}; - - if (this._showPrefs) { - // Get the desktop file ids for settings/preferences. - // These are used for search results, but not in the app menus. - let settings = this._appSystem.get_all_settings(); - for (let i = 0; i < settings.length; i++) { - let app = settings[i]; - this._addApp(app); - } - } else { - let apps = this._appSystem.get_flattened_apps(); - for (let i = 0; i < apps.length; i++) { - let app = apps[i]; - this._addApp(app); - } - } - - this._appsStale = false; - return false; + this.actor.visible = !this.actor.visible; }, - _setDefaultList : function() { - this._matchedItems = this._allItems; - this._matchedItemKeys = []; - for (let itemId in this._matchedItems) { - let app = this._allItems[itemId]; - if (app.get_is_nodisplay()) - continue; - this._matchedItemKeys.push(itemId); - } - this._matchedItemKeys.sort(Lang.bind(this, this._compareItems)); - }, - - // Compares items associated with the item ids based on the alphabetical order - // of the item names. - // Returns an integer value indicating the result of the comparison. - _compareItems : function(itemIdA, itemIdB) { - let appA = this._allItems[itemIdA]; - let appB = this._allItems[itemIdB]; - return appA.get_name().localeCompare(appB.get_name()); - }, - - // Checks if the item info can be a match for the search string by checking - // the name, description, execution command, and category for the application. - // Item info is expected to be Shell.AppInfo. - // Returns a boolean flag indicating if itemInfo is a match. - _isInfoMatching : function(itemInfo, search) { - // Don't show nodisplay items here - if (itemInfo.get_is_nodisplay()) - return false; - - if (search == null || search == '') - return true; - - let fold = function(s) { - if (!s) - return s; - return GLib.utf8_casefold(GLib.utf8_normalize(s, -1, - GLib.NormalizeMode.ALL), -1); - }; - let name = fold(itemInfo.get_name()); - if (name.indexOf(search) >= 0) - return true; - - let description = fold(itemInfo.get_description()); - if (description) { - if (description.indexOf(search) >= 0) - return true; - } - - let exec = fold(itemInfo.get_executable()); - if (exec == null) { - log("Missing an executable for " + itemInfo.name); - } else { - if (exec.indexOf(search) >= 0) - return true; - } - - return false; - }, - - // Creates an AppDisplayItem based on itemInfo, which is expected be an Shell.AppInfo object. - _createDisplayItem: function(itemInfo) { - return new AppDisplayItem(itemInfo); + close: function() { + if (!this.actor.visible) + return; + this.toggle(); } }; -Signals.addSignalMethods(AppDisplay.prototype); +Signals.addSignalMethods(AllAppDisplay.prototype); function BaseAppSearchProvider() { this._init(); @@ -414,6 +329,10 @@ AppWellIcon.prototype = { return false; }, + getId: function() { + return this.app.get_id(); + }, + popupMenu: function(activatingButton) { if (!this._menu) { this._menu = new AppIconMenu(this); @@ -482,6 +401,7 @@ AppWellIcon.prototype = { _onActivate: function (event) { let running = this._getRunning(); + this.emit('launching'); if (!running) { this.app.launch(); @@ -826,12 +746,12 @@ function WellGrid() { WellGrid.prototype = { _init: function() { - this.actor = new St.Bin({ name: "dashAppWell" }); + this.actor = new St.BoxLayout({ name: "dashAppWell", vertical: true }); // Pulled from CSS, but hardcode some defaults here this._spacing = 0; this._item_size = 48; this._grid = new Shell.GenericContainer(); - this.actor.set_child(this._grid); + this.actor.add(this._grid, { expand: true, y_align: St.Align.START }); this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged)); this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); @@ -1021,7 +941,7 @@ AppWell.prototype = { // Draggable target interface acceptDrop : function(source, actor, x, y, time) { let app = null; - if (source instanceof AppDisplayItem) { + if (source instanceof AppWellIcon) { app = this._appSystem.get_app(source.getId()); } else if (source instanceof Workspace.WindowClone) { app = this._tracker.get_window_app(source.metaWindow); diff --git a/js/ui/dash.js b/js/ui/dash.js index 38a8590c6..3278b11eb 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -50,8 +50,6 @@ SEARCH_BORDER_BOTTOM_COLOR.from_pixel(0x191919ff); const BROWSE_ACTIVATED_BG = new Clutter.Color(); BROWSE_ACTIVATED_BG.from_pixel(0x303030f0); -const APPS = "apps"; -const PREFS = "prefs"; const DOCS = "docs"; const PLACES = "places"; @@ -69,11 +67,7 @@ function _getIndexWrapped(index, increment, length) { } function _createDisplay(displayType, flags) { - if (displayType == APPS) - return new AppDisplay.AppDisplay(false, flags); - else if (displayType == PREFS) - return new AppDisplay.AppDisplay(true, flags); - else if (displayType == DOCS) + if (displayType == DOCS) return new DocDisplay.DocDisplay(flags); else if (displayType == PLACES) return new PlaceDisplay.PlaceDisplay(flags); @@ -619,8 +613,8 @@ MoreLink.prototype = { reactive: true }); this.pane = null; - let expander = new St.Bin({ style_class: "more-link-expander" }); - this.actor.add(expander, { expand: true, y_fill: false }); + this._expander = new St.Bin({ style_class: "more-link-expander" }); + this.actor.add(this._expander, { expand: true, y_fill: false }); }, activate: function() { @@ -635,6 +629,10 @@ MoreLink.prototype = { setPane: function (pane) { this._pane = pane; this._pane.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) { + if (!isOpen) + this._expander.style_class = 'more-link-expander'; + else + this._expander.style_class = 'more-link-expander open'; })); } } @@ -884,13 +882,12 @@ Dash.prototype = { let appWell = new AppDisplay.AppWell(); this._appsSection.content.add(appWell.actor, { expand: true }); - this._moreAppsPane = null; + this._allApps = null; this._appsSection.header.moreLink.connect('activated', Lang.bind(this, function (link) { - if (this._moreAppsPane == null) { - this._moreAppsPane = new ResultPane(this); - this._moreAppsPane.packResults(APPS); - this._addPane(this._moreAppsPane); - link.setPane(this._moreAppsPane); + if (this._allApps == null) { + this._allApps = new AppDisplay.AllAppDisplay(); + this._addPane(this._allApps); + link.setPane(this._allApps); } })); diff --git a/js/ui/overview.js b/js/ui/overview.js index 50edd9cf0..4db84d1f4 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -234,8 +234,7 @@ Overview.prototype = { this._group.add_actor(this._dash.actor); // Container to hold popup pane chrome. - this._paneContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL, - spacing: 6 }); + this._paneContainer = new St.BoxLayout({ style_class: 'overview-pane' }); // Note here we explicitly don't set the paneContainer to be reactive yet; that's done // inside the notify::visible handler on panes. this._paneContainer.connect('button-release-event', Lang.bind(this, function(background) { @@ -245,7 +244,7 @@ Overview.prototype = { this._group.add_actor(this._paneContainer); this._transparentBackground.lower_bottom(); - this._paneContainer.lower_bottom(); + this._paneContainer.hide(); this._coverPane.lower_bottom(); @@ -362,12 +361,10 @@ Overview.prototype = { this._transparentBackground.set_size(primary.width - this._paneContainer.x, this._paneContainer.height); - if (this._activeDisplayPane != null) - this._activeDisplayPane.actor.width = displayGridColumnWidth * 2; }, addPane: function (pane) { - this._paneContainer.append(pane.actor, Big.BoxPackFlags.NONE); + this._paneContainer.add(pane.actor, { expand: true, y_fill: false, y_align: St.Align.START }); // When a pane is displayed, we raise the transparent background to the top // and connect to button-release-event on it, then raise the pane above that. // The idea here is that clicking anywhere outside the pane should close it. @@ -375,10 +372,10 @@ Overview.prototype = { let backgroundEventId = null; pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) { if (isOpen) { - pane.actor.width = displayGridColumnWidth * 2; this._activeDisplayPane = pane; this._transparentBackground.raise_top(); this._paneContainer.raise_top(); + this._paneContainer.show(); if (backgroundEventId != null) this._transparentBackground.disconnect(backgroundEventId); backgroundEventId = this._transparentBackground.connect('button-release-event', Lang.bind(this, function () { @@ -393,7 +390,7 @@ Overview.prototype = { backgroundEventId = null; } this._transparentBackground.lower_bottom(); - this._paneContainer.lower_bottom(); + this._paneContainer.hide(); this._workspaces.actor.opacity = 255; } }));