[gnome-shell/wip/paging-release2: 8/23] appDisplay: Use new IconGrid implementation in AllView and FrequentView



commit 7bdec38b2811463debb6be8454e6a12c17a6b927
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Tue Aug 27 21:22:16 2013 +0200

    appDisplay: Use new IconGrid implementation in AllView and FrequentView
    
    Add "usePagination" parameter to Alphabetical view to allow AllView
    use the new PaginatedIconGrid.
    FrequentView adapts the new IconGrid implementation. Create a
    PagesView class to allow us control the allocation of the IconGrid
    child and the scroll adjustment. Hook up the allocation/size comunication
    between the FrequentView, AllView and FolderView with AppDisplay with
    a ViewStackLayout that updates all views.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=706081

 data/theme/gnome-shell.css |    3 +-
 js/ui/appDisplay.js        |  240 ++++++++++++++++++++++++++++++--------------
 js/ui/iconGrid.js          |   15 +++-
 3 files changed, 179 insertions(+), 79 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 02aa604..48a7553 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -933,9 +933,8 @@ StScrollBar StButton#vhandle:active {
     padding: 3px 31px;
 }
 
-
 .search-display > StBoxLayout,
-.all-apps > StBoxLayout,
+.all-apps,
 .frequent-apps > StBoxLayout {
     /* horizontal padding to make sure scrollbars or dash don't overlap content */
     padding: 0px 88px;
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index cbe113a..97b99fb 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -35,6 +35,7 @@ const INACTIVE_GRID_OPACITY = 77;
 const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
 const FOLDER_SUBICON_FRACTION = .4;
 
+const PAGE_SWITCH_TIME = 0.3;
 
 // Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
 function _loadCategory(dir, view) {
@@ -59,9 +60,16 @@ const BaseAppView = new Lang.Class({
     Name: 'BaseAppView',
     Abstract: true,
 
-    _init: function() {
-        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-                                             columnLimit: MAX_COLUMNS });
+    _init: function(params, gridParams) {
+        gridParams = Params.parse(gridParams, { xAlign: St.Align.MIDDLE,
+                                                columnLimit: MAX_COLUMNS,
+                                                fillParent: false });
+        params = Params.parse(params, { usePagination: false });
+        
+        if(params.usePagination)
+            this._grid = new IconGrid.PaginatedIconGrid(gridParams);
+        else
+            this._grid = new IconGrid.IconGrid(gridParams);
 
         // Standard hack for ClutterBinLayout
         this._grid.actor.x_expand = true;
@@ -112,27 +120,22 @@ const BaseAppView = new Lang.Class({
     }
 });
 
-const AllViewLayout = new Lang.Class({
-    Name: 'AllViewLayout',
-    Extends: Clutter.BinLayout,
-
-    vfunc_get_preferred_height: function(container, forWidth) {
-        let minBottom = 0;
-        let naturalBottom = 0;
-
-        for (let child = container.get_first_child();
-             child;
-             child = child.get_next_sibling()) {
-            let childY = child.y;
-            let [childMin, childNatural] = child.get_preferred_height(forWidth);
-
-            if (childMin + childY > minBottom)
-                minBottom = childMin + childY;
+/**
+* Since St.Bin expands to child size, and we don't want that
+* we have to create a custom class that returns 0 to its
+* get_preferred_height/width. In this manner the parent
+* will not take into account child preferred height/width
+*/
+const PagesView = new Lang.Class({
+    Name: 'PagesView',
+    Extends: St.Bin,
+
+    vfunc_get_preferred_height: function (forWidth) {
+        return [0, 0];
+    },
 
-            if (childNatural + childY > naturalBottom)
-                naturalBottom = childNatural + childY;
-        }
-        return [minBottom, naturalBottom];
+    vfunc_get_preferred_width: function(forHeight) {
+        return [0, 0];
     }
 });
 
@@ -141,30 +144,32 @@ const AllView = new Lang.Class({
     Extends: BaseAppView,
 
     _init: function() {
-        this.parent();
+        this.parent({ usePagination: true }, null);
+        this._pagesView = new PagesView({ style_class: 'all-apps',
+                                          x_expand: true,
+                                          y_expand: true,
+                                          x_fill: true,
+                                          y_fill: false,
+                                          reactive: true,
+                                          y_align: St.Align.START });
+        this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
+                                     x_expand:true, y_expand:true });
+        this.actor.add_actor(this._pagesView);
 
-        this._grid.actor.y_align = Clutter.ActorAlign.START;
-        this._grid.actor.y_expand = true;
+        this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+        this._box = new St.BoxLayout({ vertical: true });
+        this._verticalAdjustment = new St.Adjustment();
+        this._box.set_adjustments(new St.Adjustment() /* unused */, this._verticalAdjustment);
 
-        let box = new St.BoxLayout({ vertical: true });
-        this._stack = new St.Widget({ layout_manager: new AllViewLayout() });
+        this._currentPage = 0;
         this._stack.add_actor(this._grid.actor);
         this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
-        this._stack.add_actor(this._eventBlocker);
-        box.add(this._stack, { y_align: St.Align.START, expand: true });
-
-        this.actor = new St.ScrollView({ x_fill: true,
-                                         y_fill: false,
-                                         y_align: St.Align.START,
-                                         x_expand: true,
-                                         y_expand: true,
-                                         overlay_scrollbars: true,
-                                         style_class: 'all-apps vfade' });
-        this.actor.add_actor(box);
-        this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
-        let action = new Clutter.PanAction({ interpolate: true });
-        action.connect('pan', Lang.bind(this, this._onPan));
-        this.actor.add_action(action);
+        this._stack.add_actor(this._eventBlocker, { x_align:St.Align.MIDDLE });
+
+        this._box.add_actor(this._stack);
+        this._pagesView.add_actor(this._box);
+
+        this._pagesView.connect('scroll-event', Lang.bind(this, this._onScroll));
 
         this._clickAction = new Clutter.ClickAction();
         this._clickAction.connect('clicked', Lang.bind(this, function() {
@@ -177,15 +182,33 @@ const AllView = new Lang.Class({
                 this._currentPopup.popdown();
         }));
         this._eventBlocker.add_action(this._clickAction);
+
+        this._availWidth = 0;
+        this._availHeight = 0;
     },
 
-    _onPan: function(action) {
-        this._clickAction.release();
+    goToPage: function(pageNumber, animated) {
+        this._currentPage = pageNumber;
+        let time = 0;
+        if (animated)
+            time = PAGE_SWITCH_TIME;
+        Tweener.addTween(this._verticalAdjustment,
+                         { value: this._grid.getPageY(this._currentPage),
+                           time: time,
+                           transition: 'easeOutQuad' });
+    },
 
-        let [dist, dx, dy] = action.get_motion_delta(0);
-        let adjustment = this.actor.vscroll.adjustment;
-        adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
-        return false;
+    _onScroll: function(actor, event) {
+        let direction = event.get_scroll_direction();
+        if (direction == Clutter.ScrollDirection.UP) {
+            if (this._currentPage > 0)
+                this.goToPage(this._currentPage - 1, true);
+        } else {
+            if (direction == Clutter.ScrollDirection.DOWN) {
+                if (this._currentPage < (this._grid.nPages() - 1))
+                    this.goToPage(this._currentPage + 1, true);
+            }
+        }
     },
 
     _getItemId: function(item) {
@@ -235,17 +258,17 @@ const AllView = new Lang.Class({
                 this._eventBlocker.reactive = isOpen;
                 this._currentPopup = isOpen ? popup : null;
                 this._updateIconOpacities(isOpen);
-                if (isOpen) {
-                    this._ensureIconVisible(popup.actor);
-                    this._grid.actor.y = popup.parentOffset;
-                } else {
-                    this._grid.actor.y = 0;
-                }
             }));
     },
 
     _ensureIconVisible: function(icon) {
-        Util.ensureActorVisibleInScrollView(this.actor, icon);
+        let itemPage = this._grid.getItemPage(icon);
+        this.goToPage(itemPage, true, true);
+    },
+
+    updateAdjustment: function(availHeight) {
+        this._verticalAdjustment.page_size = availHeight;
+        this._verticalAdjustment.upper = this._stack.height;
     },
 
     _updateIconOpacities: function(folderOpen) {
@@ -260,16 +283,42 @@ const AllView = new Lang.Class({
                        transition: 'easeOutQuad' };
             Tweener.addTween(this._items[id].actor, params);
         }
+    },
+
+    // Called before allocation to calculate dynamic spacing
+    adaptToSize: function(width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this.actor.get_theme_node().get_content_box(box);
+        box = this._pagesView.get_theme_node().get_content_box(box);
+        box = this._grid.actor.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+
+        if(this._availWidth != availWidth || this._availHeight != availHeight)
+            this.goToPage(0, false);
+
+        this._availWidth = availWidth;
+        this._availHeight = availHeight;
+
+        // Update the adjustments based on display height
+        this.updateAdjustment(availHeight);
+        // Update grid dinamyc spacing based on display width
+        this._grid.updateSpacingForSize(availWidth, availHeight);
+        // Calculate pagination values
+        this._grid.computePages(availWidth, availHeight);
     }
 });
 
 const FrequentView = new Lang.Class({
     Name: 'FrequentView',
+    Extends: BaseAppView,
 
     _init: function() {
-        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-                                             fillParent: true,
-                                             columnLimit: MAX_COLUMNS });
+        this.parent(null, { fillParent: true });
         this.actor = new St.Widget({ style_class: 'frequent-apps',
                                      x_expand: true, y_expand: true });
         this.actor.add_actor(this._grid.actor);
@@ -277,10 +326,6 @@ const FrequentView = new Lang.Class({
         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++) {
@@ -289,6 +334,20 @@ const FrequentView = new Lang.Class({
             let appIcon = new AppIcon(mostUsed[i]);
             this._grid.addItem(appIcon.actor, -1);
         }
+    },
+
+    // Called before allocation to calculate dynamic spacing
+    adaptToSize: function(width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this.actor.get_theme_node().get_content_box(box);
+        box = this._grid.actor.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        this._grid.updateSpacingForSize(availWidth, availHeight);
     }
 });
 
@@ -322,6 +381,21 @@ const ControlsBoxLayout = Lang.Class({
     }
 });
 
+const ViewStackLayout = new Lang.Class({
+    Name: 'ViewStackLayout',
+    Extends: Clutter.BinLayout,
+
+    vfunc_allocate: function (actor, box, flags) {
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        // Prepare children of all views for the upcoming allocation, calculate all
+        // the needed values to adapt available size
+        this.emit('allocated-size-changed', availWidth, availHeight);
+        this.parent(actor, box, flags);
+    }
+});
+Signals.addSignalMethods(ViewStackLayout.prototype);
+
 const AppDisplay = new Lang.Class({
     Name: 'AppDisplay',
 
@@ -357,20 +431,20 @@ const AppDisplay = new Lang.Class({
                                  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 });
-        this.actor.add(this._viewStack, { expand: true });
+        this.actor = new St.Widget({ style_class: 'app-display',
+                                     x_expand: true, y_expand: true });
+        this.actor.set_layout_manager(new Clutter.BoxLayout({ vertical: true }));
+        this._viewStackLayout = new ViewStackLayout();
+        this._viewStack = new St.Widget({ x_expand: true, y_expand: true,
+                                          layout_manager: this._viewStackLayout });
+        this._viewStackLayout.connect('allocated-size-changed', Lang.bind(this, 
this._onAllocatedSizeChanged));
 
+        this.actor.add_actor(this._viewStack, { expand: true });
         let layout = new ControlsBoxLayout({ homogeneous: true });
         this._controls = new St.Widget({ style_class: 'app-view-controls',
                                          layout_manager: layout });
         layout.hookup_style(this._controls);
-        this.actor.add(new St.Bin({ child: this._controls }));
-
+        this.actor.add_actor(new St.Bin({ child: this._controls }));
 
         for (let i = 0; i < this._views.length; i++) {
             this._viewStack.add_actor(this._views[i].view.actor);
@@ -470,6 +544,20 @@ const AppDisplay = new Lang.Class({
             if (focused)
                 this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
         }
+    },
+
+    _onAllocatedSizeChanged: function(actor, width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this._viewStack.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        for (let i = 0; i < this._views.length; i++) {
+            this._views[i].view.adaptToSize(availWidth, availHeight);
+        }
     }
 });
 
@@ -534,7 +622,7 @@ const FolderView = new Lang.Class({
     Extends: BaseAppView,
 
     _init: function() {
-        this.parent();
+        this.parent(null, null);
         this.actor = this._grid.actor;
     },
 
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index c4a254d..c019151 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -261,7 +261,6 @@ const IconGrid = new Lang.Class({
         let children = this._getVisibleChildren();
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
-        this.updateSpacingForSize(availWidth);
         let spacing = this._getSpacing();
         let [nColumns, usedWidth] = this._computeLayout(availWidth);
 
@@ -394,6 +393,10 @@ const IconGrid = new Lang.Class({
         return this._fixedSpacing ? this._fixedSpacing : this._spacing;
     },
 
+    /**
+     * This function must to be called before iconGrid allocation,
+     * to know how much spacing can the grid has
+     */
     updateSpacingForSize: function(availWidth) {
         let spacing = this._spacing;
 
@@ -509,5 +512,15 @@ const PaginatedIconGrid = new Lang.Class({
         let firstPageItem = pageNumber * this._childrenPerPage
         let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
         return childBox.y1;
+    },
+
+    getItemPage: function(item) {
+        let children = this._getVisibleChildren();
+        let index = children.indexOf(item);
+        if (index == -1) {
+            throw new Error('Item not found.');
+            return 0;
+        }
+        return Math.floor(index / this._childrenPerPage);
     }
 });


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