[gnome-shell/app-picker-refresh: 10/16] appDisplay: Add new FolderIcon class



commit 6e3e2d9f29e48513239d2e2c4912737b10e17f37
Author: Florian Müllner <fmuellner gnome org>
Date:   Thu Jan 31 17:13:37 2013 +0100

    appDisplay: Add new FolderIcon class
    
    FolderIcons will appear in the primary app view along AppIcons, but
    represent a group of applications rather than a single application.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=694192

 data/theme/gnome-shell.css |   21 +++++
 js/ui/appDisplay.js        |  194 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 215 insertions(+), 0 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 41f9965..e05d83a 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -857,6 +857,10 @@ StScrollBar StButton#vhandle:active {
     padding-right: 32px;
 }
 
+.app-folder-icon {
+    padding: 5px;
+}
+
 .dash-item-container > StButton {
     padding: 4px 8px;
 }
@@ -896,12 +900,27 @@ StScrollBar StButton#vhandle:active {
     text-align: center;
 }
 
+.app-folder-popup {
+    -arrow-border-radius: 8px;
+    -arrow-background-color: black;
+    -arrow-base: 24px;
+    -arrow-rise: 11px;
+}
+
+.app-folder-popup-bin {
+    padding: 15px;
+}
+
 .app-well-app.running > .overview-icon {
     text-shadow: black 0px 2px 2px;
     background-image: url("running-indicator.svg");
     background-size: contain;
 }
 
+.app-well-app.app-folder > .overview-icon {
+    background-color: rgba(0,0,0,0.5);
+}
+
 .app-well-app:hover > .overview-icon,
 .show-apps:hover > .overview-icon,
 .search-provider-icon:hover,
@@ -925,6 +944,8 @@ StScrollBar StButton#vhandle:active {
     color: white;
 }
 
+.app-well-app:checked > .overview-icon,
+.app-well-app:active > .overview-icon,
 .show-apps:checked > .overview-icon,
 .show-apps:active > .overview-icon {
     background-gradient-start: rgba(255, 255, 255, .05);
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 457bcdc..cc4a665 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -3,6 +3,7 @@
 const Clutter = imports.gi.Clutter;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
 const Gtk = imports.gi.Gtk;
 const GMenu = imports.gi.GMenu;
 const Shell = imports.gi.Shell;
@@ -14,6 +15,7 @@ const Mainloop = imports.mainloop;
 const Atk = imports.gi.Atk;
 
 const AppFavorites = imports.ui.appFavorites;
+const BoxPointer = imports.ui.boxpointer;
 const DND = imports.ui.dnd;
 const IconGrid = imports.ui.iconGrid;
 const Main = imports.ui.main;
@@ -29,6 +31,9 @@ const MENU_POPUP_TIMEOUT = 600;
 const SCROLL_TIME = 0.1;
 const MAX_COLUMNS = 6;
 
+const FOLDER_SUBICON_FRACTION = .4;
+
+
 // Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
 function _loadCategory(dir, view) {
     let iter = dir.iter();
@@ -93,6 +98,51 @@ const AlphabeticalView = new Lang.Class({
     }
 });
 
+const FolderView = new Lang.Class({
+    Name: 'FolderView',
+    Extends: AlphabeticalView,
+
+    _init: function() {
+        this.parent();
+        this.actor = this._grid.actor;
+    },
+
+    _getItemId: function(item) {
+        return item.get_id();
+    },
+
+    _createItemIcon: function(item) {
+        return new AppIcon(item);
+    },
+
+    _compareItems: function(a, b) {
+        return a.compare_by_name(b);
+    },
+
+    addApp: function(app) {
+        this._addItem(app);
+    },
+
+    createFolderIcon: function(size) {
+        let icon = new St.Widget({ layout_manager: new Clutter.BinLayout(),
+                                   style_class: 'app-folder-icon',
+                                   width: size, height: size });
+        let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
+
+        let aligns = [ Clutter.ActorAlign.START, Clutter.ActorAlign.END ];
+        for (let i = 0; i < Math.min(this._allItems.length, 4); i++) {
+            let texture = this._allItems[i].create_icon_texture(subSize);
+            let bin = new St.Bin({ child: texture,
+                                   x_expand: true, y_expand: true });
+            bin.set_x_align(aligns[i % 2]);
+            bin.set_y_align(aligns[Math.floor(i / 2)]);
+            icon.add_actor(bin);
+        }
+
+        return icon;
+    }
+});
+
 const AllView = new Lang.Class({
     Name: 'AllView',
     Extends: AlphabeticalView,
@@ -145,6 +195,11 @@ const AllView = new Lang.Class({
 
     addFolderPopup: function(popup) {
         this._stack.add_actor(popup.actor);
+        popup.connect('open-state-changed', Lang.bind(this,
+            function(popup, isOpen) {
+                if (isOpen)
+                    this._ensureIconVisible(popup.actor);
+            }));
     },
 
     _ensureIconVisible: function(icon) {
@@ -288,6 +343,145 @@ const AppSearchProvider = new Lang.Class({
     }
 });
 
+const FolderIcon = new Lang.Class({
+    Name: 'FolderIcon',
+
+    _init: function(dir, parentView) {
+        this._dir = dir;
+        this._parentView = parentView;
+
+        this.actor = new St.Button({ style_class: 'app-well-app app-folder',
+                                     button_mask: St.ButtonMask.ONE,
+                                     toggle_mode: true,
+                                     can_focus: true,
+                                     x_fill: true,
+                                     y_fill: true });
+        this.actor._delegate = this;
+
+        let label = this._dir.get_name();
+        this.icon = new IconGrid.BaseIcon(label,
+                                          { createIcon: Lang.bind(this, this._createIcon) });
+        this.actor.set_child(this.icon.actor);
+        this.actor.label_actor = this.icon.label;
+
+        this.view = new FolderView();
+        this.view.actor.reactive = false;
+        _loadCategory(dir, this.view);
+
+        this.actor.connect('clicked', Lang.bind(this,
+            function() {
+                this._ensurePopup();
+                this._popup.toggle();
+            }));
+        this.actor.connect('notify::mapped', Lang.bind(this,
+            function() {
+                if (!this.actor.mapped && this._popup)
+                    this._popup.popdown();
+            }));
+    },
+
+    _createIcon: function(size) {
+        return this.view.createFolderIcon(size);
+    },
+
+    _ensurePopup: function() {
+        if (this._popup)
+            return;
+
+        let spaceTop = this.actor.y;
+        let spaceBottom = this._parentView.actor.height - (this.actor.y + this.actor.height);
+        let side = spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
+
+        this._popup = new AppFolderPopup(this, side);
+        this._parentView.addFolderPopup(this._popup);
+        let constraint = new Clutter.AlignConstraint({ source: this._parentView.actor,
+                                                       align_axis: Clutter.AlignAxis.X_AXIS,
+                                                       factor: 0.5 });
+        this._popup.actor.add_constraint(constraint);
+
+        // Position the popup above or below the source icon
+        if (side == St.Side.BOTTOM) {
+            this._popup.actor.show();
+            this._popup.actor.y = this.actor.y - this._popup.actor.height;
+            this._popup.actor.hide();
+        } else {
+            this._popup.actor.y = this.actor.y + this.actor.height;
+        }
+
+        this._popup.connect('open-state-changed', Lang.bind(this,
+            function(popup, isOpen) {
+                if (!isOpen)
+                    this.actor.checked = false;
+            }));
+    },
+});
+
+const AppFolderPopup = new Lang.Class({
+    Name: 'AppFolderPopup',
+
+    _init: function(source, side) {
+        this._source = source;
+        this._view = source.view;
+        this._arrowSide = side;
+
+        this._isOpen = false;
+
+        this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
+                                     visible: false });
+        this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
+                                                     { style_class: 'app-folder-popup-bin',
+                                                       x_fill: true,
+                                                       y_fill: true,
+                                                       x_align: St.Align.START });
+
+        this._boxPointer.actor.style_class = 'app-folder-popup';
+        this.actor.add_actor(this._boxPointer.actor);
+        this._boxPointer.bin.set_child(this._view.actor);
+
+        let closeButton = Util.makeCloseButton();
+        closeButton.connect('clicked', Lang.bind(this, this.popdown));
+        this.actor.add_actor(closeButton);
+
+        this._boxPointer.actor.bind_property('opacity', closeButton, 'opacity',
+                                             GObject.BindingFlags.SYNC_CREATE);
+
+        source.actor.connect('destroy', Lang.bind(this,
+            function() {
+                this.actor.destroy();
+            }));
+    },
+
+    toggle: function() {
+        if (this._isOpen)
+            this.popdown();
+        else
+            this.popup();
+    },
+
+    popup: function() {
+        if (this._isOpen)
+            return;
+
+        this.actor.show();
+        this._boxPointer.setArrowOrigin(this._source.actor.x + this._source.actor.width / 2);
+        this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
+                              BoxPointer.PopupAnimation.SLIDE);
+
+        this._isOpen = true;
+        this.emit('open-state-changed', true);
+    },
+
+    popdown: function() {
+        if (!this._isOpen)
+            return;
+
+        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
+                              BoxPointer.PopupAnimation.SLIDE);
+        this._isOpen = false;
+        this.emit('open-state-changed', false);
+    }
+});
+Signals.addSignalMethods(AppFolderPopup.prototype);
 
 const AppIcon = new Lang.Class({
     Name: 'AppIcon',


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