[gnome-shell/wip/paging-release2: 3/12] AppDisplay: Use new IconGrid implemetation in AllView and FrequentView



commit f2baf89ce3b07a8473f95be84f323f634ef1f73f
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Tue Aug 20 11:19:34 2013 +0200

    AppDisplay: Use new IconGrid implemetation in AllView and FrequentView
    
    Create a PaginatedAlphabeticalView to allow AllView to be a
    paginatedView and adapt the implementation to that.
    On the other hand FrequentView adapts the new IconGrid implementation.
    Create a PaginationScrollView 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.

 js/ui/appDisplay.js |  313 ++++++++++++++++++++++++++++++++++++++-------------
 js/ui/iconGrid.js   |    4 +-
 2 files changed, 237 insertions(+), 80 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index b26c2b7..361253e 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -30,10 +30,14 @@ const Util = imports.misc.util;
 const MAX_APPLICATION_WORK_MILLIS = 75;
 const MENU_POPUP_TIMEOUT = 600;
 const MAX_COLUMNS = 6;
+const MIN_COLUMNS = 4;
+const MIN_ROWS = 4;
 
 const INACTIVE_GRID_OPACITY = 77;
+const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
 const FOLDER_SUBICON_FRACTION = .4;
 
+const PAGE_SWITCH_TIME = 0.25;
 
 // Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
 function _loadCategory(dir, view) {
@@ -58,10 +62,12 @@ const AlphabeticalView = new Lang.Class({
     Name: 'AlphabeticalView',
     Abstract: true,
 
-    _init: function() {
-        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-                                             columnLimit: MAX_COLUMNS });
-
+    _init: function(gridParams) {
+        gridParams = Params.parse(gridParams,  { xAlign: St.Align.MIDDLE,
+                                                 columnLimit: MAX_COLUMNS,
+                                                 minRows: MIN_ROWS,
+                                                 minColumns: MIN_COLUMNS });
+        this._grid = new IconGrid.IconGrid(gridParams);
         // Standard hack for ClutterBinLayout
         this._grid.actor.x_expand = true;
 
@@ -111,59 +117,86 @@ const AlphabeticalView = new Lang.Class({
     }
 });
 
-const AllViewLayout = new Lang.Class({
-    Name: 'AllViewLayout',
-    Extends: Clutter.BinLayout,
+const PaginatedAlphabeticalView = new Lang.Class({
+    Name: 'PaginatedAlphabeticalView',
+    Extends: AlphabeticalView,
 
-    vfunc_get_preferred_height: function(container, forWidth) {
-        let minBottom = 0;
-        let naturalBottom = 0;
+    _init: function(gridParams) {
+        gridParams = Params.parse(gridParams,  { xAlign: St.Align.MIDDLE,
+                                                 columnLimit: MAX_COLUMNS,
+                                                 minRows: MIN_ROWS,
+                                                 minColumns: MIN_COLUMNS });
+        this._grid = new IconGrid.PaginatedIconGrid(gridParams);
+        // Standard hack for ClutterBinLayout
+        this._grid.actor.x_expand = true;
+        this._items = {};
+        this._allItems = [];
+    }
+});
 
-        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);
+const PaginationScrollView = new Lang.Class({
+    Name: 'PaginationScrollView',
+    Extends: St.Bin,
 
-            if (childMin + childY > minBottom)
-                minBottom = childMin + childY;
+    _init: function(parent, params) {
+        params['reactive'] = true;
+        this.parent(params);
+        this._parent = parent;
+    },
 
-            if (childNatural + childY > naturalBottom)
-                naturalBottom = childNatural + childY;
-        }
-        return [minBottom, naturalBottom];
+   vfunc_get_preferred_height: function (forWidth) {
+        return [0, 0];
+    },
+
+    vfunc_get_preferred_width: function(forHeight) {
+        return [0, 0];
+    },
+
+    vfunc_allocate: function(box, flags) {
+        box = this.get_parent().allocation;
+        box = this.get_theme_node().get_content_box(box);
+        this.set_allocation(box, flags); 
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        let childBox = new Clutter.ActorBox();
+        childBox.x1 = 0;
+        childBox.y1 = 0;
+        childBox.x2 = availWidth;
+        childBox.y2 = availHeight;
+        let child = this.get_child();
+        child.allocate(childBox, flags);
+        this._parent.updateAdjustment(availHeight);
     }
 });
 
 const AllView = new Lang.Class({
     Name: 'AllView',
-    Extends: AlphabeticalView,
+    Extends: PaginatedAlphabeticalView,
 
     _init: function() {
         this.parent();
-
-        this._grid.actor.y_align = Clutter.ActorAlign.START;
-        this._grid.actor.y_expand = true;
-
-        let box = new St.BoxLayout({ vertical: true });
-        this._stack = new St.Widget({ layout_manager: new AllViewLayout() });
+        this._paginationView = new PaginationScrollView(this, {style_class: 'all-apps'});
+        let layout = new Clutter.BinLayout();
+        this.actor = new St.Widget({ layout_manager: layout, 
+                                     x_expand:true, y_expand:true });
+        layout.add(this._paginationView, Clutter.ActorAlign.CENTER,  Clutter.ActorAlign.CENTER);
+        this._grid.connect('n-pages-changed', Lang.bind(this, this._updatedNPages));
+
+        this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+        this._box = new St.BoxLayout({ vertical: true });
+        this._verticalAdjustment = new St.Adjustment();
+        this._horizontalAdjustment = new St.Adjustment();
+        this._box.set_adjustments(this._horizontalAdjustment, this._verticalAdjustment);
+
+        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._paginationView.add_actor(this._box);
+
+        this._paginationView.connect('scroll-event', Lang.bind(this, this._onScroll));
 
         this._clickAction = new Clutter.ClickAction();
         this._clickAction.connect('clicked', Lang.bind(this, function() {
@@ -176,14 +209,52 @@ const AllView = new Lang.Class({
                 this._currentPopup.popdown();
         }));
         this._eventBlocker.add_action(this._clickAction);
+        // When the number of pages change (i.e. when changing screen resolution or during clutter false 
allocations)
+        // we have to tell pagination that the adjustment is not correct (since the allocated size of 
pagination changed)
+        // For that problem we return to the first page of pagination.
+        this.invalidatePagination = false;
     },
 
-    _onPan: function(action) {
-        this._clickAction.release();
+    _updatedNPages: function(iconGrid, nPages) {
+        // We don't need a relayout because we already done it at iconGrid
+        // when pages are calculated (and then the signal is emitted before that)");
+        this.invalidatePagination = true;
+    },
 
-        let [dist, dx, dy] = action.get_motion_delta(0);
-        let adjustment = this.actor.vscroll.adjustment;
-        adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
+    goToPage: function(pageNumber) {
+        if(pageNumber < this._grid.nPages() && pageNumber >= 0) {
+            this._currentPage = pageNumber;
+            let params = { value: this._grid.getPagePosition(this._currentPage)[1],
+                           time: PAGE_SWITCH_TIME,
+                           transition: 'easeOutQuad'
+                         };
+            Tweener.addTween(this._verticalAdjustment, params);
+        }
+    },
+
+    _onScroll: function(actor, event) {
+        let direction = event.get_scroll_direction();
+        let nextPage;
+        if (direction == Clutter.ScrollDirection.UP)
+            if(this._currentPage > 0) {
+                nextPage = this._currentPage - 1;
+                this.goToPage(nextPage);
+            }
+        if (direction == Clutter.ScrollDirection.DOWN)
+            if(this._currentPage < (this._grid.nPages() - 1)) {
+                nextPage = this._currentPage + 1;
+                this.goToPage(nextPage);
+            }
+    },
+
+    _onKeyRelease: function(actor, event) {
+        if (event.get_key_symbol() == Clutter.KEY_Up) {
+            this.goToNextPage();
+            return true;
+        } else if(event.get_key_symbol() == Clutter.KEY_Down) {
+            this.goToPreviousPage();
+            return true;
+        }
         return false;
     },
 
@@ -199,9 +270,9 @@ const AllView = new Lang.Class({
     _createItemIcon: function(item) {
         if (item instanceof Shell.App)
             return new AppIcon(item);
-        else if (item instanceof GMenu.TreeDirectory)
+        else if (item instanceof GMenu.TreeDirectory) {
             return new FolderIcon(item, this);
-        else
+        } else
             return null;
     },
 
@@ -214,17 +285,11 @@ const AllView = new Lang.Class({
     },
 
     addApp: function(app) {
-        let appIcon = this._addItem(app);
-        if (appIcon)
-            appIcon.actor.connect('key-focus-in',
-                                  Lang.bind(this, this._ensureIconVisible));
+        this._addItem(app);
     },
 
     addFolder: function(dir) {
-        let folderIcon = this._addItem(dir);
-        if (folderIcon)
-            folderIcon.actor.connect('key-focus-in',
-                                     Lang.bind(this, this._ensureIconVisible));
+        this._addItem(dir);
     },
 
     addFolderPopup: function(popup) {
@@ -234,26 +299,57 @@ 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);
+    updateAdjustment: function(availHeight) {
+        this._verticalAdjustment.page_size = availHeight;
+        this._verticalAdjustment.upper = this._stack.height;
+        if(this.invalidatePagination)
+            this.goToPage(0);
+        this.invalidatePagination = false;
     },
 
     _updateIconOpacities: function(folderOpen) {
         for (let id in this._items) {
-            if (folderOpen && !this._items[id].actor.checked)
-                this._items[id].actor.opacity = INACTIVE_GRID_OPACITY;
-            else
-                this._items[id].actor.opacity = 255;
+            if (folderOpen && !this._items[id].actor.checked) {
+                let params = { opacity: INACTIVE_GRID_OPACITY,
+                        time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
+                        transition: 'easeOutQuad'
+                       };
+                Tweener.addTween(this._items[id].actor, params);
+            }
+            else {
+                let params = { opacity: 255,
+                        time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
+                        transition: 'easeOutQuad'
+                       };
+                Tweener.addTween(this._items[id].actor, params);
+            }
         }
+    },
+
+    onUpdatedDisplaySize: 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._paginationView.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;
+
+        // Update grid dinamyc spacing based on display width
+        let spacing = this._grid.maxSpacingForWidthHeight(availWidth, availHeight, MIN_COLUMNS, MIN_ROWS, 
true);
+        // Calculate pagination values
+        this._grid.calculatePaginationValues(availWidth, availHeight);
+        this._grid.top_padding = spacing;
+        this._grid.bottom_padding = spacing;
+        this._grid.left_padding = spacing;
+        this._grid.right_padding = spacing;
+        this._grid.setSpacing(spacing);
     }
 });
 
@@ -263,6 +359,8 @@ const FrequentView = new Lang.Class({
     _init: function() {
         this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
                                              fillParent: true,
+                                             minRows: MIN_ROWS,
+                                             minColumns: MIN_COLUMNS,
                                              columnLimit: MAX_COLUMNS });
         this.actor = new St.Widget({ style_class: 'frequent-apps',
                                      x_expand: true, y_expand: true });
@@ -283,6 +381,24 @@ const FrequentView = new Lang.Class({
             let appIcon = new AppIcon(mostUsed[i]);
             this._grid.addItem(appIcon.actor, -1);
         }
+    },
+
+    onUpdatedDisplaySize: 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;
+        let spacing = this._grid.maxSpacingForWidthHeight(availWidth, availHeight, MIN_COLUMNS, MIN_ROWS, 
true);
+        this._grid.top_padding = spacing;
+        this._grid.bottom_padding = spacing;
+        this._grid.left_padding = spacing;
+        this._grid.right_padding = spacing;
+        this._grid.setSpacing(spacing);
     }
 });
 
@@ -316,6 +432,32 @@ 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 upcomming allocation, calculate all
+        // the needed values in the responsive design we are trying to emulate
+        this.emit('allocated-size-changed', availWidth, availHeight);
+        this.parent(actor, box, flags);
+    },
+
+    vfunc_set_container: function(container) {
+        if(this._styleChangedId) {
+            this._container.disconnect(this._styleChangedId);
+            this._styleChangedId = 0;
+        }
+        if(container != null)
+            this._styleChangedId = container.connect('style-changed', Lang.bind(this,
+                    function() { this.spacing = this._container.get_theme_node().get_length('spacing'); }));
+        this._container = container;
+    }
+});
+Signals.addSignalMethods(ViewStackLayout.prototype);
+
 const AppDisplay = new Lang.Class({
     Name: 'AppDisplay',
 
@@ -351,20 +493,21 @@ 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.actor = new St.Widget({ style_class: 'app-display',
+                                     x_expand: true, y_expand: true });
+        this._viewStackLayout = new ViewStackLayout();
+        this.actor.set_layout_manager(new Clutter.BoxLayout({vertical: true}));
+        this._viewStackLayout.connect('allocated-size-changed', Lang.bind(this, this._onUpdatedDisplaySize));
 
-        this._viewStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
-                                          x_expand: true, y_expand: true });
-        this.actor.add(this._viewStack, { expand: true });
+        this._viewStack = new St.Widget({ x_expand: true, y_expand: true });
+        this._viewStack.set_layout_manager(this._viewStackLayout);
 
+        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);
@@ -464,6 +607,20 @@ const AppDisplay = new Lang.Class({
             if (focused)
                 this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
         }
+    },
+
+    _onUpdatedDisplaySize: 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.onUpdatedDisplaySize(availWidth, availHeight);
+        }
     }
 });
 
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index fa07796..6e71bd4 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -513,13 +513,13 @@ const PaginatedIconGrid = new Lang.Class({
         let oldNPages = this._nPages;
 
         let spacing = this.getSpacing();
-        this._spacePerRow = this.getVItemSize() + spacing;
+        this._spacePerRow = this._vItemSize + spacing;
         this._rowsPerPage = Math.floor(availHeightPerPage / this._spacePerRow);
         // Check if deleting spacing from bottom there's enough space for another row
         let spaceWithOneMoreRow = (this._rowsPerPage + 1) * this._spacePerRow - spacing;
         this._rowsPerPage = spaceWithOneMoreRow <= availHeightPerPage? this._rowsPerPage + 1 : 
this._rowsPerPage;
         this._nPages = Math.ceil(nRows / this._rowsPerPage);
-        this._spaceBetweenPages = availHeightPerPage - (this._rowsPerPage * (this.getVItemSize() + spacing) 
- spacing);
+        this._spaceBetweenPages = availHeightPerPage - (this._rowsPerPage * (this._vItemSize + spacing) - 
spacing);
         this._spaceBetweenPagesTotal = this._spaceBetweenPages * (this._nPages);
         this._childrenPerPage = nColumns * this._rowsPerPage;
 


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