[gnome-shell/wip/paging-release2: 1/12] IconGrid: Add PaginatedIConGrid and change IconGrid for allow us to use pagination



commit aecf77fd1a86c3d5708db5b9f6893bef5f623148
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Tue Aug 20 10:14:25 2013 +0200

    IconGrid: Add PaginatedIConGrid and change IconGrid for allow us to use pagination
    
    Add a subclass of IconGrid with proper functions to work with
    pagination.
    Also, we change the implementation of IconGrid to use the dynamic
    spacing calculated before allocation of parents which allow us
    to use the PaginationIconGrid, since we need to know before
    PaginatedIconGrid allocation the parent size to know how many
    pages we need to allocate all the apps, and therefore,how many
    height we need for the PaginatedIconGrid.

 js/ui/iconGrid.js |  271 +++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 223 insertions(+), 48 deletions(-)
---
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 416e659..e5a32d0 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
 const Shell = imports.gi.Shell;
 const St = imports.gi.St;
 
+const Signals = imports.signals;
 const Lang = imports.lang;
 const Params = imports.misc.params;
 
@@ -176,10 +177,14 @@ const IconGrid = new Lang.Class({
     _init: function(params) {
         params = Params.parse(params, { rowLimit: null,
                                         columnLimit: null,
+                                        minRows: 1,
+                                        minColumns: 1,
                                         fillParent: false,
                                         xAlign: St.Align.MIDDLE });
         this._rowLimit = params.rowLimit;
         this._colLimit = params.columnLimit;
+        this._minRows = params.minRows;
+        this._minColumns = params.minColumns;
         this._xAlign = params.xAlign;
         this._fillParent = params.fillParent;
 
@@ -208,7 +213,7 @@ const IconGrid = new Lang.Class({
         let nColumns = this._colLimit ? Math.min(this._colLimit,
                                                  nChildren)
                                       : nChildren;
-        let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
+        let totalSpacing = Math.max(0, nColumns - 1) * this.getSpacing();
         // Kind of a lie, but not really an issue right now.  If
         // we wanted to support some sort of hidden/overflow that would
         // need higher level design
@@ -216,14 +221,6 @@ const IconGrid = new Lang.Class({
         alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
     },
 
-    _getVisibleChildren: function() {
-        let children = this._grid.get_children();
-        children = children.filter(function(actor) {
-            return actor.visible;
-        });
-        return children;
-    },
-
     _getPreferredHeight: function (grid, forWidth, alloc) {
         if (this._fillParent)
             // Ignore all size requests of children and request a size of 0;
@@ -234,9 +231,8 @@ const IconGrid = new Lang.Class({
         let nColumns, spacing;
         if (forWidth < 0) {
             nColumns = children.length;
-            spacing = this._spacing;
         } else {
-            [nColumns, , spacing] = this._computeLayout(forWidth);
+            [nColumns, ] = this._computeLayout(forWidth);
         }
 
         let nRows;
@@ -246,7 +242,7 @@ const IconGrid = new Lang.Class({
             nRows = 0;
         if (this._rowLimit)
             nRows = Math.min(nRows, this._rowLimit);
-        let totalSpacing = Math.max(0, nRows - 1) * spacing;
+        let totalSpacing = Math.max(0, nRows - 1) * this.getSpacing();
         let height = nRows * this._vItemSize + totalSpacing;
         alloc.min_size = height;
         alloc.natural_size = height;
@@ -263,8 +259,8 @@ const IconGrid = new Lang.Class({
         let children = this._getVisibleChildren();
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
-
-        let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
+        let spacing = this.getSpacing();
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);        
 
         let leftPadding;
         switch(this._xAlign) {
@@ -282,29 +278,15 @@ const IconGrid = new Lang.Class({
         let y = box.y1;
         let columnIndex = 0;
         let rowIndex = 0;
+
         for (let i = 0; i < children.length; i++) {
-            let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
-                = children[i].get_preferred_size();
-
-            /* Center the item in its allocation horizontally */
-            let width = Math.min(this._hItemSize, childNaturalWidth);
-            let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
-            let height = Math.min(this._vItemSize, childNaturalHeight);
-            let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
-
-            let childBox = new Clutter.ActorBox();
-            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
-                let _x = box.x2 - (x + width);
-                childBox.x1 = Math.floor(_x - childXSpacing);
-            } else {
-                childBox.x1 = Math.floor(x + childXSpacing);
+            let childBox = this._calculateChildBox(children[i], x, y, box);
+            if(children[i].translate_y) {
+                childBox.y1 += children[i].translate_y;
+                childBox.y2 += children[i].translate_y;
             }
-            childBox.y1 = Math.floor(y + childYSpacing);
-            childBox.x2 = childBox.x1 + width;
-            childBox.y2 = childBox.y1 + height;
-
             if (this._rowLimit && rowIndex >= this._rowLimit ||
-                this._fillParent && childBox.y2 > availHeight) {
+                this._fillParent && childBox.y2 >= availHeight) {
                 this._grid.set_skip_paint(children[i], true);
             } else {
                 children[i].allocate(childBox, flags);
@@ -326,25 +308,33 @@ const IconGrid = new Lang.Class({
         }
     },
 
-    childrenInRow: function(rowWidth) {
-        return this._computeLayout(rowWidth)[0];
-    },
+    _calculateChildBox: function(child, x, y, box) {
+        let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] =
+             child.get_preferred_size();
 
-    getRowLimit: function() {
-        return this._rowLimit;
+        /* Center the item in its allocation horizontally */
+        let width = Math.min(this._hItemSize, childNaturalWidth);
+        let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
+        let height = Math.min(this._vItemSize, childNaturalHeight);
+        let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
+
+        let childBox = new Clutter.ActorBox();
+        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
+            let _x = box.x2 - (x + width);
+            childBox.x1 = Math.floor(_x - childXSpacing);
+        } else {
+            childBox.x1 = Math.floor(x + childXSpacing);
+        }
+        childBox.y1 = Math.floor(y + childYSpacing);
+        childBox.x2 = childBox.x1 + width;
+        childBox.y2 = childBox.y1 + height;
+        return childBox;
     },
 
     _computeLayout: function (forWidth) {
         let nColumns = 0;
         let usedWidth = 0;
-        let spacing = this._spacing;
-
-        if (this._colLimit) {
-            let itemWidth = this._hItemSize * this._colLimit;
-            let emptyArea = forWidth - itemWidth;
-            spacing = Math.max(this._spacing, emptyArea / (2 * this._colLimit));
-            spacing = Math.round(spacing);
-        }
+        let spacing = this.getSpacing();
 
         while ((this._colLimit == null || nColumns < this._colLimit) &&
                (usedWidth + this._hItemSize <= forWidth)) {
@@ -355,7 +345,7 @@ const IconGrid = new Lang.Class({
         if (nColumns > 0)
             usedWidth -= spacing;
 
-        return [nColumns, usedWidth, spacing];
+        return [nColumns, usedWidth];
     },
 
     _onStyleChanged: function() {
@@ -366,6 +356,22 @@ const IconGrid = new Lang.Class({
         this._grid.queue_relayout();
     },
 
+    _getVisibleChildren: function() {
+        let children = this._grid.get_children();
+        children = children.filter(function(actor) {
+            return actor.visible;
+        });
+        return children;
+    },
+
+    childrenInRow: function(rowWidth) {
+        return this._computeLayout(rowWidth)[0];
+    },
+
+    getRowLimit: function() {
+        return this._rowLimit;
+    },
+
     removeAll: function() {
         this._grid.destroy_all_children();
     },
@@ -383,5 +389,174 @@ const IconGrid = new Lang.Class({
 
     visibleItemsCount: function() {
         return this._grid.get_n_children() - this._grid.get_n_skip_paint();
+    },
+
+    setSpacing: function(spacing) {
+            this._fixedSpacing = spacing;
+    },
+
+    getSpacing: function() {
+        return this._fixedSpacing ? this._fixedSpacing : this._spacing;
+    },
+
+    /**
+     * This function is intended to use before iconGrid allocation, to know how much spacing can we have at 
the grid
+     * but also to set manually the top/bottom rigth/left padding accordnly to the spacing calculated here.
+     * To take into account the spacing also for before the first row and for after the last row mark 
usingSurroundingSpacing true
+     * This function doesn't take into account the dynamic padding rigth now, since in fact we want to 
calculate also that.
+     */
+    maxSpacingForWidthHeight: function(availWidth, availHeight) {
+        // Maximum spacing will be the icon item size. It doesn't make any sense to have more spacing than 
items.
+        let maxSpacing = Math.floor(Math.min(this._vItemSize, this._hItemSize));
+        let minEmptyVerticalArea = availHeight - this._minRows * this._vItemSize;
+        let minEmptyHorizontalArea = availWidth - this._minColumns * this._hItemSize;
+        let spacing;
+        if(this._useSurroundingSpacing) {
+            // minRows + 1 because we want to put spacing before the first row, so it is like we have one 
more row
+            // to divide the empty space
+            let maxSpacingForRows = Math.floor(minEmptyVerticalArea / (this._minRows +1));
+            let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / (this._minColumns +1));
+            let spacingToEnsureMinimums = Math.min(maxSpacingForRows, maxSpacingForColumns);
+            let spacingNotTooBig = Math.min(spacingToEnsureMinimums, maxSpacing);
+            spacing = Math.max(this._spacing, spacingNotTooBig);
+        } else {
+            let maxSpacingForRows, maxSpacingForColumns;
+            if(this._minRows == 1) {
+                maxSpacingForRows = Math.floor(minEmptyVerticalArea / this._minRows);
+                maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / this._minColumns);
+            } else {
+                maxSpacingForRows = Math.floor(minEmptyVerticalArea / (this._minRows - 1));
+                maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / (this._minColumns - 1));
+            }
+            let spacingToEnsureMinimums = Math.min(maxSpacingForRows, maxSpacingForColumns);
+            let spacingNotTooBig = Math.min(spacingToEnsureMinimums, maxSpacing);
+            spacing = Math.max(this._spacing, spacingNotTooBig); 
+        }
+        return spacing;
+    },
+});
+Signals.addSignalMethods(IconGrid.prototype);
+
+const PaginatedIconGrid = new Lang.Class({
+    Name: 'PaginatedIconGrid',
+    Extends: IconGrid,
+
+    _init: function(params) {
+        this.parent(params);
+        this._nPages = 0;
+    },
+
+    _getPreferredHeight: function (grid, forWidth, alloc) {
+        if(this._nPages) {
+            alloc.min_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            alloc.natural_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            return;
+        }
+        this.parent(grid, forWidth, alloc);
+    },
+
+    _allocate: function (grid, box, flags) {
+        if(this._fillParent) {
+            // Reset the passed in box to fill the parent
+            let parentBox = this.actor.get_parent().allocation;
+            let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
+            box = this._grid.get_theme_node().get_content_box(gridBox);
+        }
+        let children = this._getVisibleChildren();
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        let spacing = this.getSpacing();
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);
+
+        let leftPadding;
+        switch(this._xAlign) {
+            case St.Align.START:
+                leftPadding = 0;
+                break;
+            case St.Align.MIDDLE:
+                leftPadding = Math.floor((availWidth - usedWidth) / 2);
+                break;
+            case St.Align.END:
+                leftPadding = availWidth - usedWidth;
+        }
+
+        let x = box.x1 + leftPadding;
+        let y = box.y1;
+        let columnIndex = 0;
+        let rowIndex = 0;
+
+        for (let i = 0; i < children.length; i++) {
+            let childBox = this._calculateChildBox(children[i], x, y, box);
+            if(children[i].translate_y) {
+                childBox.y1 += children[i].translate_y;
+                childBox.y2 += children[i].translate_y;
+            }
+            children[i].allocate(childBox, flags);
+            this._grid.set_skip_paint(children[i], false);
+
+            columnIndex++;
+            if (columnIndex == nColumns) {
+                columnIndex = 0;
+                rowIndex++;
+            }
+            if (columnIndex == 0) {
+                y += this._vItemSize + spacing;
+                if((i + 1) % this._childrenPerPage == 0)
+                    y += this._spaceBetweenPages - spacing;
+                x = box.x1 + leftPadding;
+            } else
+                x += this._hItemSize + spacing;
+        }
+    },
+
+    calculatePaginationValues: function (availWidthPerPage, availHeightPerPage) {
+        let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
+        let nRows;
+        let children = this._getVisibleChildren();
+        if (nColumns > 0)
+            nRows = Math.ceil(children.length / nColumns);
+        else
+            nRows = 0;
+        if (this._rowLimit)
+            nRows = Math.min(nRows, this._rowLimit);
+        let oldHeightUsedPerPage = this.usedHeightPerPage();
+        let oldNPages = this._nPages;
+
+        let spacing = this.getSpacing();
+        this._spacePerRow = this.getVItemSize() + 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._spaceBetweenPagesTotal = this._spaceBetweenPages * (this._nPages);
+        this._childrenPerPage = nColumns * this._rowsPerPage;
+
+        // Take into account when the number of pages changed (then the height of the entire grid changed 
for sure)
+        // and also when the spacing is changed, sure the hegiht per page changed and the entire grid height 
changes, althougt
+        // maybe the number of pages doesn't change
+        if(oldNPages != this._nPages || oldHeightUsedPerPage != this.usedHeightPerPage()) {
+            this.emit('n-pages-changed', this._nPages);
+        }
+    },
+
+    usedHeightPerPage: function() {
+        return this._rowsPerPage * this._spacePerRow - this.getSpacing();
+    },
+
+    nPages: function() {
+        return this._nPages;
+    },
+
+    getPagePosition: function(pageNumber) {
+        if(!this._nPages)
+            return [0,0];
+        if(pageNumber < 0 || pageNumber > this._nPages) {
+            throw new Error('Invalid page number ' + pageNumber);
+        }
+        let childBox = this._getVisibleChildren()[pageNumber *
+                        this._childrenPerPage].get_allocation_box();
+        return [childBox.x1, childBox.y1];
     }
 });


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