[gnome-shell/gbsneto/pagination: 13/22] iconGrid: Use IconGridLayout



commit 75e62e7ec96208f9db00a7c618ec426f37a14317
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Tue May 19 10:43:10 2020 -0300

    iconGrid: Use IconGridLayout
    
    Replace the current grid code with IconGridLayout.
    
    https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271

 data/theme/gnome-shell-sass/widgets/_app-grid.scss |  10 +-
 js/ui/appDisplay.js                                |  90 +--
 js/ui/iconGrid.js                                  | 640 ++++++---------------
 3 files changed, 191 insertions(+), 549 deletions(-)
---
diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss 
b/data/theme/gnome-shell-sass/widgets/_app-grid.scss
index c2b8f7e848..f0334f3972 100644
--- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss
+++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss
@@ -1,17 +1,11 @@
 /* App Grid */
 
 $app_icon_size: 96px;
-$app_icon_padding: 24px;
 
 // app icons
 .icon-grid {
-  -shell-grid-horizontal-item-size: $app_icon_size + $app_icon_padding * 2;
-  -shell-grid-vertical-item-size: $app_icon_size + $app_icon_padding * 2;
-  spacing: $base_spacing * 6;
-
-  .overview-icon {
-    icon-size: $app_icon_size;
-  }
+  row-spacing: $base_spacing * 6;
+  column-spacing: $base_spacing * 6;
 }
 
 /* App Icons */
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 5ab871f07d..0097c3d9f5 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -19,9 +19,6 @@ const Util = imports.misc.util;
 const SystemActions = imports.misc.systemActions;
 
 var MENU_POPUP_TIMEOUT = 600;
-var MAX_COLUMNS = 6;
-var MIN_COLUMNS = 4;
-var MIN_ROWS = 4;
 
 var INACTIVE_GRID_OPACITY = 77;
 // This time needs to be less than IconGrid.EXTRA_SPACE_ANIMATION_TIME
@@ -32,7 +29,6 @@ var FOLDER_SUBICON_FRACTION = .4;
 var VIEWS_SWITCH_TIME = 400;
 var VIEWS_SWITCH_ANIMATION_DELAY = 100;
 
-var PAGE_SWITCH_TIME = 250;
 var SCROLL_TIMEOUT_TIME = 150;
 
 var APP_ICON_SCALE_IN_TIME = 500;
@@ -128,17 +124,10 @@ var BaseAppView = GObject.registerClass({
         'view-loaded': {},
     },
 }, class BaseAppView extends St.Widget {
-    _init(params = {}, gridParams) {
+    _init(params = {}) {
         super._init(params);
 
-        gridParams = Params.parse(gridParams, {
-            columnLimit: MAX_COLUMNS,
-            minRows: MIN_ROWS,
-            minColumns: MIN_COLUMNS,
-            padWithSpacing: true,
-        }, true);
-
-        this._grid = new IconGrid.IconGrid(gridParams);
+        this._grid = new IconGrid.IconGrid();
         this._grid.connect('child-focused', (grid, actor) => {
             this._childFocused(actor);
         });
@@ -188,7 +177,7 @@ var BaseAppView = GObject.registerClass({
             let iconIndex = newApps.indexOf(icon);
 
             this._orderedItems.splice(iconIndex, 0, icon);
-            this._grid.addItem(icon, iconIndex);
+            this._grid.addItem(icon);
             this._items.set(icon.id, icon);
         });
 
@@ -338,19 +327,13 @@ class AppDisplay extends BaseAppView {
         });
         this.add_actor(this._stack);
 
-        let box = new St.BoxLayout({
-            vertical: true,
-            y_align: Clutter.ActorAlign.START,
-        });
-        box.add_child(this._grid);
-
         this._scrollView = new St.ScrollView({
             style_class: 'all-apps',
             x_expand: true,
             y_expand: true,
             reactive: true,
         });
-        this._scrollView.add_actor(box);
+        this._scrollView.add_actor(this._grid);
         this._stack.add_actor(this._scrollView);
 
         this._eventBlocker = new St.Widget({
@@ -380,8 +363,6 @@ class AppDisplay extends BaseAppView {
 
         this._folderIcons = [];
 
-        this._grid.currentPage = 0;
-
         this._scrollView.connect('scroll-event', this._onScroll.bind(this));
 
         this._swipeTracker = new SwipeTracker.SwipeTracker(
@@ -503,6 +484,16 @@ class AppDisplay extends BaseAppView {
         this.selectApp(item.id);
     }
 
+    _isItemInFolder(itemId) {
+        for (let folder of this._folderIcons) {
+            let folderApps = folder.getAppIds();
+            if (folderApps.some(appId => appId === itemId))
+                return true;
+        }
+
+        return false;
+    }
+
     _refilterApps() {
         let filteredApps = this._orderedItems.filter(icon => !icon.visible);
 
@@ -578,6 +569,7 @@ class AppDisplay extends BaseAppView {
                 });
             }
 
+            icon.visible = !this._isItemInFolder(appId);
             appIcons.push(icon);
         });
 
@@ -627,7 +619,7 @@ class AppDisplay extends BaseAppView {
     }
 
     goToPage(pageNumber, animate = true) {
-        pageNumber = clamp(pageNumber, 0, this._grid.nPages() - 1);
+        pageNumber = clamp(pageNumber, 0, this._grid.nPages - 1);
 
         if (this._grid.currentPage === pageNumber &&
             this._displayingDialog &&
@@ -636,23 +628,7 @@ class AppDisplay extends BaseAppView {
         if (this._displayingDialog && this._currentDialog)
             this._currentDialog.popdown();
 
-        if (!this.mapped) {
-            this._adjustment.value = this._grid.getPageY(pageNumber);
-            this._pageIndicators.setCurrentPosition(pageNumber);
-            this._grid.currentPage = pageNumber;
-            return;
-        }
-
-        if (this._grid.currentPage === pageNumber)
-            return;
-
-        this._grid.currentPage = pageNumber;
-
-        // Animate the change between pages.
-        this._adjustment.ease(this._grid.getPageY(this._grid.currentPage), {
-            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
-            duration: animate ? PAGE_SWITCH_TIME : 0,
-        });
+        this._grid.goToPage(pageNumber, animate);
     }
 
     _onScroll(actor, event) {
@@ -692,7 +668,7 @@ class AppDisplay extends BaseAppView {
         adjustment.remove_transition('value');
 
         let progress = adjustment.value / adjustment.page_size;
-        let points = Array.from({ length: this._grid.nPages() }, (v, i) => i);
+        let points = Array.from({ length: this._grid.nPages }, (v, i) => i);
 
         tracker.confirmSwipe(this._scrollView.height,
             points, progress, Math.round(progress));
@@ -793,21 +769,15 @@ class AppDisplay extends BaseAppView {
         box = this._grid.get_theme_node().get_content_box(box);
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
-        let oldNPages = this._grid.nPages();
+        let oldNPages = this._grid.nPages;
 
         this._grid.adaptToSize(availWidth, availHeight);
 
-        let fadeOffset = Math.min(this._grid.topPadding,
-                                  this._grid.bottomPadding);
-        this._scrollView.update_fade_effect(fadeOffset, 0);
-        if (fadeOffset > 0)
-            this._scrollView.get_effect('fade').fade_edges = true;
-
-        if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != 
this._grid.nPages()) {
+        if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != 
this._grid.nPages) {
             Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                 this._adjustment.value = 0;
                 this._grid.currentPage = 0;
-                this._pageIndicators.setNPages(this._grid.nPages());
+                this._pageIndicators.setNPages(this._grid.nPages);
                 this._pageIndicators.setCurrentPosition(0);
                 return GLib.SOURCE_REMOVE;
             });
@@ -1064,8 +1034,6 @@ class FolderView extends BaseAppView {
             layout_manager: new Clutter.BinLayout(),
             x_expand: true,
             y_expand: true,
-        }, {
-            minRows: 1,
         });
 
         // If it not expand, the parent doesn't take into account its preferred_width when allocating
@@ -1144,20 +1112,6 @@ class FolderView extends BaseAppView {
         this._parentAvailableHeight = height;
 
         this._grid.adaptToSize(width, height);
-
-        // To avoid the fade effect being applied to the unscrolled grid,
-        // the offset would need to be applied after adjusting the padding;
-        // however the final padding is expected to be too small for the
-        // effect to look good, so use the unadjusted padding
-        let fadeOffset = Math.min(this._grid.topPadding,
-                                  this._grid.bottomPadding);
-        this._scrollView.update_fade_effect(fadeOffset, 0);
-
-        // Set extra padding to avoid popup or close button being cut off
-        this._grid.topPadding = Math.max(this._grid.topPadding, 0);
-        this._grid.bottomPadding = Math.max(this._grid.bottomPadding, 0);
-        this._grid.leftPadding = Math.max(this._grid.leftPadding, 0);
-        this._grid.rightPadding = Math.max(this._grid.rightPadding, 0);
     }
 
     _loadApps() {
@@ -1691,8 +1645,6 @@ var AppFolderDialog = GObject.registerClass({
             contentBox.get_width(),
             contentBox.get_height() - entryBoxHeight - spacing);
 
-        this._view._grid.topPadding = 0;
-
         super.vfunc_allocate(box);
 
         // We can only start zooming after receiving an allocation
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 711f2be024..edef71b4ad 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -1,22 +1,20 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
-/* exported BaseIcon, IconGrid, PaginatedIconGrid */
+/* exported BaseIcon, IconGrid, IconGridLayout */
 
-const { Clutter, GLib, GObject, Graphene, Meta, St } = imports.gi;
+const { Clutter, GLib, GObject, Meta, St } = imports.gi;
 
 const Params = imports.misc.params;
 const Main = imports.ui.main;
 
 var ICON_SIZE = 96;
-var MIN_ICON_SIZE = 16;
 
 var ANIMATION_TIME_IN = 350;
 var ANIMATION_TIME_OUT = 1 / 2 * ANIMATION_TIME_IN;
 var ANIMATION_MAX_DELAY_FOR_ITEM = 2 / 3 * ANIMATION_TIME_IN;
-var ANIMATION_BASE_DELAY_FOR_ITEM = 1 / 4 * ANIMATION_MAX_DELAY_FOR_ITEM;
 var ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2 / 3 * ANIMATION_TIME_OUT;
 var ANIMATION_FADE_IN_TIME_FOR_ITEM = 1 / 4 * ANIMATION_TIME_IN;
 
-var ANIMATION_BOUNCE_ICON_SCALE = 1.1;
+var PAGE_SWITCH_TIME = 300;
 
 var AnimationDirection = {
     IN: 0,
@@ -24,7 +22,6 @@ var AnimationDirection = {
 };
 
 var IconSize = {
-    HUGE: 128,
     LARGE: 96,
     MEDIUM: 64,
     SMALL: 32,
@@ -278,7 +275,7 @@ var IconGridLayout = GObject.registerClass({
     Signals: {
         'pages-changed': {},
     },
-}, class OmniGridLayout extends Clutter.LayoutManager {
+}, class IconGridLayout extends Clutter.LayoutManager {
     _init(params = {}) {
         params = Params.parse(params, {
             allow_incomplete_pages: true,
@@ -798,6 +795,22 @@ var IconGridLayout = GObject.registerClass({
         return [...this._pages[pageIndex].children];
     }
 
+    /**
+     * getItemPage:
+     * @param {BaseIcon} item: the item
+     *
+     * Retrieves the page @item is in, or -1 if @item is not part of the grid.
+     *
+     * @returns {int} the page where @item is in
+     */
+    getItemPage(item) {
+        if (!this._items.has(item))
+            return -1;
+
+        const itemData = this._items.get(item);
+        return itemData.pageIndex;
+    }
+
     // eslint-disable-next-line camelcase
     get allow_incomplete_pages() {
         return this._allowIncompletePages;
@@ -979,78 +992,67 @@ var IconGridLayout = GObject.registerClass({
 });
 
 var IconGrid = GObject.registerClass({
-    Signals: { 'animation-done': {},
-               'child-focused': { param_types: [Clutter.Actor.$gtype] } },
-}, class IconGrid extends St.Widget {
-    _init(params) {
-        super._init({ style_class: 'icon-grid',
-                      y_align: Clutter.ActorAlign.START });
-
-        params = Params.parse(params, { rowLimit: null,
-                                        columnLimit: null,
-                                        minRows: 1,
-                                        minColumns: 1,
-                                        fillParent: false,
-                                        xAlign: St.Align.MIDDLE,
-                                        padWithSpacing: false });
-        this._rowLimit = params.rowLimit;
-        this._colLimit = params.columnLimit;
-        this._minRows = params.minRows;
-        this._minColumns = params.minColumns;
-        this._xAlign = params.xAlign;
-        this._fillParent = params.fillParent;
-        this._padWithSpacing = params.padWithSpacing;
-
-        this.topPadding = 0;
-        this.bottomPadding = 0;
-        this.rightPadding = 0;
-        this.leftPadding = 0;
-
-        this._nPages = 0;
-        this.currentPage = 0;
-        this._rowsPerPage = 0;
-        this._spaceBetweenPages = 0;
-        this._childrenPerPage = 0;
+    Signals: {
+        'pages-changed': {},
+        'animation-done': {},
+        'child-focused': { param_types: [Clutter.Actor.$gtype] },
+    },
+}, class IconGrid extends St.Viewport {
+    _init(layoutParams = {}) {
+        layoutParams = Params.parse(layoutParams, {
+            allow_incomplete_pages: false,
+            orientation: Clutter.Orientation.VERTICAL,
+            columns_per_page: 6,
+            rows_per_page: 4,
+            page_halign: Clutter.ActorAlign.CENTER,
+            page_valign: Clutter.ActorAlign.CENTER,
+            last_row_align: Clutter.ActorAlign.START,
+            column_spacing: 0,
+            row_spacing: 0,
+        });
+        const layoutManager = new IconGridLayout(layoutParams);
 
-        this._updateIconSizesLaterId = 0;
+        super._init({
+            style_class: 'icon-grid',
+            layout_manager: layoutManager,
+            x_expand: true,
+            y_expand: true,
+        });
 
-        this._items = [];
+        this._currentPage = 0;
         this._clonesAnimating = [];
-        // Pulled from CSS, but hardcode some defaults here
-        this._spacing = 0;
-        this._hItemSize = this._vItemSize = ICON_SIZE;
-        this._fixedHItemSize = this._fixedVItemSize = undefined;
-        this.connect('style-changed', this._onStyleChanged.bind(this));
 
         this.connect('actor-added', this._childAdded.bind(this));
         this.connect('actor-removed', this._childRemoved.bind(this));
-        this.connect('destroy', this._onDestroy.bind(this));
     }
 
-    vfunc_unmap() {
-        // Cancel animations when hiding the overview, to avoid icons
-        // swarming into the void ...
-        this._resetAnimationActors();
-        super.vfunc_unmap();
+    _getChildrenToAnimate() {
+        let layoutManager = this.layout_manager;
+        let children = layoutManager.getChildrenAtPage(this._currentPage);
+
+        return children.filter(c => c.visible);
     }
 
-    _onDestroy() {
-        if (this._updateIconSizesLaterId) {
-            Meta.later_remove(this._updateIconSizesLaterId);
-            this._updateIconSizesLaterId = 0;
-        }
+    _resetAnimationActors() {
+        this._clonesAnimating.forEach(clone => {
+            clone.source.reactive = true;
+            clone.source.opacity = 255;
+            clone.destroy();
+        });
+        this._clonesAnimating = [];
     }
 
-    _keyFocusIn(actor) {
-        this.emit('child-focused', actor);
+    _animationDone() {
+        this._resetAnimationActors();
+        this.emit('animation-done');
     }
 
     _childAdded(grid, child) {
-        child._iconGridKeyFocusInId = child.connect('key-focus-in', this._keyFocusIn.bind(this));
+        child._iconGridKeyFocusInId = child.connect('key-focus-in', actor => this.emit('child-focused', 
actor));
 
         child._paintVisible = child.opacity > 0;
         child._opacityChangedId = child.connect('notify::opacity', () => {
-            let paintVisible = child._paintVisible;
+            const paintVisible = child._paintVisible;
             child._paintVisible = child.opacity > 0;
             if (paintVisible !== child._paintVisible)
                 this.queue_relayout();
@@ -1064,214 +1066,137 @@ var IconGrid = GObject.registerClass({
         child.disconnect(child._opacityChangedId);
         delete child._opacityChangedId;
         delete child._paintVisible;
+
     }
 
-    vfunc_get_preferred_width(_forHeight) {
-        if (this._fillParent)
-            // Ignore all size requests of children and request a size of 0;
-            // later we'll allocate as many children as fit the parent
-            return [0, 0];
+    vfunc_unmap() {
+        // Cancel animations when hiding the overview, to avoid icons
+        // swarming into the void ...
+        this._resetAnimationActors();
+        super.vfunc_unmap();
+    }
+
+    vfunc_style_changed() {
+        super.vfunc_style_changed();
 
-        let nChildren = this.get_n_children();
-        let nColumns = this._colLimit
-            ? Math.min(this._colLimit, nChildren)
-            : nChildren;
-        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
-        let minSize = this._getHItemSize() + this.leftPadding + this.rightPadding;
-        let natSize = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding;
+        let node = this.get_theme_node();
+        let [found, len] = node.lookup_length('row-spacing', false);
+        this.layout_manager.row_spacing = found ? len : 0;
 
-        return this.get_theme_node().adjust_preferred_width(minSize, natSize);
+        [found, len] = node.lookup_length('column-spacing', false);
+        this.layout_manager.column_spacing = found ? len : 0;
     }
 
-    _getVisibleChildren() {
-        return this.get_children().filter(actor => actor.visible);
-    }
+    /**
+     * addItem:
+     * @param {Clutter.Actor} item: item to append to the grid
+     * @param {int} index: position in the page
+     * @param {int} page: page number
+     *
+     * Adds @item to the grid. @item must not be part of the grid.
+     *
+     * If @index exceeds the number of items per page, @item will
+     * be added to the next page.
+     *
+     * @page must be a number between 0 and the number of pages.
+     * Adding to the page after next will create a new page.
+     */
+    addItem(item, index = -1, page = -1) {
+        if (!(item.icon instanceof BaseIcon))
+            throw new Error('Only items with a BaseIcon icon property can be added to IconGrid');
 
-    _availableHeightPerPageForItems() {
-        return this.usedHeightForNRows(this._rowsPerPage) - (this.topPadding + this.bottomPadding);
+        this.layout_manager.addItem(item, index, page);
+        this.add_child(item);
     }
 
-    vfunc_get_preferred_height() {
-        let height = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * 
this._nPages + this._spaceBetweenPages * this._nPages;
-        return [height, height];
+    /**
+     * appendItem:
+     * @param {Clutter.Actor} item: item to append to the grid
+     *
+     * Appends @item to the grid. @item must not be part of the grid.
+     */
+    appendItem(item) {
+        this.layout_manager.appendItem(item);
     }
 
-    vfunc_allocate(box) {
-        if (this._childrenPerPage == 0)
-            log('computePages() must be called before allocate(); pagination will not work.');
+    /**
+     * removeItem:
+     * @param {Clutter.Actor} item: item to remove from the grid
+     *
+     * Removes @item to the grid. @item must be part of the grid.
+     */
+    removeItem(item) {
+        if (!this.contains(item))
+            throw new Error(`Item ${item} is not part of the IconGrid`);
 
-        this.set_allocation(box);
+        this.layout_manager.removeItem(item);
+        this.remove_child(item);
+    }
 
-        if (this._fillParent) {
-            // Reset the passed in box to fill the parent
-            let parentBox = this.get_parent().allocation;
-            let gridBox = this.get_theme_node().get_content_box(parentBox);
-            box = this.get_theme_node().get_content_box(gridBox);
-        }
-        let children = this._getVisibleChildren();
-        let availWidth = box.x2 - box.x1;
-        let spacing = this._getSpacing();
-        let [nColumns, usedWidth] = this._computeLayout(availWidth);
+    /**
+     * goToPage:
+     * @param {int} pageIndex: page index
+     * @param {boolean} animate: animate the page transition
+     *
+     * Moves the current page to @pageIndex. @pageIndex must be a valid page
+     * number.
+     */
+    goToPage(pageIndex, animate = true) {
+        if (pageIndex >= this.nPages)
+            throw new Error(`IconGrid does not have page ${pageIndex}`);
 
-        let leftEmptySpace;
-        switch (this._xAlign) {
-        case St.Align.START:
-            leftEmptySpace = 0;
+        let newValue;
+        let adjustment;
+        switch (this.layout_manager.orientation) {
+        case Clutter.Orientation.VERTICAL:
+            adjustment = this.vadjustment;
+            newValue = pageIndex * this.layout_manager.pageHeight;
             break;
-        case St.Align.MIDDLE:
-            leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
+        case Clutter.Orientation.HORIZONTAL:
+            adjustment = this.hadjustment;
+            newValue = pageIndex * this.layout_manager.pageWidth;
             break;
-        case St.Align.END:
-            leftEmptySpace = availWidth - usedWidth;
-        }
-
-        let x = box.x1 + leftEmptySpace + this.leftPadding;
-        let y = box.y1 + this.topPadding;
-        let columnIndex = 0;
-
-        let nChangedIcons = 0;
-        for (let i = 0; i < children.length; i++) {
-            let childBox = this._calculateChildBox(children[i], x, y, box);
-
-            if (animateIconPosition(children[i], childBox, nChangedIcons))
-                nChangedIcons++;
-
-            children[i].show();
-
-            columnIndex++;
-            if (columnIndex == nColumns)
-                columnIndex = 0;
-
-            if (columnIndex == 0) {
-                y += this._getVItemSize() + spacing;
-                if ((i + 1) % this._childrenPerPage == 0)
-                    y +=  this._spaceBetweenPages - spacing + this.bottomPadding + this.topPadding;
-                x = box.x1 + leftEmptySpace + this.leftPadding;
-            } else {
-                x += this._getHItemSize() + spacing;
-            }
         }
-    }
-
-    vfunc_get_paint_volume(paintVolume) {
-        // Setting the paint volume does not make sense when we don't have
-        // any allocation
-        if (!this.has_allocation())
-            return false;
-
-        let themeNode = this.get_theme_node();
-        let allocationBox = this.get_allocation_box();
-        let paintBox = themeNode.get_paint_box(allocationBox);
-
-        let origin = new Graphene.Point3D();
-        origin.x = paintBox.x1 - allocationBox.x1;
-        origin.y = paintBox.y1 - allocationBox.y1;
-        origin.z = 0.0;
-
-        paintVolume.set_origin(origin);
-        paintVolume.set_width(paintBox.x2 - paintBox.x1);
-        paintVolume.set_height(paintBox.y2 - paintBox.y1);
-
-        if (this.get_clip_to_allocation())
-            return true;
-
-        for (let child = this.get_first_child();
-            child != null;
-            child = child.get_next_sibling()) {
-
-            if (!child.visible || !child.opacity)
-                continue;
 
-            let childVolume = child.get_transformed_paint_volume(this);
-            if (!childVolume)
-                return false;
+        this._currentPage = pageIndex;
 
-            paintVolume.union(childVolume);
+        if (!this.mapped) {
+            adjustment.value = newValue;
+            return;
         }
 
-        return true;
+        adjustment.ease(newValue, {
+            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
+            duration: animate ? PAGE_SWITCH_TIME : 0,
+        });
     }
 
-    /*
-     * Intended to be override by subclasses if they need a different
-     * set of items to be animated.
+    /**
+     * getItemPage:
+     * @param {BaseIcon} item: the item
+     *
+     * Retrieves the page @item is in, or -1 if @item is not part of the grid.
+     *
+     * @returns {int} the page where @item is in
      */
-    _getChildrenToAnimate() {
-        const children = this._getVisibleChildren().filter(child => child.opacity > 0);
-        let firstIndex = this._childrenPerPage * this.currentPage;
-        let lastIndex = firstIndex + this._childrenPerPage;
-
-        return children.slice(firstIndex, lastIndex);
+    getItemPage(item) {
+        return this.layout_manager.getItemPage(item);
     }
 
-    _resetAnimationActors() {
-        this._clonesAnimating.forEach(clone => {
-            clone.source.reactive = true;
-            clone.source.opacity = 255;
-            clone.destroy();
-        });
-        this._clonesAnimating = [];
+    get currentPage() {
+        return this._currentPage;
     }
 
-    _animationDone() {
-        this._resetAnimationActors();
-        this.emit('animation-done');
+    set currentPage(v) {
+        this.goToPage(v);
     }
 
-    animatePulse(animationDirection) {
-        if (animationDirection != AnimationDirection.IN) {
-            throw new GObject.NotImplementedError("Pulse animation only implements " +
-                                                  "'in' animation direction");
-        }
-
-        this._resetAnimationActors();
-
-        let actors = this._getChildrenToAnimate();
-        if (actors.length == 0) {
-            this._animationDone();
-            return;
-        }
+    get nPages() {
+        return this.layout_manager.nPages;
+    }
 
-        // For few items the animation can be slow, so use a smaller
-        // delay when there are less than 4 items
-        // (ANIMATION_BASE_DELAY_FOR_ITEM = 1/4 *
-        // ANIMATION_MAX_DELAY_FOR_ITEM)
-        let maxDelay = Math.min(ANIMATION_BASE_DELAY_FOR_ITEM * actors.length,
-                                ANIMATION_MAX_DELAY_FOR_ITEM);
-
-        for (let index = 0; index < actors.length; index++) {
-            let actor = actors[index];
-            actor.set_scale(0, 0);
-            actor.set_pivot_point(0.5, 0.5);
-
-            let delay = index / actors.length * maxDelay;
-            let bounceUpTime = ANIMATION_TIME_IN / 4;
-            let isLastItem = index == actors.length - 1;
-            actor.ease({
-                scale_x: ANIMATION_BOUNCE_ICON_SCALE,
-                scale_y: ANIMATION_BOUNCE_ICON_SCALE,
-                duration: bounceUpTime,
-                mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
-                delay,
-                onComplete: () => {
-                    let duration = ANIMATION_TIME_IN - bounceUpTime;
-                    actor.ease({
-                        scale_x: 1,
-                        scale_y: 1,
-                        duration,
-                        mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
-                        onComplete: () => {
-                            if (isLastItem)
-                                this._animationDone();
-                            actor.reactive = true;
-                        },
-                    });
-                },
-            });
-        }
+    adaptToSize(width, height) {
+        this.layout_manager.adaptToSize(width, height);
     }
 
     animateSpring(animationDirection, sourceActor) {
@@ -1318,11 +1243,11 @@ var IconGrid = GObject.registerClass({
          */
 
         this._clonesAnimating.forEach(actorClone => {
-            let actor = actorClone.source;
+            const actor = actorClone.source;
             actor.opacity = 0;
             actor.reactive = false;
 
-            let [width, height] = this._getAllocatedChildSizeAndSpacing(actor);
+            let [width, height] = actor.get_size();
             actorClone.set_size(width, height);
             let scaleX = sourceScaledWidth / width;
             let scaleY = sourceScaledHeight / height;
@@ -1391,238 +1316,9 @@ var IconGrid = GObject.registerClass({
         });
     }
 
-    _getAllocatedChildSizeAndSpacing(child) {
-        let [,, natWidth, natHeight] = child.get_preferred_size();
-        let width = Math.min(this._getHItemSize(), natWidth);
-        let xSpacing = Math.max(0, width - natWidth) / 2;
-        let height = Math.min(this._getVItemSize(), natHeight);
-        let ySpacing = Math.max(0, height - natHeight) / 2;
-        return [width, height, xSpacing, ySpacing];
-    }
-
-    _calculateChildBox(child, x, y, box) {
-        /* Center the item in its allocation horizontally */
-        let [width, height, childXSpacing, childYSpacing] =
-            this._getAllocatedChildSizeAndSpacing(child);
-
-        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;
-    }
-
-    columnsForWidth(rowWidth) {
-        return this._computeLayout(rowWidth)[0];
-    }
-
-    getRowLimit() {
-        return this._rowLimit;
-    }
-
-    _computeLayout(forWidth) {
-        this.ensure_style();
-
-        let nColumns = 0;
-        let usedWidth = this.leftPadding + this.rightPadding;
-        let spacing = this._getSpacing();
-
-        while ((this._colLimit == null || nColumns < this._colLimit) &&
-               (usedWidth + this._getHItemSize() <= forWidth)) {
-            usedWidth += this._getHItemSize() + spacing;
-            nColumns += 1;
-        }
-
-        if (nColumns > 0)
-            usedWidth -= spacing;
-
-        return [nColumns, usedWidth];
-    }
-
-    _onStyleChanged() {
-        let themeNode = this.get_theme_node();
-        this._spacing = themeNode.get_length('spacing');
-        this._hItemSize = themeNode.get_length('-shell-grid-horizontal-item-size') || ICON_SIZE;
-        this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
-        this.queue_relayout();
-    }
-
-    nRows(forWidth) {
-        let children = this._getVisibleChildren();
-        let nColumns = forWidth < 0 ? children.length : this._computeLayout(forWidth)[0];
-        let nRows = nColumns > 0 ? Math.ceil(children.length / nColumns) : 0;
-        if (this._rowLimit)
-            nRows = Math.min(nRows, this._rowLimit);
-        return nRows;
-    }
-
-    rowsForHeight(forHeight) {
-        return Math.floor((forHeight - (this.topPadding + this.bottomPadding) + this._getSpacing()) / 
(this._getVItemSize() + this._getSpacing()));
-    }
-
-    usedHeightForNRows(nRows) {
-        return (this._getVItemSize() + this._getSpacing()) * nRows - this._getSpacing() + this.topPadding + 
this.bottomPadding;
-    }
-
-    usedWidth(forWidth) {
-        return this.usedWidthForNColumns(this.columnsForWidth(forWidth));
-    }
-
-    usedWidthForNColumns(columns) {
-        let usedWidth = columns  * (this._getHItemSize() + this._getSpacing());
-        usedWidth -= this._getSpacing();
-        return usedWidth + this.leftPadding + this.rightPadding;
-    }
-
-    addItem(item, index) {
-        if (!(item.icon instanceof BaseIcon))
-            throw new Error('Only items with a BaseIcon icon property can be added to IconGrid');
-
-        this._items.push(item);
-        if (index !== undefined)
-            this.insert_child_at_index(item, index);
-        else
-            this.add_actor(item);
-    }
-
-    removeItem(item) {
-        this.remove_child(item);
-    }
+    get appsPerPage() {
+        const layoutManager = this.layout_manager;
 
-    setSpacing(spacing) {
-        this._fixedSpacing = spacing;
-    }
-
-    _getSpacing() {
-        return this._fixedSpacing ? this._fixedSpacing : this._spacing;
-    }
-
-    _getHItemSize() {
-        return this._fixedHItemSize ? this._fixedHItemSize : this._hItemSize;
-    }
-
-    _getVItemSize() {
-        return this._fixedVItemSize ? this._fixedVItemSize : this._vItemSize;
-    }
-
-    _updateSpacingForSize(availWidth, availHeight) {
-        let maxEmptyVArea = availHeight - this._minRows * this._getVItemSize();
-        let maxEmptyHArea = availWidth - this._minColumns * this._getHItemSize();
-        let maxHSpacing, maxVSpacing;
-
-        if (this._padWithSpacing) {
-            // 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
-            maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows + 1));
-            maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns + 1));
-        } else {
-            if (this._minRows <=  1)
-                maxVSpacing = maxEmptyVArea;
-            else
-                maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows - 1));
-
-            if (this._minColumns <=  1)
-                maxHSpacing = maxEmptyHArea;
-            else
-                maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns - 1));
-        }
-
-        let maxSpacing = Math.min(maxHSpacing, maxVSpacing);
-        // Limit spacing to the item size
-        maxSpacing = Math.min(maxSpacing, Math.min(this._getVItemSize(), this._getHItemSize()));
-        // The minimum spacing, regardless of whether it satisfies the row/columng minima,
-        // is the spacing we get from CSS.
-        let spacing = Math.max(this._spacing, maxSpacing);
-        this.setSpacing(spacing);
-        if (this._padWithSpacing)
-            this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing;
-    }
-
-    _computePages(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);
-
-        // We want to contain the grid inside the parent box with padding
-        this._rowsPerPage = this.rowsForHeight(availHeightPerPage);
-        this._nPages = Math.ceil(nRows / this._rowsPerPage);
-        this._spaceBetweenPages = availHeightPerPage - (this.topPadding + this.bottomPadding) - 
this._availableHeightPerPageForItems();
-        this._childrenPerPage = nColumns * this._rowsPerPage;
-    }
-
-    /*
-     * This function must to be called before iconGrid allocation,
-     * to know how much spacing can the grid has
-     */
-    adaptToSize(availWidth, availHeight) {
-        this._fixedHItemSize = this._hItemSize;
-        this._fixedVItemSize = this._vItemSize;
-        this._updateSpacingForSize(availWidth, availHeight);
-
-        if (this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < 
this._minRows) {
-            let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth;
-            let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight;
-
-            let neededSpacePerItem = neededWidth > neededHeight
-                ? Math.ceil(neededWidth / this._minColumns)
-                : Math.ceil(neededHeight / this._minRows);
-            this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE);
-            this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE);
-
-            this._updateSpacingForSize(availWidth, availHeight);
-        }
-        if (!this._updateIconSizesLaterId) {
-            this._updateIconSizesLaterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
-                                                          this._updateIconSizes.bind(this));
-        }
-        this._computePages(availWidth, availHeight);
-    }
-
-    // Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up
-    _updateIconSizes() {
-        this._updateIconSizesLaterId = 0;
-        let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, 
this._vItemSize);
-        let newIconSize = Math.floor(ICON_SIZE * scale);
-        for (let i in this._items)
-            this._items[i].icon.setIconSize(newIconSize);
-
-        return GLib.SOURCE_REMOVE;
-    }
-
-    nPages() {
-        return this._nPages;
-    }
-
-    getPageHeight() {
-        return this._availableHeightPerPageForItems();
-    }
-
-    getPageY(pageNumber) {
-        if (!this._nPages)
-            return 0;
-
-        let firstPageItem = pageNumber * this._childrenPerPage;
-        let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
-        return childBox.y1 - this.topPadding;
-    }
-
-    getItemPage(item) {
-        let children = this._getVisibleChildren();
-        let index = children.indexOf(item);
-        if (index == -1)
-            throw new Error('Item not found.');
-        return Math.floor(index / this._childrenPerPage);
+        return layoutManager.rows_per_page * layoutManager.columns_per_page;
     }
 });


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