[gnome-shell/app-picker-refresh: 16/16] appDisplay: Add additional view showing frequently used apps



commit 61cc42600e4cd90a5a4dae0c4838fcc2e2c13a0c
Author: Florian Müllner <fmuellner gnome org>
Date:   Mon Feb 18 20:18:08 2013 +0100

    appDisplay: Add additional view showing frequently used apps
    
    According to the design mockups, the app picker should follow the
    recent view pattern as used by applications, where the user is first
    offered a subset of applications he/she is likely to start, and only
    then allow switching to the full set of installed applications.
    So implement the ability to manage several views in AppDisplay and add
    FrequentView as additional view, which uses the existing ShellAppUsage
    to display a list of frequently used applications.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=694192

 data/theme/gnome-shell.css |   39 ++++++++++-
 js/ui/appDisplay.js        |  156 ++++++++++++++++++++++++++++++++++++++------
 2 files changed, 170 insertions(+), 25 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index e05d83a..52b0f69 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -848,13 +848,44 @@ StScrollBar StButton#vhandle:active {
 }
 
 .app-display {
-    padding: 0px 16px 32px 32px;
+    padding: 8px;
     spacing: 20px;
 }
 
-.app-display:rtl {
-    padding-left: 16px;
-    padding-right: 32px;
+.app-view-controls {
+    border-radius: 8px;
+    border: 1px solid rgba(245,245,245,0.6);
+    width: 250px;
+}
+
+.app-view-controls-bin {
+    padding-right: 10px;
+}
+
+.app-view-control {
+    padding: 4px 16px;
+    background-gradient-start: rgba(254,254,254,0.1);
+    background-gradient-end: rgba(5,5,6,0.1);
+    background-gradient-direction: vertical;
+}
+
+.app-view-control:first-child {
+    border-radius: 8px 0px 0px 8px;
+}
+
+.app-view-control:last-child {
+    border-radius: 0px 8px 8px 0px;
+}
+
+.app-view-control:checked {
+    background-gradient-start: rgba(0,0,0,0.8);
+    background-gradient-end: rgba(5,5,6,0.1);
+    background-gradient-direction: vertical;
+}
+
+StScrollView.frequent-apps StScrollBar {
+    min-width: 0px;
+    width: 0px;
 }
 
 .app-folder-icon {
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index b4c0a63..174dc12 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -20,6 +20,7 @@ const DND = imports.ui.dnd;
 const IconGrid = imports.ui.iconGrid;
 const Main = imports.ui.main;
 const Overview = imports.ui.overview;
+const OverviewControls = imports.ui.overviewControls;
 const PopupMenu = imports.ui.popupMenu;
 const Tweener = imports.ui.tweener;
 const Workspace = imports.ui.workspace;
@@ -233,61 +234,174 @@ const AlphabeticalView = new Lang.Class({
     }
 });
 
+const FrequentView = new Lang.Class({
+    Name: 'FrequentView',
+
+    _init: function() {
+        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
+                                             columnLimit: MAX_COLUMNS });
+        let box = new St.BoxLayout({ vertical: true });
+        box.add(this._grid.actor);
+
+        // HACK: IconGrid currently lacks API to only display items that match
+        // the allocation, so rather than clipping away eventual overflow, we
+        // use an unscrollable ScrollView with hidden scrollbars to nicely
+        // fade out cut off items
+        this.actor = new St.ScrollView({ x_fill: true,
+                                         y_fill: false,
+                                         y_align: St.Align.START,
+                                         x_expand: true,
+                                         reactive: false,
+                                         style_class: 'frequent-apps vfade' });
+        this.actor.add_actor(box);
+        this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+
+        this._usage = Shell.AppUsage.get_default();
+    },
+
+    removeAll: function() {
+        this._grid.removeAll();
+    },
+
+    loadApps: function() {
+        let mostUsed = this._usage.get_most_used ("");
+        for (let i = 0; i < mostUsed.length; i++) {
+            let appIcon = new AppIcon(mostUsed[i]);
+            this._grid.addItem(appIcon.actor, -1);
+        }
+    }
+});
+
+const Views = {
+    FREQUENT: 0,
+    ALL: 1
+};
+
 const AppDisplay = new Lang.Class({
     Name: 'AppDisplay',
 
     _init: function() {
         this._appSystem = Shell.AppSystem.get_default();
         this._appSystem.connect('installed-changed', Lang.bind(this, function() {
-            Main.queueDeferredWork(this._workId);
+            Main.queueDeferredWork(this._allAppsWorkId);
+        }));
+        Main.overview.connect('showing', Lang.bind(this, function() {
+            Main.queueDeferredWork(this._frequentAppsWorkId);
         }));
         global.settings.connect('changed::app-folder-categories', Lang.bind(this, function() {
             Main.queueDeferredWork(this._workId);
         }));
 
-        let box = new St.BoxLayout();
-        this.actor = new St.Bin({ child: box,
-                                  style_class: 'app-display',
-                                  x_fill: true, y_fill: true });
-
-        this._view = new AlphabeticalView({ scrollable: true });
-        box.add(this._view.actor, { expand: true });
+        this._views = [];
+
+        let view, button;
+        view = new FrequentView();
+        button = new St.Button({ label: _("Frequent"),
+                                 style_class: 'app-view-control',
+                                 can_focus: true,
+                                 x_expand: true });
+        this._views[Views.FREQUENT] = { 'view': view, 'control': button };
+
+        view = new AlphabeticalView({ scrollable: true });
+        button = new St.Button({ label: _("All"),
+                                 style_class: 'app-view-control',
+                                 can_focus: true,
+                                 x_expand: true });
+        this._views[Views.ALL] = { 'view': view, 'control': button };
+
+        this.actor = new St.BoxLayout({ style_class: 'app-display',
+                                        vertical: true,
+                                        x_expand: true, y_expand: true });
+
+        this._viewStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
+                                          x_expand: true, y_expand: true });
+        let bin = new St.Bin({ child: this._viewStack,
+                               clip_to_allocation: true,
+                               x_fill: true, y_fill: true });
+        this.actor.add(bin, { expand: true });
+
+        let layout = new Clutter.BoxLayout({ homogeneous: true });
+        this._controls = new St.Widget({ style_class: 'app-view-controls',
+                                         layout_manager: layout });
+        this.actor.add(new St.Bin({ child: this._controls }));
+
+
+        for (let i = 0; i < this._views.length; i++) {
+            this._viewStack.add_actor(this._views[i].view.actor);
+            this._controls.add_actor(this._views[i].control);
+
+            let viewIndex = i;
+            this._views[i].control.connect('clicked', Lang.bind(this,
+                function(actor) {
+                    this._showView(viewIndex);
+                }));
+        }
+        this._showView(Views.FREQUENT);
 
         // We need a dummy actor to catch the keyboard focus if the
         // user Ctrl-Alt-Tabs here before the deferred work creates
         // our real contents
         this._focusDummy = new St.Bin({ can_focus: true });
-        box.add(this._focusDummy);
+        this._viewStack.add_actor(this._focusDummy);
 
-        this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
+        this._allAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, 
this._redisplayAllApps));
+        this._frequentAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, 
this._redisplayFrequentApps));
     },
 
-    _removeAll: function() {
-        this._view.removeAll();
+    _showView: function(activeIndex) {
+        for (let i = 0; i < this._views.length; i++) {
+            let actor = this._views[i].view.actor;
+            let params = { time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+                           opacity: (i == activeIndex) ? 255 : 0 };
+            if (i == activeIndex)
+                actor.visible = true;
+            else
+                params.onComplete = function() { actor.hide(); };
+            Tweener.addTween(actor, params);
+
+            if (i == activeIndex)
+                this._views[i].control.add_style_pseudo_class('checked');
+            else
+                this._views[i].control.remove_style_pseudo_class('checked');
+        }
     },
 
     _redisplay: function() {
-        this._removeAll();
+        this._redisplayFrequentApps();
+        this._redisplayAllApps();
+    },
+
+    _redisplayFrequentApps: function() {
+        let view = this._views[Views.FREQUENT].view;
+
+        view.removeAll();
+        view.loadApps();
+    },
+
+    _redisplayAllApps: function() {
+        let view = this._views[Views.ALL].view;
+
+        view.removeAll();
 
-        var tree = this._appSystem.get_tree();
-        var root = tree.get_root_directory();
+        let tree = this._appSystem.get_tree();
+        let root = tree.get_root_directory();
 
-        var iter = root.iter();
-        var nextType;
+        let iter = root.iter();
+        let nextType;
         let folderCategories = global.settings.get_strv('app-folder-categories');
         while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
             if (nextType == GMenu.TreeItemType.DIRECTORY) {
-                var dir = iter.get_directory();
+                let dir = iter.get_directory();
                 if (dir.get_is_nodisplay())
                     continue;
 
                 if (folderCategories.indexOf(dir.get_menu_id()) != -1)
-                    this._view.addFolder(dir);
+                    view.addFolder(dir);
                 else
-                    _loadCategory(dir, this._view);
+                    _loadCategory(dir, view);
             }
         }
-        this._view.loadGrid();
+        view.loadGrid();
 
         if (this._focusDummy) {
             let focused = this._focusDummy.has_key_focus();


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