[gnome-shell/wip/paging-release: 1/3] Added pagination and collections expandings



commit 130be7784dae380be83947406a4cfda04caa1574
Author: Carlos Soriano <csoriano src gnome org>
Date:   Fri Jun 14 11:58:50 2013 +0200

    Added pagination and collections expandings
    
    Creating new implementation of icongrid for spacing etc
    
    Icongrid with spacing
    
    iconGrid: Spacing calculated fine with queue_relayout. Parent size from
    a variable. Update pages with a signal.
    appDisplay: Erase some custom allocations.
    
    appDisplay: really fix spacing
    
    apDisplay: clean up
    
    appDispaly: IndicatorLayout: erased custom get_preferred_heigth
    
    PAginationScroll with custom allocate. Allview with onyl stBin
    
    Spacing set on parents views, not on IconGrid
    Calculation os spacing on each iconGrid parent
    
    Fix collection position and size
    
    Add commented testing to more or less items in collection at FolderView
    
    FIXES
    
    Fixed RTL languages
    
    fixed last harcoded value for screen size
    
    fixed spacing undeclared
    
    Fixed bad calculations to rowsup and rowsdown at expanding collection
    view
    
    FIXES
    
    frequentView: not overlaping dash
    
    Fixed icongrid outside allocation on 1024x768
    
    Portrait mode working
    
    icongrid clean up
    
    Fixed start always to page 0 and, between calculations of nPages always
    invalidate the pagination and start with a new adjustment
    
    Fixed vad folder view between screen resolution changes
    
    FIXES
    
    yeah....Shell.GenericContainer works fine for skipping painting
    actors...
    
    fixed frequent apps css spacing calculation, since the box passed didn't
    take into account the grid spacing in updateDisplaySize, which is the
    widget which has the right padding
    
    Substract some padding from top, since we have now some spacing on the
    surrounding main grid
    
    I think fixed some issue when closing folder views and make scroll
    movement while animating makes view push down a bit
    
    FIXES
    
    Fixed folder view not expading when we change screen resolution from a
    small resolution to a bigger resolution
    
    Little clean up
    
    Fixed special case when we are at last page and only one row is shown
    and we have a folder. We have to not animate here and directly show the
    collection view.
    
    Fixed bad collection view width on small resolutions due to the padding
    and close button of the popup
    
    Really fix bad width of folder views on small resolutions.
    Also make space to put 4 rows in resolution from 900px height up.
    
    Fixed bad calculus of nRows on collection view, since we have to base
    the calculations on parent spacing, not the colection spacing.
    
    Fixed indicator layout make growing the parent bin due to its min size
    
    Fixed bad relayout of the grid when we change resolution and the number
    of pages remain the same, but not the height of the page, which causes
    that if we not take that case into account, grid allocate fine, but the
    parent of the grid doesn't give it the needed heigth (and the view is
    bad displayed in the last page)
    Also always start at 0 when opening overview
    
    Add comments and "Florian reviews" fixmes
    
    Fixed not updating arrow side when boxpointer doesn't change size (and
    then it doesn't reallocate items and doesn't take into account the new
    updated arrow side) but it changes the arrow side because of the
    resolution change.
    Fixed it queuing a redrawing on the border of the boxpointer
    
    Fixed bad calculation of empty rows, causing moving the current row with
    the fodler icon when it can stay inn the position
    
    Don't remember collection view scroll positions when between calls
    
    Added good page indicators
    
    Forgot the indicators
    
    FIXES
    
    Fixed visual animation changing pages when opening overview with
    shorcuts
    
    all views are again alphabetical views
    
    we don't need anymore the pagination calculation at allocation time,
    since we calcualte the responsive grid in parents allocation before
    
    rename leftPadding to leftEmptySpace, since we have now a
    programatically padding
    
    Fixed empty space typo
    
    Clean up setViewForPageSize
    
    Fix searching application
    
    Fixed no pages when opening and closing overview without going to
    applciatons
    
    Really fix always goes to first page, althougth the grid is not
    allocated never
    
    testing
    
    Fixed regression when we are in the last page, since we need a relayout
    when changing resolutions, but we don't need it because we always go to
    the first page
    
    Take into accoutn if grid are allocated before going to a page
    
    Animation time of icons changed to follow collection open animation
    
    Refactored lsit to view in loadcategory
    
    Fixed not going to page 0 when no mapped, nedd to fix it better

 data/Makefile.am                       |    2 +
 data/theme/gnome-shell.css             |   40 +-
 data/theme/page-indicator-active.svg   |   67 ++
 data/theme/page-indicator-inactive.svg |   67 ++
 js/ui/appDisplay.js                    | 1185 +++++++++++++++++++++++++++-----
 js/ui/boxpointer.js                    |    2 +-
 js/ui/iconGrid.js                      |  412 +++++++++--
 js/ui/searchDisplay.js                 |    4 +-
 8 files changed, 1514 insertions(+), 265 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index ece7925..3126e27 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -41,6 +41,8 @@ dist_theme_DATA =                             \
        theme/message-tray-background.png       \
        theme/more-results.svg                  \
        theme/noise-texture.png                 \
+       theme/page-indicator-active.svg \
+       theme/page-indicator-inactive.svg       \
        theme/panel-button-border.svg           \
        theme/panel-button-highlight-narrow.svg \
        theme/panel-button-highlight-wide.svg   \
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 84768b1..fc82141 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -871,9 +871,9 @@ StScrollBar StButton#vhandle:active {
 /* Application Launchers, Grid and List results */
 
 .icon-grid {
-    spacing: 36px;
-    -shell-grid-horizontal-item-size: 118px;
-    -shell-grid-vertical-item-size: 118px;
+    spacing: 30px;
+    -shell-grid-horizontal-item-size: 136px;
+    -shell-grid-vertical-item-size: 136px;
 }
 
 .icon-grid .overview-icon {
@@ -881,7 +881,6 @@ StScrollBar StButton#vhandle:active {
 }
 
 .app-display {
-    padding: 8px;
     spacing: 20px;
 }
 
@@ -899,10 +898,24 @@ StScrollBar StButton#vhandle:active {
 
 
 .search-display > StBoxLayout,
-.all-apps > StBoxLayout,
-.frequent-apps > StBoxLayout {
+.all-apps,
+.frequent-apps > StBoxLayout{
     /* horizontal padding to make sure scrollbars or dash don't overlap content */
-    padding: 0px 88px;
+    padding: 0px 88px 10px 88px;
+}
+
+.pages-icon-indicator {
+       background-image: url(page-indicator-inactive.svg);
+}
+
+.pages-icon-indicator:hover,
+.pages-icon-indicator:checked{
+       background-image: url(page-indicator-active.svg);
+}
+
+.pages-indicator {
+       spacing: 40px;
+       padding: 0px, 25px 0px, 0px;
 }
 
 .app-folder-icon {
@@ -934,6 +947,19 @@ StScrollBar StButton#vhandle:active {
     background-image: url("more-results.svg");
 }
 
+.app-well-app > .overview-icon-with-label,
+.search-provider-icon,
+.list-search-result,
+.grid-search-result .overview-icon-with-label {
+    border-radius: 4px;
+    /* visually, since the label control its own espacing inside, seems more visual consistent to have 
diferent padding for top and bottom */
+    padding: 10px 8px 5px 8px;
+    border: 1px rgba(0,0,0,0);
+    transition-duration: 100ms;
+    text-align: center;
+    spacing: 4px;
+}
+
 .app-well-app > .overview-icon,
 .show-apps > .overview-icon,
 .search-provider-icon,
diff --git a/data/theme/page-indicator-active.svg b/data/theme/page-indicator-active.svg
new file mode 100644
index 0000000..b64c91b
--- /dev/null
+++ b/data/theme/page-indicator-active.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="24"
+   height="24"
+   id="svg4703"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="page-indicator-active.svg">
+  <defs
+     id="defs4705" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.197802"
+     inkscape:cx="2.1522887"
+     inkscape:cy="16.782904"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1021"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata4708">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,8)">
+    <path
+       transform="matrix(1.0013282,0,0,1.1462573,-2079.7699,-728.26471)"
+       d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 
-10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
+       sodipodi:ry="9.5964489"
+       sodipodi:rx="10.985409"
+       sodipodi:cy="638.83099"
+       sodipodi:cx="2088.9954"
+       id="path4711"
+       style="fill:#fdffff;fill-opacity:0.94117647;stroke:none"
+       sodipodi:type="arc" />
+  </g>
+</svg>
diff --git a/data/theme/page-indicator-inactive.svg b/data/theme/page-indicator-inactive.svg
new file mode 100644
index 0000000..2f0b6e7
--- /dev/null
+++ b/data/theme/page-indicator-inactive.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="24"
+   height="24"
+   id="svg5266"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="page-indicator-inactive.svg">
+  <defs
+     id="defs5268" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313709"
+     inkscape:cx="13.381365"
+     inkscape:cy="17.859535"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1021"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5271">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,8)">
+    <path
+       sodipodi:type="arc"
+       
style="fill:#ffffff;fill-opacity:0;stroke:#ffffff;stroke-width:2.93356276000000005;stroke-miterlimit:4;stroke-opacity:0.39215686000000000;stroke-dasharray:none"
+       id="path5274"
+       sodipodi:cx="2088.9954"
+       sodipodi:cy="638.83099"
+       sodipodi:rx="10.985409"
+       sodipodi:ry="9.5964489"
+       d="m 2099.9808,638.83099 c 0,5.29998 -4.9184,9.59645 -10.9854,9.59645 -6.0671,0 -10.9854,-4.29647 
-10.9854,-9.59645 0,-5.29997 4.9183,-9.59645 10.9854,-9.59645 6.067,0 10.9854,4.29648 10.9854,9.59645 z"
+       transform="matrix(0.87616219,0,0,1.0029752,-1818.2988,-636.73159)" />
+  </g>
+</svg>
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 9132d18..c6d79bf 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -13,6 +13,7 @@ const Meta = imports.gi.Meta;
 const St = imports.gi.St;
 const Mainloop = imports.mainloop;
 const Atk = imports.gi.Atk;
+const Gdk = imports.gi.Gdk;
 
 const AppFavorites = imports.ui.appFavorites;
 const BoxPointer = imports.ui.boxpointer;
@@ -30,12 +31,23 @@ 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.45;
 const FOLDER_SUBICON_FRACTION = .4;
 
+const MAX_APPS_PAGES = 20;
+const PAGE_SWITCH_TIME = 0.3;
+//fraction of page height the finger or mouse must reach before
+//change page
+const PAGE_SWITCH_TRESHOLD = 0.2;
 
-// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
+const POPUP_FOLDER_VIEW_ANIMATION = 0.3;
+
+// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem
+// too
 function _loadCategory(dir, view) {
     let iter = dir.iter();
     let appSystem = Shell.AppSystem.get_default();
@@ -58,9 +70,14 @@ 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,
+                                                 usePagination: false,
+                                                 useSurroundingSpacing: false });
+        this._grid = new IconGrid.IconGrid(gridParams);
 
         // Standard hack for ClutterBinLayout
         this._grid.actor.x_expand = true;
@@ -111,105 +128,283 @@ const AlphabeticalView = new Lang.Class({
     }
 });
 
-const FolderView = new Lang.Class({
-    Name: 'FolderView',
+const AppPages = new Lang.Class({
+    Name: 'AppPages',
     Extends: AlphabeticalView,
-
-    _init: function() {
-        this.parent();
+   
+    _init: function(parent) {
+        this.parent({ usePagination: true,
+                      useSurroundingSpacing: true });
         this.actor = this._grid.actor;
+        this._parent = parent;
+        this._folderIcons = [];
+        this._popupExpansionNeeded = true;
     },
 
     _getItemId: function(item) {
-        return item.get_id();
+        if (item instanceof Shell.App)
+            return item.get_id();
+        else if (item instanceof GMenu.TreeDirectory)
+            return item.get_menu_id();
+        else
+            return null;
     },
-
+   
     _createItemIcon: function(item) {
-        return new AppIcon(item);
+        if (item instanceof Shell.App)
+            return new AppIcon(item);
+        else if (item instanceof GMenu.TreeDirectory) {
+            let folderIcon = new FolderIcon(item, this);
+            this._folderIcons.push(folderIcon);
+            return folderIcon;
+        } else
+            return null;
     },
 
-    _compareItems: function(a, b) {
-        return a.compare_by_name(b);
+    _compareItems: function(itemA, itemB) {
+        // bit of a hack: rely on both ShellApp and GMenuTreeDirectory
+        // having a get_name() method
+        let nameA = GLib.utf8_collate_key(itemA.get_name(), -1);
+        let nameB = GLib.utf8_collate_key(itemB.get_name(), -1);
+        return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0);
     },
-
-    addApp: function(app) {
-        this._addItem(app);
+    
+    updateIconOpacities: function(folderOpen) {
+        for (let id in this._items) {
+            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);
+            }
+        }
+    },
+    
+    addItem: function(item) {
+        this._addItem(item);
+    },
+    
+    nPages: function() {
+        return this._grid.nPages();
+    },
+    
+    getPagePosition: function(pageNumber) {
+        return this._grid.getPagePosition(pageNumber);
+    },
+    
+    addFolderPopup: function(popup) {
+        this._parent.addFolderPopup(popup);
     },
 
-    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);
+    /**
+     * Pan view with items to make space for the folder view.
+     * @param folderNVisibleRowsAtOnce this parameter tell how many rows the folder view has, but,
+     * it is already constrained to be at maximum of main grid rows least one, to ensure we have
+     * enough space to show the folder view.
+     */
+    makeSpaceForPopUp: function(iconActor, side, folderNVisibleRowsAtOnce) {
+        let rowsUp = [];
+        let rowsDown = [];
+        let mainIconYPosition = iconActor.actor.y;
+        let currentPage = this._parent.currentPage();
+        let mainIconRowReached = false;
+        let isMainIconRow = false;
+        let rows = this._grid.pageRows(currentPage);
+        this._translatedRows = rows;
+        for(let rowIndex in rows) {
+            isMainIconRow = mainIconYPosition == rows[rowIndex][0].y;
+            if(isMainIconRow)
+                mainIconRowReached = true;
+            if(!mainIconRowReached) {
+                rowsUp.push(rows[rowIndex]);
+            } else {
+                if(isMainIconRow) {
+                    if(side == St.Side.BOTTOM)
+                        rowsDown.push(rows[rowIndex]);
+                    else
+                        rowsUp.push(rows[rowIndex]);
+                } else
+                    rowsDown.push(rows[rowIndex]);
+            }
         }
-
-        return icon;
-    }
-});
-
-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;
-
-            if (childNatural + childY > naturalBottom)
-                naturalBottom = childNatural + childY;
+        //The last page can have space without rows
+        let emptyRows = this._grid._rowsPerPage - rows.length ;
+        let panViewUpNRows = 0;
+        let panViewDownNRows = 0;
+        if(side == St.Side.BOTTOM) {
+            // There's not need to pan view down
+            if(rowsUp.length >= folderNVisibleRowsAtOnce)
+                panViewUpNRows = folderNVisibleRowsAtOnce;
+            else {
+                panViewUpNRows = rowsUp.length;
+                panViewDownNRows = folderNVisibleRowsAtOnce - rowsUp.length;
+            }
+        } else {
+            // There's not need to pan view up
+            if(rowsDown.length + emptyRows >= folderNVisibleRowsAtOnce){
+                panViewDownNRows = folderNVisibleRowsAtOnce;
+            }
+            else {
+                panViewDownNRows = rowsDown.length + emptyRows;
+                panViewUpNRows = folderNVisibleRowsAtOnce - rowsDown.length - emptyRows;
+            }
+        }
+        // Especial case, last page and no rows below the icon of the folder, no rows down neither rows up,
+        // we call directly the popup
+        this.updateIconOpacities(true);
+        if(panViewDownNRows > 0 && rowsDown.length == 0 && rowsUp.length == 0) {
+            this.displayingPopup = true;
+            this._popupExpansionNeeded = false;
+            iconActor.onCompleteMakeSpaceForPopUp();            
+        } else {
+            this._popupExpansionNeeded = true;
+            this._panViewForFolderView(rowsUp, rowsDown, panViewUpNRows, panViewDownNRows, iconActor);
+        }    
+    },
+    
+    returnSpaceToOriginalPosition: function() {
+        this.updateIconOpacities(false);
+        if(!this._popupExpansionNeeded) {
+            this.displayingPopup = false;
+            return;
+        }
+        
+        if(this._translatedRows) {
+            for(let rowId in this._translatedRows) {
+                for(let childrenId in this._translatedRows[rowId]) {
+                    if(this._translatedRows[rowId][childrenId].translate_y){
+                        let tweenerParams = { translate_y: 0,
+                                time: POPUP_FOLDER_VIEW_ANIMATION,
+                                onUpdate: function() {this.queue_relayout();},
+                                transition: 'easeInOutQuad',
+                                onComplete: Lang.bind(this, function(){this.displayingPopup = false;}
+                                )};
+                        Tweener.addTween(this._translatedRows[rowId][childrenId], tweenerParams);
+                    }
+                }
+            }
+        }
+    },
+    
+    _panViewForFolderView: function(rowsUp, rowsDown, panViewUpNRows, panViewDownNRows, iconActor) {
+        let rowHeight = this._grid.rowHeight();
+        if(panViewUpNRows > 0) {
+            this.displayingPopup = true;
+            let height = rowHeight * panViewUpNRows;
+            for(let rowId in rowsUp) {
+                for(let childrenId in rowsUp[rowId]) {
+                    rowsUp[rowId][childrenId].translate_y = 0;
+                    let tweenerParams = { translate_y: - height,
+                                          time: POPUP_FOLDER_VIEW_ANIMATION,
+                                          onUpdate: function() {this.queue_relayout();},
+                                          transition: 'easeInOutQuad' };
+                    if((rowId == rowsUp.length - 1) && (childrenId == rowsUp[rowId].length - 1)) {
+                        tweenerParams['onComplete'] = Lang.bind(iconActor, 
iconActor.onCompleteMakeSpaceForPopUp);
+                    }
+                    Tweener.addTween(rowsUp[rowId][childrenId], tweenerParams);
+                }
+            }
+        }
+        if(panViewDownNRows > 0) {
+            this.displayingPopup = true;
+            let height = rowHeight * panViewDownNRows;
+            for(let rowId in rowsDown) {
+                for(let childrenId in rowsDown[rowId]) {
+                    rowsDown[rowId][childrenId].translate_y = 0;
+                    let tweenerParams = { translate_y: height,
+                                          time: POPUP_FOLDER_VIEW_ANIMATION,
+                                          onUpdate: function() {this.queue_relayout();} };
+                    if((rowId == rowsDown.length - 1) && (childrenId == rowsDown[rowId].length - 1)) {
+                        tweenerParams['onComplete'] = Lang.bind(iconActor, 
iconActor.onCompleteMakeSpaceForPopUp);
+                    }
+                    Tweener.addTween(rowsDown[rowId][childrenId], tweenerParams);
+                }
+            }
+        }
+    },
+    
+    removeAll: function() {
+        this._folderIcons = [];
+        this.parent();
+    },
+    
+    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);
+        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);
+        this._grid.top_padding = spacing;
+        this._grid.bottom_padding = spacing;
+        this._grid.left_padding = spacing;
+        this._grid.right_padding = spacing;
+        this._grid.setSpacing(spacing);
+        // Update folder views
+        for(let id in this._folderIcons) {
+            this._folderIcons[id].onUpdatedDisplaySize(width, height);
         }
-        return [minBottom, naturalBottom];
     }
 });
 
-const AllView = new Lang.Class({
-    Name: 'AllView',
-    Extends: AlphabeticalView,
-
-    _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._stack.add_actor(this._grid.actor);
+const PaginationScrollView = new Lang.Class({
+    Name: 'PaginationScrollView',
+    Extends: St.Bin,
+    
+    _init: function(parent, params) {
+        params['reactive'] = true;
+        this.parent(params);
+        this._verticalAdjustment = new St.Adjustment();
+        this._horizontalAdjustment = new St.Adjustment();
+
+        this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });        
+        this._box = new St.BoxLayout({ vertical: true });
+        this._pages = new AppPages(this);
+        
+        this._stack.add_actor(this._pages.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._box.set_adjustments(this._horizontalAdjustment, this._verticalAdjustment);
+        this.add_actor(this._box);
+
+        this._currentPage = 0;
+        this._parent = parent;
+        
+        // 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.
+        // In fact it is not necesary with the current state of the gnome-shell since we can only change the 
resolution outside the overview,
+        // so sicne we always start at page 0 of pagination when entering overview,we always already did 
that adjustment. BUT, who knows,
+        // since it is a diferent case from the other, better to take it into account for future changes.
+        this.invalidatePagination = false;
+        
+        this.connect('scroll-event', Lang.bind(this, this._onScroll));
+        
+        let panAction = new Clutter.PanAction({ interpolate: false });
+        panAction.connect('pan', Lang.bind(this, this._onPan));
+        panAction.connect('gesture-cancel', Lang.bind(this, function() {
+            this._onPanEnd(this._panAction);
+        }));
+        panAction.connect('gesture-end', Lang.bind(this, function() {
+            this._onPanEnd(this._panAction);
+        }));
+        this._panAction = panAction;
+        this.add_action(panAction);
+        
         this._clickAction = new Clutter.ClickAction();
         this._clickAction.connect('clicked', Lang.bind(this, function() {
             if (!this._currentPopup)
@@ -222,83 +417,366 @@ const AllView = new Lang.Class({
         }));
         this._eventBlocker.add_action(this._clickAction);
     },
+    
+    vfunc_get_preferred_height: function (forWidht) {
+        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;   
+        this._box.allocate(childBox, flags);
+        
+        this._verticalAdjustment.page_size = availHeight;
+        this._verticalAdjustment.upper = this._stack.height;
+        if(this.invalidatePagination) {
+            //FLORIAN REVIEW
+            // we can modify our adjustment, so we do that to show the first page, but we can't modify the 
indicators,
+            // so we modify it before redraw (we won't see too much flickering at all)
+            if(this._pages.nPages() > 1) {
+                this.goToPage(0);
+                Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() 
{this._parent.goToPage(0);}));
+            }
+        }
+        this.invalidatePagination = false;
+    },
+
+    goToPage: function(pageNumber, action) {
+        if(this._currentPage != pageNumber && this._pages.displayingPopup && this._currentPopup) {
+            this._currentPopup.popdown();
+        } else if(this._pages.displayingPopup && this._currentPopup) {
+            return;
+        }
+        //FIXME
+        /*if(!this._pages._grid.actor.mapped)
+            return;*/
+        let velocity;
+        if(!action)
+            velocity = 0;
+        else
+            velocity = Math.abs(action.get_velocity(0)[2]);
+        // Tween the change between pages.
+        // If velocity is not specified (i.e. scrolling with mouse wheel),
+        // use the same speed regardless of original position
+        // if velocity is specified, it's in pixels per milliseconds
+        let diffFromPage =  this._diffToPage(pageNumber);
+        let childBox = this.get_allocation_box();
+        let totalHeight = childBox.y2 - childBox.y1;
+        let time;
+        // Only take into account the velocity if we change of page, if not,
+        // we returns smoothly with default velocity to the current page
+        if(this._currentPage != pageNumber) {
+            let min_velocity = totalHeight / (PAGE_SWITCH_TIME * 1000);
+            velocity = Math.max(min_velocity, velocity);
+            time = (diffFromPage / velocity) / 1000;            
+        } else
+            time = PAGE_SWITCH_TIME * diffFromPage / totalHeight;
+        // Take care when we are changing more than one page, maximum time
+        // regardless the velocity is the default one
+        time = Math.min(time, PAGE_SWITCH_TIME);
+        if(pageNumber < this._pages.nPages() && pageNumber >= 0) {
+            this._currentPage = pageNumber;
+            let params = { value: this._pages.getPagePosition(this._currentPage)[1],
+                           time: time,
+                           transition: 'easeOutQuad'
+                          };
+            Tweener.addTween(this._verticalAdjustment, params);
+        }
+    },
+
+    nPages: function() {
+      return this._pages.nPages();  
+    },
+
+    currentPage: function() {
+        return this._currentPage;
+    },
+
+    _diffToPage: function (pageNumber) {
+        let currentScrollPosition = this._verticalAdjustment.value;
+        return Math.abs(currentScrollPosition - this._pages._grid.getPagePosition(pageNumber)[1]);
+    },
+
+    _nearestPage: function() {
+        let currentNearestPage = 0;
+        let diff = this._diffToPage(currentNearestPage);
+        let oldDiff = diff;
+        
+        while(diff <= oldDiff && currentNearestPage < (this._pages.nPages() - 1)) {
+            currentNearestPage++;
+            oldDiff = diff;
+            diff = this._diffToPage(currentNearestPage);            
+        }
+        if(diff > oldDiff)
+            currentNearestPage--;
+
+        return currentNearestPage; 
+    },
+
+    _goToNearestPage: function(action) {
+        this._parent.goToPage(this._nearestPage(), action);
+    },
+
+    _onScroll: function(actor, event) {
+        if(this._pages.displayingPopup)
+            return;
+        let direction = event.get_scroll_direction();
+        let nextPage;
+        if (direction == Clutter.ScrollDirection.UP)
+            if(this._currentPage > 0) {
+                nextPage = this._currentPage - 1;
+                this._parent.goToPage(nextPage);
+            }
+        if (direction == Clutter.ScrollDirection.DOWN)
+            if(this._currentPage < (this.nPages() - 1)) {
+                nextPage = this._currentPage + 1;
+                this._parent.goToPage(nextPage);
+            }
+    },
+    
+    addFolderPopup: function(popup) {
+        this._stack.add_actor(popup.actor);
+        popup.openStateId = popup.connect('open-state-changed', Lang.bind(this,
+                function(popup, isOpen) {
+                    this._eventBlocker.reactive = isOpen;
+                    this._currentPopup = isOpen ? popup : null;
+                }));
+    },
+    
     _onPan: function(action) {
         this._clickAction.release();
-
+        if(this._pages.displayingPopup)
+            return;
         let [dist, dx, dy] = action.get_motion_delta(0);
-        let adjustment = this.actor.vscroll.adjustment;
-        adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
+        let adjustment = this._verticalAdjustment;
+        adjustment.value -= (dy / this.height) * adjustment.page_size;
         return false;
     },
+    
+    _onPanEnd: function(action) {
+        if(this._pages.displayingPopup)
+            return;
+        let diffCurrentPage = this._diffToPage(this._currentPage);
+        if(diffCurrentPage > this.height * PAGE_SWITCH_TRESHOLD) {
+            if(action.get_velocity(0)[2] > 0 && this._currentPage > 0) {
+                this._parent.goToPage(this._currentPage - 1, action);
+            } else if(this._currentPage < this.nPages() - 1) {
+                this._parent.goToPage(this._currentPage + 1, action);
+            }
+        } else
+            this._parent.goToPage(this._currentPage, action);
+    },
+    
+    onUpdatedDisplaySize: function(width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        this._pages.onUpdatedDisplaySize(availWidth, availHeight);
+    }
+    
+});
 
-    _getItemId: function(item) {
-        if (item instanceof Shell.App)
-            return item.get_id();
-        else if (item instanceof GMenu.TreeDirectory)
-            return item.get_menu_id();
-        else
-            return null;
+const PaginationIconIndicator = new Lang.Class({
+    Name: 'PaginationIconIndicator',
+
+    _init: function(parent, index) {
+
+        this.actor = new St.Button({ style_class: 'pages-icon-indicator',
+                                     button_mask: St.ButtonMask.ONE || St.ButtonMask.TWO,
+                                     toggle_mode: true,
+                                     can_focus: true });
+        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
+        this.actor._delegate = this;
+        this.actor.set_size(24, 24);
+        this._parent = parent;
+        this.actor._index = index;
     },
 
-    _createItemIcon: function(item) {
-        if (item instanceof Shell.App)
-            return new AppIcon(item);
-        else if (item instanceof GMenu.TreeDirectory)
-            return new FolderIcon(item, this);
+    _onClicked: function(actor, button) {        
+        this._parent.goToPage(this.actor._index); 
+        return false;
+    },
+
+    setChecked: function (checked) {
+        this.actor.set_checked(checked);
+    }
+});
+
+const PaginationIndicator = new Lang.Class({
+    Name:'PaginationIndicator',
+    
+    _init: function(params) {
+        params['y_expand'] = true;
+        this.actor = new Shell.GenericContainer(params);
+        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+        this.actor.connect('allocate', Lang.bind(this, this._allocate));
+        this.actor.connect('style-changed', Lang.bind(this, this._styleChanged));
+        this._spacing = 0;
+    },
+    
+    _getPreferredHeight: function(actor, forWidth, alloc) {
+        let [minHeight, natHeight] = this.actor.get_children()[0].get_preferred_height(forWidth);
+        if(this._nPages) {
+            let natHeightPerChild = natHeight + this._spacing;
+            let totalUsedHeight =  this._nPages * natHeightPerChild - this._spacing;
+            let minHeightPerChild = minHeight + this._spacing;
+            minHeight = this._nPages * minHeightPerChild - this._spacing;
+            natHeight = this._nPages * natHeightPerChild - this._spacing;
+        } else
+            minHeight = natHeight = 0;
+        alloc.min_size = 0;
+        alloc.natural_size = natHeight;
+    },
+    
+    _getPreferredWidth: function(actor, forHeight, alloc) {
+        let [minWidth, natWidth] = this.actor.get_children()[0].get_preferred_width(forHeight);
+        let totalWidth = natWidth + this._spacing;
+        alloc.min_size = totalWidth;
+        alloc.natural_size = totalWidth;
+    },
+
+    _allocate: function(actor, box, flags) {
+        let children = this.actor.get_children();
+        for(let i in children)
+            this.actor.set_skip_paint(children[i], true);
+        if(children.length < 1)
+            return;
+        let availHeight = box.y2 - box.y1;
+        let availWidth = box.x2 - box.x1;
+        let [minHeight, natHeight] = children[0].get_preferred_height(availWidth);
+        let heightPerChild = natHeight + this._spacing;
+        let totalUsedHeight =  this._nPages * heightPerChild - this._spacing;
+        
+        let [minWidth, natWidth] = children[0].get_preferred_width(natHeight);
+        let widthPerChild = natWidth + this._spacing * 2;
+        let firstPosition = [this._spacing, availHeight / 2 - totalUsedHeight / 2];
+        for(let i = 0; i < this._nPages; i++) {
+            let childBox = new Clutter.ActorBox();
+            childBox.x1 = 0;
+            childBox.x2 = availWidth;
+            childBox.y1 = firstPosition[1] + i * heightPerChild;
+            childBox.y2 = childBox.y1 + heightPerChild;
+            if(childBox.y2 > availHeight)
+                break;
+            children[i].allocate(childBox, flags);
+            this.actor.set_skip_paint(children[i], false);
+        }
+    },
+   
+    _styleChanged: function() {
+        this._spacing = this.actor.get_theme_node().get_length('spacing');
+        this.actor.queue_relayout();
+    }
+});
+
+const AllView = new Lang.Class({
+    Name: 'AllView',
+   
+    _init: function() {
+        let paginationScrollViewParams = {style_class: 'all-apps'};
+        this._paginationIndicator = new PaginationIndicator({style_class: 'pages-indicator'});
+        this._paginationIndicator._nPages = 0;
+        this._paginationView = new PaginationScrollView(this, paginationScrollViewParams);
+
+        let layout = new Clutter.BinLayout();
+        this.actor = new St.Widget({ layout_manager: layout, 
+                                     x_expand:true, y_expand:true });
+        //FIXME Clutter align properties
+        layout.add(this._paginationView, 2,2);
+        if(Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
+            layout.add(this._paginationIndicator.actor, 2,2);
         else
-            return null;
+            layout.add(this._paginationIndicator.actor, 3,2);
+        for(let i = 0; i < MAX_APPS_PAGES; i++) {
+            let indicatorIcon = new PaginationIconIndicator(this, i);
+            if(i == 0) {
+                indicatorIcon.setChecked(true);
+            }
+            this._paginationIndicator.actor.add_actor(indicatorIcon.actor);
+        }
+
+        this._paginationView._pages._grid.connect('n-pages-changed', Lang.bind(this, this._updatedNPages));
+        // Always start at page 0 when we enter and quit overview
+        Main.overview.connect('hidden', Lang.bind(this, function() {this.goToPage(0);}));
     },
+    
+    _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._paginationIndicator._nPages = nPages;
+        this._paginationView.invalidatePagination = true;
+    },
+    
+    _onKeyRelease: function(actor, event) {
+        if (event.get_key_symbol() == Clutter.KEY_Up) {
+            this._paginationView.goToNextPage();
+            return true;
+        } else if(event.get_key_symbol() == Clutter.KEY_Down) {
+            this._paginationView.goToPreviousPage();
+            return true;
+        }
 
-    _compareItems: function(itemA, itemB) {
-        // bit of a hack: rely on both ShellApp and GMenuTreeDirectory
-        // having a get_name() method
-        let nameA = GLib.utf8_collate_key(itemA.get_name(), -1);
-        let nameB = GLib.utf8_collate_key(itemB.get_name(), -1);
-        return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0);
+        return false;
     },
 
     addApp: function(app) {
-        let appIcon = this._addItem(app);
-        if (appIcon)
-            appIcon.actor.connect('key-focus-in',
-                                  Lang.bind(this, this._ensureIconVisible));
+       let appIcon = this._paginationView._pages.addItem(app);
     },
 
     addFolder: function(dir) {
-        let folderIcon = this._addItem(dir);
-        if (folderIcon)
-            folderIcon.actor.connect('key-focus-in',
-                                     Lang.bind(this, this._ensureIconVisible));
+        let folderIcon = this._paginationView._pages.addItem(dir);
     },
-
-    addFolderPopup: function(popup) {
-        this._stack.add_actor(popup.actor);
-        popup.connect('open-state-changed', Lang.bind(this,
-            function(popup, isOpen) {
-                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;
-                }
-            }));
+   
+    removeAll: function() {
+        this._paginationView._pages.removeAll();
     },
 
-    _ensureIconVisible: function(icon) {
-        Util.ensureActorVisibleInScrollView(this.actor, icon);
+    loadGrid: function() {
+        this._paginationView._pages.loadGrid();
     },
-
-    _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;
-        }
+    
+    goToPage: function(index, action) {
+        // Since we call this function also from shown signal of the overview, we can't assure the 
pagination is already calculated
+        // so we first ask pagination if it has some page, if not, we return and that's it. (nevermind, 
because when creating for first time
+        // pagination, that is when it can happens, paginations starts at page 0 already, so no problem here)
+        if(this._paginationView.nPages() < 2)
+            return;
+        // Since it can happens after a relayout, we have to ensure that all is unchecked
+        let indicators = this._paginationIndicator.actor.get_children();
+        for(let i in indicators)
+            indicators[i].set_checked(false);
+        this._paginationView.goToPage(index, action);
+        
this._paginationIndicator.actor.get_child_at_index(this._paginationView.currentPage()).set_checked(true);
+    },
+    
+    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);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        this._paginationView.onUpdatedDisplaySize(availWidth, availHeight);
     }
 });
 
@@ -328,6 +806,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);
     }
 });
 
@@ -361,6 +857,32 @@ const ControlsBoxLayout = Lang.Class({
     }
 });
 
+const AppDisplayActor = new Lang.Class({
+    Name: 'AppDisplayActor',
+    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(AppDisplayActor.prototype);
+
 const AppDisplay = new Lang.Class({
     Name: 'AppDisplay',
 
@@ -396,20 +918,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,
+        this.actor = new St.Widget({ style_class: 'app-display',
                                         x_expand: true, y_expand: true });
+        this._viewStackLayout = new AppDisplayActor();
+        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(),
+        this._viewStack = new St.Widget({ 
                                           x_expand: true, y_expand: true });
-        this.actor.add(this._viewStack, { 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);
@@ -429,9 +952,9 @@ const AppDisplay = new Lang.Class({
         // our real contents
         this._focusDummy = new St.Bin({ can_focus: true });
         this._viewStack.add_actor(this._focusDummy);
-
         this._allAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, 
this._redisplayAllApps));
         this._frequentAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, 
this._redisplayFrequentApps));
+        
     },
 
     _showView: function(activeIndex) {
@@ -509,6 +1032,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.actor.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);
+        }
     }
 });
 
@@ -568,12 +1105,163 @@ const AppSearchProvider = new Lang.Class({
     }
 });
 
+const FolderView = new Lang.Class({
+    Name: 'FolderView',
+    Extends: AlphabeticalView,
+
+    _init: function() {
+        this.parent({ useSurroundingSpacing: true });
+        
+        this.actor = new St.ScrollView({overlay_scrollbars: true});
+        this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+        this._box = new St.BoxLayout({vertical: true, reactive: true});
+        this._widget = new St.Widget({layout_manager: new Clutter.BinLayout()});
+        this._widget.add_child(this._grid.actor);
+        this._box.add_actor(this._widget);
+        this.actor.add_actor(this._box);
+        this._boxPointerOffsets = {};
+    },
+
+    _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) {
+        //FIXME
+        /*if(this._allItems.length > 0)
+            return;*/
+        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;
+    },
+
+    onUpdatedDisplaySize: function(width, height) {
+        this._appDisplayWidth = width;
+        this._appDisplayHeight = height;
+        // Update grid dinamyc spacing based on display width
+        let spacing = this._grid.maxSpacingForWidthHeight(width, height, MIN_COLUMNS, MIN_ROWS, true);
+        this._grid.setSpacing(spacing);
+        if(!Object.keys(this._boxPointerOffsets).length)
+            return;
+        //We put the normal padding as spacing as we have in the main grid to do well the calculations for 
used rows, used columns etc, since
+        // it is the behaviour we want to emulate. After that we will put the correct padding from 
calculations of the boxpointer offsets, to ensure
+        //the boxpointer will be contained inside the view
+        this._parentSpacing = spacing;
+        
+        let boxPointerTotalOffset = this._boxPointerOffsets['arrowHeight'] + 
this._boxPointerOffsets['padding'] * 2 + this._boxPointerOffsets['closeButtonOverlap'];
+        let offsetForEachSide = Math.ceil(boxPointerTotalOffset / 2);
+        this._offsetForEachSide = offsetForEachSide;
+        this._grid.top_padding = spacing - offsetForEachSide;
+        this._grid.bottom_padding = spacing - offsetForEachSide;
+        this._grid.left_padding = spacing - offsetForEachSide;
+        this._grid.right_padding = spacing - offsetForEachSide;
+    },
+    
+    _containerBox: function() {
+        let pageBox = new Clutter.ActorBox();
+        pageBox.x1 = 0;
+        pageBox.y1 = 0;
+        pageBox.x2 = this._appDisplayWidth;
+        pageBox.y2 = this._appDisplayHeight;
+        return this.actor.get_theme_node().get_content_box(pageBox);
+    },
+    
+    usedWidth: function() {
+        let box = this._containerBox();
+        let availWidthPerPage = box.x2 - box.x1;
+        // Since we want to do the calculation of the real width of the grid
+        // taking into account the parent behaviour, we have to substract from the
+        // avail width the padding we subsctratc before to the folder view
+        // in its surrounding spacings
+        availWidthPerPage -= 2 * this._offsetForEachSide;
+        let maxUsedWidth = this._grid.usedWidth(availWidthPerPage);
+        return maxUsedWidth;
+    },
+    
+    usedHeight: function() {
+        // Then calculate the real maxUsedHeight
+        return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce()) + this._grid.top_padding + 
this._grid.bottom_padding;
+    },   
+    
+    nRowsDisplayedAtOnce: function() {
+        let box = this._containerBox();
+        let availHeightPerPage = box.y2 - box.y1;
+        let availWidthPerPage = box.x2 - box.x1;
+        //FIXME: if we do that, we really not showing the maximum of rows we can show on collection view
+        // Since we want to do the calculation of the real width of the grid
+        // taking into account the parent behaviour, we have to substract from the
+        // avail width the padding we subsctratc before to the folder view
+        // in its surrounding spacings
+        availWidthPerPage -= 2 * this._offsetForEachSide;
+        availHeightPerPage -= 2 * this._offsetForEachSide;
+        
+        let maxRowsDisplayedAtOnce = this.maxRowsDisplayedAtOnce();
+        let usedRows = this._grid.nUsedRows(availWidthPerPage);
+        usedRows = usedRows <= maxRowsDisplayedAtOnce ? usedRows : maxRowsDisplayedAtOnce;
+        return usedRows;
+    },
+    
+    maxRowsDisplayedAtOnce: function() {
+        let box = this._containerBox();
+        let availHeightPerPage = box.y2 - box.y1;
+      //FIXME: if we do that, we really not showing the maximum of rows we can show on collection view
+        // Since we want to do the calculation of the real width of the grid
+        // taking into account the parent behaviour, we have to substract from the
+        // avail width the padding we subsctratc before to the folder view
+        // in its surrounding spacings
+        availHeightPerPage -= 2 * this._offsetForEachSide;
+        let maxRowsPerPage = this._grid.rowsForHeight(availHeightPerPage);
+        //Then, we can only show that rows least one.
+        maxRowsPerPage -= 1;
+        return maxRowsPerPage;
+    },
+    
+    updateBoxPointerOffsets: function(arrowHeight, padding, closeButtonOverlap) {
+        // We have to ensure the close button doesn't go outside the allocation, so
+        // if we are using all the possible rows, means the boxpointer goes until
+        // the boundary of allocation, and then we have to take into account the close button
+        // overlap and least it from padding
+        this._boxPointerOffsets['arrowHeight'] = arrowHeight;
+        this._boxPointerOffsets['padding'] = padding;
+        this._boxPointerOffsets['closeButtonOverlap'] = closeButtonOverlap;
+        
+    },
+    
+    setScrollToStart: function() {
+        this.actor.vscroll.adjustment.value = 0;
+    }
+});
+
 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,
@@ -582,7 +1270,14 @@ const FolderIcon = new Lang.Class({
                                      x_fill: true,
                                      y_fill: true });
         this.actor._delegate = this;
-
+        this._parentView = parentView;
+        // when changing screen resolution or during clutter false allocations
+        // we have to tell folder view that the calculated values of boxpointer arrow side, position, etc 
+        // are not correct (since the allocated size of pagination changed)
+        // For that problem we calculate everything again and apply it maintaining current popup.
+        this.invalidatePopUp = false;
+        this._boxPointerOffsets = {};
+        
         let label = this._dir.get_name();
         this.icon = new IconGrid.BaseIcon(label,
                                           { createIcon: Lang.bind(this, this._createIcon) });
@@ -590,56 +1285,175 @@ const FolderIcon = new Lang.Class({
         this.actor.label_actor = this.icon.label;
 
         this.view = new FolderView();
-        this.view.actor.reactive = false;
         _loadCategory(dir, this.view);
         this.view.loadGrid();
 
         this.actor.connect('clicked', Lang.bind(this,
             function() {
                 this._ensurePopup();
-                this._popup.toggle();
+                this.view.setScrollToStart();
             }));
         this.actor.connect('notify::mapped', Lang.bind(this,
             function() {
                 if (!this.actor.mapped && this._popup)
                     this._popup.popdown();
             }));
+        
+        this.actor.connect('notify::allocation', Lang.bind(this, function() {
+            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, this._updatePopupPosition));
+        }));
     },
 
     _createIcon: function(size) {
-        return this.view.createFolderIcon(size);
+        return this.view.createFolderIcon(size, this);
+    },
+    
+    _popUpGridWidth: function() {
+        return this.view.usedWidth();
+    },
+    
+    _popUpGridHeight: function() {
+        let usedHeight = this.view.usedHeight();
+        return usedHeight;   
+    },
+    
+    _popUpHeight: function() {
+        let usedHeight = this.view.usedHeight() + this._boxPointerOffsets['arrowHeight'] + 
this._boxPointerOffsets['padding'] * 2;
+        return usedHeight;   
     },
 
+    makeSpaceForPopUp: function() {
+        this._parentView.makeSpaceForPopUp(this, this._boxPointerArrowside, 
this.view.nRowsDisplayedAtOnce());
+    },
+    
+    returnSpaceToOriginalPosition: function() {
+        this._parentView.returnSpaceToOriginalPosition();
+    },
+    
+    onCompleteMakeSpaceForPopUp: function() {
+        this._popup.popup();
+    },
+    
+    _calculateBoxPointerArrowSide: function() {
+        let absoluteActorYPosition = this.actor.get_transformed_position()[1];
+        let spaceTop = absoluteActorYPosition;
+        // Be careful, but doesn't matter, we don0t take into account the top panel height etc,
+        // So maybe we put an arrow side "wrong", but anyway, the expanding of colelction view will
+        // do the required space
+        let spaceBottom = this.actor.get_stage().height - (absoluteActorYPosition + this.actor.height);
+        return spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
+    },
+    
+    _updatePopUpSize: function() {
+     // FLORIAN REVIEW
+        /**
+         * Why we need that: AppDiplay update width for the spacing for all
+         * views Allview and frequent view and folder views calcualte spacing
+         * with the items of icongrid with harcoded values
+         * 
+         * Open overview, then iconSizes changes in allview and frequent view
+         * icongrids, which is the actors who are added to the main AppDisplay.
+         * Then a relayout occurs. AppDiplay update width for the spacing for
+         * all views Allview and frequent view and folder views calcualte
+         * spacing with the items of icongrid, which allview and frequetn view
+         * has the new values, but folderview has the hardcoded values, since
+         * folderview icongrid is not still added to the main Actor, and then,
+         * they didn't emitted style changed signal with new valuesw of item
+         * sizes. Then, frequent view and all view has correct spacing and item
+         * size values, and fodler view has incorrect size and spacing values.
+         * Then, we click icon folder, a folderIcon popup is created and added
+         * to the parent actor, then the style changes, and item size changes,
+         * but spacing is the old one. Then, we calculate the position of the
+         * popup, but, the required height is with the old spacing and new item
+         * sizes, so the height is bigger, then the position is bad. Then,
+         * appDisplay allocate all views updating spacing, and set the good
+         * spacing to folder view, then allocate the folder view, but the
+         * positoon of the boxpointer is already calcualted with the old
+         * spacing, so the boxpointer is displaced.
+         * 
+         * Solution: ensure style of the grid just after we add it to the parent
+         * and before the calculation of the position.
+         */
+
+        this.view._grid.actor.ensure_style();
+        this._boxPointerOffsets['arrowHeight'] = 
this._popup._boxPointer.actor.get_theme_node().get_length('-arrow-rise');
+        this._boxPointerOffsets['padding'] = 
this._popup._boxPointer.bin.get_theme_node().get_length('padding');
+        //It will be negative value, so we have to substract it, instead of plust it.
+        this._boxPointerOffsets['closeButtonOverlap'] = - 
this._popup.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
+
+        this.view.updateBoxPointerOffsets(this._boxPointerOffsets['arrowHeight'], 
this._boxPointerOffsets['padding'], this._boxPointerOffsets['closeButtonOverlap']);
+        this.view.onUpdatedDisplaySize(this._displayWidth, this._displayHeight);
+        /*
+         * Always make the grid (and therefore the boxpointer) to be the max
+         * width it can be if it use full icon rows, althougth there's less
+         * icons than necesary to full the row. In that manner the popup will be
+         * more eye pleasant, fulling the parent view
+         */
+        this.view.actor.set_width(this._popUpGridWidth());
+
+        /*
+         * A folder view can only be, at a maximum, one row less than the parent
+         * view, so calculate the maximum rows it can have, and then deduct one,
+         * then calculate the maxUsedHeigth and the current Used height, if it
+         * is more, strech to the maxUsedHeight
+         */
+        this.view.actor.set_height(this._popUpGridHeight());
+    },
+    
+    _updatePopupPosition: function() {
+        if(this._popup) {
+            // Position the popup above or below the source icon
+            if (this._boxPointerArrowside == St.Side.BOTTOM) {
+                let closeButtonOffset = -this._popup.closeButton.translation_y;
+                // FLORIAN REVIEW
+                // We have to use this function, since this._popup.actor.height not always return a good 
value (32 px??)
+                // and then all this calculation of position fails. To solve this in this function we 
calculate the used height with the grid
+                // since we knoe all of the properties of grid. Then we add the padding, arrowheigth etc of 
boxpointer, and we have the
+                // used height of the popup
+                let y = this.actor.y - this._popUpHeight();
+                let yWithButton = y - closeButtonOffset;
+                this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
+                this._popup.actor.y = Math.max(y, closeButtonOffset);
+                this._popup.actor.y = y
+            } else {
+                this._popup.actor.y = this.actor.y + this.actor.height;
+            }
+        }
+    },
+    
     _ensurePopup: function() {
-        if (this._popup)
+        if(this._popup && !this.invalidatePopUp){
+            this.makeSpaceForPopUp();
             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);
-
-        // Position the popup above or below the source icon
-        if (side == St.Side.BOTTOM) {
-            this._popup.actor.show();
-            let closeButtonOffset = -this._popup.closeButton.translation_y;
-            let y = this.actor.y - this._popup.actor.height;
-            let yWithButton = y - closeButtonOffset;
-            this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
-            this._popup.actor.y = Math.max(y, closeButtonOffset);
-            this._popup.actor.hide();
         } else {
-            this._popup.actor.y = this.actor.y + this.actor.height;
+            
+            this._boxPointerArrowside = this._calculateBoxPointerArrowSide();
+            if(!this._popup) {
+                this._popup = new AppFolderPopup(this, this._boxPointerArrowside);
+                this._parentView.addFolderPopup(this._popup);
+                this._popup.connect('open-state-changed', Lang.bind(this,
+                        function(popup, isOpen) {
+                            if (!isOpen) {
+                                this.actor.checked = false;
+                                this.returnSpaceToOriginalPosition();
+                            }
+                }));
+            } else
+                this._popup.updateBoxPointer(this._boxPointerArrowside);
+            this._updatePopUpSize();
+            this._updatePopupPosition();
+            this.makeSpaceForPopUp();
+            this.invalidatePopUp = false; 
         }
+    },
 
-        this._popup.connect('open-state-changed', Lang.bind(this,
-            function(popup, isOpen) {
-                if (!isOpen)
-                    this.actor.checked = false;
-            }));
+    onUpdatedDisplaySize: function(width, height) {
+        this._displayWidth = width;
+        this._displayHeight = height;
+        this.view.onUpdatedDisplaySize(width, height);
+        this.invalidatePopUp = true;
     },
+
 });
 
 const AppFolderPopup = new Lang.Class({
@@ -657,7 +1471,6 @@ const AppFolderPopup = new Lang.Class({
                                      visible: false,
                                      // We don't want to expand really, but look
                                      // at the layout manager of our parent...
-                                     //
                                      // DOUBLE HACK: if you set one, you automatically
                                      // get the effect for the other direction too, so
                                      // we need to set the y_align
@@ -665,10 +1478,12 @@ const AppFolderPopup = new Lang.Class({
                                      y_expand: true,
                                      x_align: Clutter.ActorAlign.CENTER,
                                      y_align: Clutter.ActorAlign.START });
+
         this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
                                                      { style_class: 'app-folder-popup-bin',
                                                        x_fill: true,
                                                        y_fill: true,
+                                                       x_expand: true,
                                                        x_align: St.Align.START });
 
         this._boxPointer.actor.style_class = 'app-folder-popup';
@@ -715,10 +1530,8 @@ const AppFolderPopup = new Lang.Class({
 
         this.actor.show();
         this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
-
         this._boxPointer.setArrowActor(this._source.actor);
-        this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+        this._boxPointer.show(BoxPointer.PopupAnimation.FADE | BoxPointer.PopupAnimation.SLIDE);
 
         this._isOpen = true;
         this.emit('open-state-changed', true);
@@ -728,10 +1541,15 @@ const AppFolderPopup = new Lang.Class({
         if (!this._isOpen)
             return;
 
-        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE | BoxPointer.PopupAnimation.SLIDE);
         this._isOpen = false;
         this.emit('open-state-changed', false);
+    },
+    
+    updateBoxPointer: function (side) {
+        this._arrowSide = side;
+        this._boxPointer._arrowSide = side;
+        this._boxPointer._border.queue_repaint();
     }
 });
 Signals.addSignalMethods(AppFolderPopup.prototype);
@@ -1027,3 +1845,4 @@ const AppIconMenu = new Lang.Class({
     }
 });
 Signals.addSignalMethods(AppIconMenu.prototype);
+
diff --git a/js/ui/boxpointer.js b/js/ui/boxpointer.js
index 2fd1a67..d6d3f84 100644
--- a/js/ui/boxpointer.js
+++ b/js/ui/boxpointer.js
@@ -219,7 +219,7 @@ const BoxPointer = new Lang.Class({
                 break;
         }
         this.bin.allocate(childBox, flags);
-
+        
         if (this._sourceActor && this._sourceActor.mapped) {
             this._reposition();
             this._updateFlip();
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index e26b6f1..c84ff9f 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -3,7 +3,9 @@
 const Clutter = imports.gi.Clutter;
 const Shell = imports.gi.Shell;
 const St = imports.gi.St;
+const Meta = imports.gi.Meta;
 
+const Signals = imports.signals;
 const Lang = imports.lang;
 const Params = imports.misc.params;
 
@@ -177,12 +179,23 @@ const IconGrid = new Lang.Class({
         params = Params.parse(params, { rowLimit: null,
                                         columnLimit: null,
                                         fillParent: false,
-                                        xAlign: St.Align.MIDDLE });
+                                        xAlign: St.Align.MIDDLE,
+                                        usePagination: false});
         this._rowLimit = params.rowLimit;
         this._colLimit = params.columnLimit;
         this._xAlign = params.xAlign;
         this._fillParent = params.fillParent;
-
+        this._usePagination = params.usePagination;
+        
+        this.top_padding = 0;
+        this.bottom_padding = 0;
+        this.right_padding = 0;
+        this.left_padding = 0;
+        
+        if(this._usePagination) {
+            this._nPages = 0;
+            this._firstPagesItems = [];
+        }
         this.actor = new St.BoxLayout({ style_class: 'icon-grid',
                                         vertical: true });
 
@@ -206,22 +219,14 @@ const IconGrid = new Lang.Class({
 
         let children = this._grid.get_children();
         let nColumns = this._colLimit ? Math.min(this._colLimit,
-                                                 children.length)
-                                      : children.length;
-        let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
+                                                                                children.length)
+                                                         : children.length;
+        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
-        alloc.min_size = this._hItemSize;
-        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;
+        alloc.min_size = this._hItemSize + this.left_padding + this.right_padding;
+        alloc.natural_size = nColumns * this._hItemSize + totalSpacing + this.left_padding + 
this.right_padding;
     },
 
     _getPreferredHeight: function (grid, forWidth, alloc) {
@@ -234,11 +239,11 @@ 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 spacing = this.getSpacing();
+        
         let nRows;
         if (nColumns > 0)
             nRows = Math.ceil(children.length / nColumns);
@@ -247,13 +252,26 @@ const IconGrid = new Lang.Class({
         if (this._rowLimit)
             nRows = Math.min(nRows, this._rowLimit);
         let totalSpacing = Math.max(0, nRows - 1) * spacing;
-        let height = nRows * this._vItemSize + totalSpacing;
+        let height = nRows * this._vItemSize + totalSpacing + this.top_padding + this.bottom_padding;     
+        if(this._usePagination && this._nPages) {
+            alloc.min_size =  this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            alloc.natural_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            return;
+        }
         alloc.min_size = height;
         alloc.natural_size = height;
     },
+    
+    _getVisibleChildren: function() {
+        let children = this._grid.get_children();
+        children = children.filter(function(actor) {
+            return actor.visible;
+        });
+        return children;
+    },
 
     _allocate: function (grid, box, flags) {
-        if (this._fillParent) {
+        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);
@@ -263,54 +281,46 @@ 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 leftPadding;
+        let spacing = this.getSpacing();
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);
+        let leftEmptySpace;
         switch(this._xAlign) {
             case St.Align.START:
-                leftPadding = 0;
+                leftEmptySpace = 0;
                 break;
             case St.Align.MIDDLE:
-                leftPadding = Math.floor((availWidth - usedWidth) / 2);
+                leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
                 break;
             case St.Align.END:
-                leftPadding = availWidth - usedWidth;
+                leftEmptySpace = availWidth - usedWidth;
         }
-
-        let x = box.x1 + leftPadding;
-        let y = box.y1;
+        
+        let x = box.x1 + leftEmptySpace + this.left_padding;
+        let y = box.y1 + this.top_padding;
         let columnIndex = 0;
         let rowIndex = 0;
+        if(this._usePagination && children.length > 0) {
+            this._firstPagesItems = [children[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._calculateChildrenBox(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._grid.set_skip_paint(children[i], true);
+            if(!this._usePagination) {
+                if (this._rowLimit && rowIndex >= this._rowLimit ||
+                        this._fillParent && childBox.y2 > availHeight - this.bottom_padding) {
+                    this._grid.set_skip_paint(children[i], true);
+                } else {
+                    children[i].allocate(childBox, flags);
+                    this._grid.set_skip_paint(children[i], false);
+                }
             } else {
                 children[i].allocate(childBox, flags);
                 this._grid.set_skip_paint(children[i], false);
             }
-
+            
             columnIndex++;
             if (columnIndex == nColumns) {
                 columnIndex = 0;
@@ -319,33 +329,86 @@ const IconGrid = new Lang.Class({
 
             if (columnIndex == 0) {
                 y += this._vItemSize + spacing;
-                x = box.x1 + leftPadding;
+                if(this._usePagination) {
+                    if((i + 1) % this._childrenPerPage == 0) {
+                        y+= this._spaceBetweenPages + this.top_padding;
+                        if(i < children.length) {
+                            this._firstPagesItems.push(children[i+1]);
+                        }
+                    }
+                }
+                x = box.x1 + leftEmptySpace + this.left_padding;
             } else {
                 x += this._hItemSize + spacing;
             }
         }
     },
-
-    childrenInRow: function(rowWidth) {
-        return this._computeLayout(rowWidth)[0];
+    
+    _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._vItemSize + spacing;
+        // We want to contain the grid inside the parent box with padding
+        availHeightPerPage -= this.top_padding + this.bottom_padding;
+        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._vItemSize + 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);
+            /*Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
+                this._grid.queue_relayout();
+                return false;
+            }));*/
+        }
     },
-
-    getRowLimit: function() {
-        return this._rowLimit;
+    
+    _calculateChildrenBox: function(child, x, y, box) {
+        let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
+        = child.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);
+        }
+        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 usedWidth = this.left_padding + this.right_padding;
+        let spacing = this.getSpacing();
         while ((this._colLimit == null || nColumns < this._colLimit) &&
                (usedWidth + this._hItemSize <= forWidth)) {
             usedWidth += this._hItemSize + spacing;
@@ -354,8 +417,7 @@ const IconGrid = new Lang.Class({
 
         if (nColumns > 0)
             usedWidth -= spacing;
-
-        return [nColumns, usedWidth, spacing];
+        return [nColumns, usedWidth];
     },
 
     _onStyleChanged: function() {
@@ -365,6 +427,61 @@ const IconGrid = new Lang.Class({
         this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
         this._grid.queue_relayout();
     },
+    
+    columnsForWidth: function(rowWidth) {
+        return this._computeLayout(rowWidth)[0];
+    },
+
+    getRowLimit: function() {
+        return this._rowLimit;
+    },
+
+    nUsedRows: function(forWidth) {
+        let children = this._getVisibleChildren();
+        let nColumns;
+        if (forWidth < 0) {
+            nColumns = children.length;
+        } else {
+            [nColumns, ] = this._computeLayout(forWidth);
+        }
+        let nRows;
+        if (nColumns > 0)
+            nRows = Math.ceil(children.length / nColumns);
+        else
+            nRows = 0;
+        if (this._rowLimit)
+            nRows = Math.min(nRows, this._rowLimit);
+        return nRows;
+    },
+
+    rowsForHeight: function(forHeight) {
+        forHeight -= this.top_padding + this.bottom_padding;
+        let spacePerRow = this._vItemSize + this.getSpacing();
+        let rowsPerPage = Math.floor(forHeight / spacePerRow);
+        // Check if deleting spacing from bottom there's enough space for another row
+        let spaceWithOneMoreRow = (rowsPerPage + 1) * spacePerRow - this.getSpacing();
+        rowsPerPage = spaceWithOneMoreRow <= forHeight? rowsPerPage + 1 : rowsPerPage;
+        return rowsPerPage;
+    },
+
+    /**
+     * Don't take into account paddings
+     */
+    usedHeightForNRows: function(nRows) {
+        let spacePerRow = this.rowHeight();
+        return spacePerRow * nRows - this.getSpacing();
+    },
+
+    usedHeightPerPage: function() {
+        return this._rowsPerPage * this._spacePerRow - this.getSpacing() + this.top_padding + 
this.bottom_padding;
+    },
+
+    usedWidth: function(forWidth) {
+        let childrenInRow = this.childrenInRow(forWidth);
+        let usedWidth = childrenInRow  * (this._hItemSize + this.getSpacing());
+        usedWidth -= this.getSpacing();
+        return usedWidth + this.left_padding + this.right_padding;
+    },
 
     removeAll: function() {
         this._grid.destroy_all_children();
@@ -383,5 +500,156 @@ const IconGrid = new Lang.Class({
 
     visibleItemsCount: function() {
         return this._grid.get_n_children() - this._grid.get_n_skip_paint();
+    },
+    
+    nPages: function() {
+        return this._nPages;
+    },
+    
+    getPagePosition: function(pageNumber) {
+        if(!this._nPages)
+            return;
+        if(pageNumber == 0)
+            return [0, 0];
+        if(pageNumber < 0 || pageNumber > this._nPages) {
+            throw new Error('Invalid page number ' + pageNumber);
+        }
+        let childBox = this._firstPagesItems[pageNumber].get_allocation_box();
+        return [childBox.x1 - this.top_padding, childBox.y1 - this.top_padding];
+    },
+    
+    setSpacing: function(spacing) {
+            this._fixedSpacing = spacing;
+    },
+    
+    getSpacing: function() {
+        return this._fixedSpacing ? this._fixedSpacing : this._spacing;
+    },
+    /**
+     * This functions 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, minColumns, minRows, 
usingSurroundingSpacing) {
+        // 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 - minRows * this._vItemSize);
+        let minEmptyHorizontalArea = (availWidth - minColumns * this._hItemSize);
+        let spacing;
+        if(usingSurroundingSpacing) {
+            // 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 / (minRows +1));
+            let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / (minColumns +1));
+            let spacingToEnsureMinimums = Math.min(maxSpacingForRows, maxSpacingForColumns);
+            let spacingNotTooBig = Math.min(spacingToEnsureMinimums, maxSpacing);
+            spacing = Math.max(this._spacing, spacingNotTooBig);
+        } else {
+            if(minRows == 1) {
+                let maxSpacingForRows = Math.floor(minEmptyVerticalArea / minRows);
+                let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / minColumns);
+            } else {
+                let maxSpacingForRows = Math.floor(minEmptyVerticalArea / (minRows - 1));
+                let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / (minColumns - 1));
+            }
+            let spacingToEnsureMinimums = Math.min(maxSpacingForRows, maxSpacingForColumns);
+            let spacingNotTooBig = Math.min(spacingToEnsureMinimums, maxSpacing);
+            spacing = Math.max(this._spacing, spacingNotTooBig); 
+        }
+        return spacing;
+    },
+
+    calculateResponsiveGrid: function(availWidth, availHeight) {
+        this._fixedHItemSize = this._hItemSize;
+        this._fixedVItemSize = this._vItemSize;
+
+        let spacing = this.maxSpacingForWidthHeight(availWidth, availHeight);
+        this.setSpacing(spacing);
+        if(this._useSurroundingSpacing)
+            this.top_padding = this.bottom_padding = this.right_padding = this.left_padding = spacing;
+
+        let count = 0;
+        if(this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < 
this._minRows) {
+            let neededWidth, neededHeight;
+            if(this._useSurroundingSpacing)
+                neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth ;
+            else
+                neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth ;
+
+            if(this._useSurroundingSpacing)
+                neededHeight = this.usedHeightForNRows(this._minRows) - availHeight;
+            else
+                neededHeight = this.usedHeightForNRows(this._minRows) - availHeight ;
+
+            if(neededWidth > neededHeight) {
+                let neededSpaceForEachItem = Math.ceil(neededWidth / this._minColumns);
+                this._fixedHItemSize = this._hItemSize - neededSpaceForEachItem;
+                this._fixedVItemSize = this._vItemSize - neededSpaceForEachItem;
+            } else {
+                let neededSpaceForEachItem = Math.ceil(neededHeight / this._minRows);
+                this._fixedHItemSize = this._hItemSize - neededSpaceForEachItem;
+                this._fixedVItemSize = this._vItemSize - neededSpaceForEachItem;
+            }
+            
+            if(this._fixedHItemSize < MIN_ICON_SIZE)
+                this._fixedHItemSize = MIN_ICON_SIZE;
+            if(this._fixedVItemSize < MIN_ICON_SIZE)
+                this._fixedVItemSize = MIN_ICON_SIZE;
+            
+            let spacing = this.maxSpacingForWidthHeight(availWidth, availHeight);
+            this.setSpacing(spacing);
+            if(this._useSurroundingSpacing)
+                this.top_padding = this.bottom_padding = this.right_padding = this.left_padding = spacing;
+        }
+        
+        let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, 
this._vItemSize);
+        this.updateChildrenScale(scale);
+        if(this._usePagination)
+            this._calculatePaginationValues(availWidth, availHeight);
+    },
+    /**
+     * FLORIAN REVIEW
+     * We are supossing that the this._items contain some item that we can set its size. 
+     * Also, we suposse that they are icons, and the original size is ICON_SIZE, to let the good icon size 
when updating the size.
+     * Also, we supose that we need a Meta.later, since when we call calculateResponsiveGrid that calls 
updateChildrenScale
+     * we are inside the allocation of the AppDisplay, and modifinyg icon size can cause allocation cycles
+     * So this functions is not intentded to be called outside this class, lets think a little about that. 
Now reescaling icons
+     * works fine at least.
+     */
+    updateChildrenScale: function(scale) {
+        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
+            for(let i in this._items) {
+                let newIconSize = Math.floor(ICON_SIZE * scale);
+                this._items[i].setIconSize(newIconSize);
+            }
+        }));
+    },
+
+    pageRows: function(pageNumber) {
+        let pagePosition = this.getPagePosition(pageNumber);
+        let currentRowItemsYPosition = pagePosition;
+        let rows = [];
+        let currentItem = this._firstPagesItems[pageNumber];
+        let children = this._grid.get_children();
+        let index = 0;
+        // Positioning to the first element of the page
+        while(children[index] != this._firstPagesItems[pageNumber])
+            index++;
+        for( let rowIndex = 0; rowIndex < this._rowsPerPage && index < children.length; rowIndex++) {
+            rows[rowIndex] = [];
+            while(index < children.length && children[index].y == currentItem.y ) {
+                rows[rowIndex].push(children[index]);
+                index++;
+            }
+            currentItem = children[index];
+        }
+        return rows;
+    },
+    
+    rowHeight: function() {
+        return this._vItemSize + this.getSpacing();
     }
+    
 });
+Signals.addSignalMethods(IconGrid.prototype);
diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js
index 2177874..a7a1cde 100644
--- a/js/ui/searchDisplay.js
+++ b/js/ui/searchDisplay.js
@@ -320,14 +320,14 @@ const GridSearchResults = new Lang.Class({
     },
 
     _getMaxDisplayedResults: function() {
-        return this._grid.childrenInRow(this._bin.width) * this._grid.getRowLimit();
+        return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit();
     },
 
     _renderResults: function(metas) {
         for (let i = 0; i < metas.length; i++) {
             let display = new GridSearchResult(this.provider, metas[i], this._terms);
             display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
-            this._grid.addItem(display.actor);
+            this._grid.addItem(display);
         }
     },
 



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