[gnome-shell/wip/paging-release2: 1/9] appDisplay, IconGrid: Added pagination
- From: Carlos Soriano <csoriano src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/paging-release2: 1/9] appDisplay, IconGrid: Added pagination
- Date: Mon, 19 Aug 2013 18:02:09 +0000 (UTC)
commit c9ab4b306bd371b8e292942c2336a9781f9107f9
Author: Carlos Soriano <carlos soriano89 gmail com>
Date: Mon Aug 12 19:22:03 2013 +0200
appDisplay, IconGrid: Added pagination
https://bugzilla.gnome.org/show_bug.cgi?id=706081
data/theme/gnome-shell.css | 4 +-
js/ui/appDisplay.js | 631 +++++++++++++++++++++++++++++++++-----------
js/ui/iconGrid.js | 326 +++++++++++++++++++----
3 files changed, 742 insertions(+), 219 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 84768b1..1387b2b 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -899,10 +899,10 @@ StScrollBar StButton#vhandle:active {
.search-display > StBoxLayout,
-.all-apps > StBoxLayout,
+.all-apps,
.frequent-apps > StBoxLayout {
/* horizontal padding to make sure scrollbars or dash don't overlap content */
- padding: 0px 88px;
+ padding: 0px 88px 10px 88px;
}
.app-folder-icon {
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 9132d18..1a98694 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -30,10 +30,14 @@ const Util = imports.misc.util;
const MAX_APPLICATION_WORK_MILLIS = 75;
const MENU_POPUP_TIMEOUT = 600;
const MAX_COLUMNS = 6;
+const MIN_COLUMNS = 4;
+const MIN_ROWS = 4;
const INACTIVE_GRID_OPACITY = 77;
+const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
const FOLDER_SUBICON_FRACTION = .4;
+const PAGE_SWITCH_TIME = 0.25;
// Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
function _loadCategory(dir, view) {
@@ -60,11 +64,9 @@ const AlphabeticalView = new Lang.Class({
_init: function() {
this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
- columnLimit: MAX_COLUMNS });
-
+ columnLimit: MAX_COLUMNS });
// Standard hack for ClutterBinLayout
this._grid.actor.x_expand = true;
-
this._items = {};
this._allItems = [];
},
@@ -111,127 +113,169 @@ const AlphabeticalView = new Lang.Class({
}
});
-const FolderView = new Lang.Class({
- Name: 'FolderView',
+const PaginatedAlphabeticalView = new Lang.Class({
+ Name: 'PaginatedAlphabeticalView',
Extends: AlphabeticalView,
_init: function() {
- this.parent();
- this.actor = this._grid.actor;
- },
+ this._grid = new IconGrid.PaginatedIconGrid({ xAlign: St.Align.MIDDLE,
+ columnLimit:
MAX_COLUMNS });
+ // Standard hack for ClutterBinLayout
+ this._grid.actor.x_expand = true;
+ this._items = {};
+ this._allItems = [];
+ }
+});
- _getItemId: function(item) {
- return item.get_id();
- },
+const PaginationScrollView = new Lang.Class({
+ Name: 'PaginationScrollView',
+ Extends: St.Bin,
- _createItemIcon: function(item) {
- return new AppIcon(item);
+ _init: function(parent, params) {
+ params['reactive'] = true;
+ this.parent(params);
+ this._parent = parent;
},
- _compareItems: function(a, b) {
- return a.compare_by_name(b);
+ vfunc_get_preferred_height: function (forWidht) {
+ return [0, 0];
},
- addApp: function(app) {
- this._addItem(app);
+ vfunc_get_preferred_width: function(forHeight) {
+ return [0, 0];
},
- 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;
- }
-});
-
-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;
- }
- return [minBottom, naturalBottom];
+ vfunc_allocate: function(box, flags) {
+ box = this.get_parent().allocation;
+ box = this.get_theme_node().get_content_box(box);
+ this.set_allocation(box, flags);
+ let availWidth = box.x2 - box.x1;
+ let availHeight = box.y2 - box.y1;
+ let childBox = new Clutter.ActorBox();
+ childBox.x1 = 0;
+ childBox.y1 = 0;
+ childBox.x2 = availWidth;
+ childBox.y2 = availHeight;
+ let child = this.get_child();
+ child.allocate(childBox, flags);
+ this._parent.updateAdjustment(availHeight);
}
});
const AllView = new Lang.Class({
Name: 'AllView',
- Extends: AlphabeticalView,
-
+ Extends: PaginatedAlphabeticalView,
+
_init: function() {
this.parent();
-
- this._grid.actor.y_align = Clutter.ActorAlign.START;
- this._grid.actor.y_expand = true;
-
- let box = new St.BoxLayout({ vertical: true });
- this._stack = new St.Widget({ layout_manager: new AllViewLayout() });
+ this._paginationView = new PaginationScrollView(this, {style_class: 'all-apps'});
+ let layout = new Clutter.BinLayout();
+ this.actor = new St.Widget({ layout_manager: layout,
+ x_expand:true, y_expand:true });
+ layout.add(this._paginationView, Clutter.ActorAlign.CENTER, Clutter.ActorAlign.CENTER);
+ this._grid.connect('n-pages-changed', Lang.bind(this, this._updatedNPages));
+
+ this._folderIcons = [];
+
+ this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+ this._box = new St.BoxLayout({ vertical: true });
+ this._verticalAdjustment = new St.Adjustment();
+ this._horizontalAdjustment = new St.Adjustment();
+ this._box.set_adjustments(this._horizontalAdjustment, this._verticalAdjustment);
+
+ this._grid._viewForPageSize = this._paginationView;
+ this._currentPage = 0;
this._stack.add_actor(this._grid.actor);
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
- this._stack.add_actor(this._eventBlocker);
- box.add(this._stack, { y_align: St.Align.START, expand: true });
-
- this.actor = new St.ScrollView({ x_fill: true,
- y_fill: false,
- y_align: St.Align.START,
- x_expand: true,
- y_expand: true,
- overlay_scrollbars: true,
- style_class: 'all-apps vfade' });
- this.actor.add_actor(box);
- this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
- let action = new Clutter.PanAction({ interpolate: true });
- action.connect('pan', Lang.bind(this, this._onPan));
- this.actor.add_action(action);
+ this._stack.add_actor(this._eventBlocker, {x_align:St.Align.MIDDLE});
+
+ this._box.add_actor(this._stack);
+ this._paginationView.add_actor(this._box);
+
+ this._paginationView.connect('scroll-event', Lang.bind(this, this._onScroll));
this._clickAction = new Clutter.ClickAction();
this._clickAction.connect('clicked', Lang.bind(this, function() {
if (!this._currentPopup)
return;
-
let [x, y] = this._clickAction.get_coords();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
if (!this._currentPopup.actor.contains(actor))
this._currentPopup.popdown();
}));
this._eventBlocker.add_action(this._clickAction);
+ // When the number of pages change (i.e. when changing screen resolution or during clutter false
allocations)
+ // we have to tell pagination that the adjustment is not correct (since the allocated size of
pagination changed)
+ // For that problem we return to the first page of pagination.
+ this.invalidatePagination = false;
+ },
+
+ _updatedNPages: function(iconGrid, nPages) {
+ // We don't need a relayout because we already done it at iconGrid
+ // when pages are calculated (and then the signal is emitted before that)");
+ this.invalidatePagination = true;
+ },
+
+ goToPage: function(pageNumber) {
+ if(pageNumber < this._grid.nPages() && pageNumber >= 0) {
+ this._currentPage = pageNumber;
+ let params = { value: this._grid.getPagePosition(this._currentPage)[1],
+ time: PAGE_SWITCH_TIME,
+ transition: 'easeOutQuad'
+ };
+ Tweener.addTween(this._verticalAdjustment, params);
+ }
},
- _onPan: function(action) {
- this._clickAction.release();
+ _onScroll: function(actor, event) {
+ let direction = event.get_scroll_direction();
+ let nextPage;
+ if (direction == Clutter.ScrollDirection.UP)
+ if(this._currentPage > 0) {
+ nextPage = this._currentPage - 1;
+ this.goToPage(nextPage);
+ }
+ if (direction == Clutter.ScrollDirection.DOWN)
+ if(this._currentPage < (this._grid.nPages() - 1)) {
+ nextPage = this._currentPage + 1;
+ this.goToPage(nextPage);
+ }
+ },
- let [dist, dx, dy] = action.get_motion_delta(0);
- let adjustment = this.actor.vscroll.adjustment;
- adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
+ _onKeyRelease: function(actor, event) {
+ if (event.get_key_symbol() == Clutter.KEY_Up) {
+ this.goToNextPage();
+ return true;
+ } else if(event.get_key_symbol() == Clutter.KEY_Down) {
+ this.goToPreviousPage();
+ return true;
+ }
return false;
},
+ addApp: function(app) {
+ this._addItem(app);
+ },
+
+ addFolder: function(dir) {
+ this._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);
+ }));
+ },
+
+ removeAll: function() {
+ this._folderIcons = [];
+ this.parent();
+ },
+
_getItemId: function(item) {
if (item instanceof Shell.App)
return item.get_id();
@@ -244,9 +288,11 @@ const AllView = new Lang.Class({
_createItemIcon: function(item) {
if (item instanceof Shell.App)
return new AppIcon(item);
- else if (item instanceof GMenu.TreeDirectory)
- return new FolderIcon(item, this);
- else
+ else if (item instanceof GMenu.TreeDirectory) {
+ let folderIcon = new FolderIcon(item, this);
+ this._folderIcons.push(folderIcon);
+ return folderIcon;
+ } else
return null;
},
@@ -258,46 +304,55 @@ const AllView = new Lang.Class({
return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0);
},
- addApp: function(app) {
- let appIcon = this._addItem(app);
- if (appIcon)
- appIcon.actor.connect('key-focus-in',
- Lang.bind(this, this._ensureIconVisible));
- },
-
- addFolder: function(dir) {
- let folderIcon = this._addItem(dir);
- if (folderIcon)
- folderIcon.actor.connect('key-focus-in',
- Lang.bind(this, this._ensureIconVisible));
- },
-
- 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;
- }
- }));
- },
-
- _ensureIconVisible: function(icon) {
- Util.ensureActorVisibleInScrollView(this.actor, icon);
+ updateAdjustment: function(availHeight) {
+ this._verticalAdjustment.page_size = availHeight;
+ this._verticalAdjustment.upper = this._stack.height;
+ if(this.invalidatePagination)
+ this.goToPage(0);
+ this.invalidatePagination = false;
},
_updateIconOpacities: function(folderOpen) {
for (let id in this._items) {
- if (folderOpen && !this._items[id].actor.checked)
- this._items[id].actor.opacity = INACTIVE_GRID_OPACITY;
- else
- this._items[id].actor.opacity = 255;
+ if (folderOpen && !this._items[id].actor.checked) {
+ let params = { opacity: INACTIVE_GRID_OPACITY,
+ time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
+ transition: 'easeOutQuad'
+ };
+ Tweener.addTween(this._items[id].actor, params);
+ }
+ else {
+ let params = { opacity: 255,
+ time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
+ transition: 'easeOutQuad'
+ };
+ Tweener.addTween(this._items[id].actor, params);
+ }
+ }
+ },
+
+ onUpdatedDisplaySize: function(width, height) {
+ let box = new Clutter.ActorBox();
+ box.x1 = 0;
+ box.x2 = width;
+ box.y1 = 0;
+ box.y2 = height;
+ box = this.actor.get_theme_node().get_content_box(box);
+ box = this._paginationView.get_theme_node().get_content_box(box);
+ box = this._grid.actor.get_theme_node().get_content_box(box);
+ let availWidth = box.x2 - box.x1;
+ let availHeight = box.y2 - box.y1;
+
+ // Update grid dinamyc spacing based on display width
+ let spacing = this._grid.maxSpacingForWidthHeight(availWidth, availHeight, MIN_COLUMNS, MIN_ROWS,
true);
+ 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(availWidth, availHeight);
}
}
});
@@ -328,6 +383,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 +434,32 @@ const ControlsBoxLayout = Lang.Class({
}
});
+const ViewStackLayout = new Lang.Class({
+ Name: 'ViewStackLayout',
+ Extends: Clutter.BinLayout,
+
+ vfunc_allocate: function (actor, box, flags) {
+ let availWidth = box.x2 - box.x1;
+ let availHeight = box.y2 - box.y1;
+ // Prepare children of all views for the upcomming allocation, calculate all
+ // the needed values in the responsive design we are trying to emulate
+ this.emit('allocated-size-changed', availWidth, availHeight);
+ this.parent(actor, box, flags);
+ },
+
+ vfunc_set_container: function(container) {
+ if(this._styleChangedId) {
+ this._container.disconnect(this._styleChangedId);
+ this._styleChangedId = 0;
+ }
+ if(container != null)
+ this._styleChangedId = container.connect('style-changed', Lang.bind(this,
+ function() { this.spacing = this._container.get_theme_node().get_length('spacing'); }));
+ this._container = container;
+ }
+});
+Signals.addSignalMethods(ViewStackLayout.prototype);
+
const AppDisplay = new Lang.Class({
Name: 'AppDisplay',
@@ -396,20 +495,21 @@ const AppDisplay = new Lang.Class({
x_expand: true });
this._views[Views.ALL] = { 'view': view, 'control': button };
- this.actor = new St.BoxLayout({ style_class: 'app-display',
- vertical: true,
- x_expand: true, y_expand: true });
+ this.actor = new St.Widget({ style_class: 'app-display',
+ x_expand: true, y_expand: true });
+ this._viewStackLayout = new ViewStackLayout();
+ this.actor.set_layout_manager(new Clutter.BoxLayout({vertical: true}));
+ this._viewStackLayout.connect('allocated-size-changed', Lang.bind(this, this._onUpdatedDisplaySize));
- this._viewStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
- x_expand: true, y_expand: true });
- this.actor.add(this._viewStack, { expand: true });
+ this._viewStack = new St.Widget({ x_expand: true, y_expand: true });
+ this._viewStack.set_layout_manager(this._viewStackLayout);
+ this.actor.add_actor(this._viewStack, { expand: true });
let layout = new ControlsBoxLayout({ homogeneous: true });
this._controls = new St.Widget({ style_class: 'app-view-controls',
layout_manager: layout });
layout.hookup_style(this._controls);
- this.actor.add(new St.Bin({ child: this._controls }));
-
+ this.actor.add_actor(new St.Bin({ child: this._controls }));
for (let i = 0; i < this._views.length; i++) {
this._viewStack.add_actor(this._views[i].view.actor);
@@ -429,9 +529,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 +609,20 @@ const AppDisplay = new Lang.Class({
if (focused)
this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}
+ },
+
+ _onUpdatedDisplaySize: function(actor, width, height) {
+ let box = new Clutter.ActorBox();
+ box.x1 = 0;
+ box.x2 = width;
+ box.y1 = 0;
+ box.y2 = height;
+ box = this._viewStack.get_theme_node().get_content_box(box);
+ let availWidth = box.x2 - box.x1;
+ let availHeight = box.y2 - box.y1;
+ for (let i = 0; i < this._views.length; i++) {
+ this._views[i].view.onUpdatedDisplaySize(availWidth, availHeight);
+ }
}
});
@@ -568,12 +682,119 @@ const AppSearchProvider = new Lang.Class({
}
});
+const FolderView = new Lang.Class({
+ Name: 'FolderView',
+ Extends: AlphabeticalView,
+
+ _init: function() {
+ this.parent({ xAlign: St.Align.MIDDLE});
+ // If it not expand, the parent doesn't take into account its preferred_width when allocating
+ // the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
+ this._grid.actor.x_expand = 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);
+ },
+
+ _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) {
+ 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 itemWidth = this._grid._hItemSize * MAX_COLUMNS;
+ let emptyArea = width - itemWidth;
+ let spacing;
+ spacing = Math.max(this._grid._spacing, emptyArea / ( 2 * MAX_COLUMNS));
+ spacing = Math.round(spacing);
+ this._grid.setSpacing(spacing);
+ },
+
+ _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;
+ let maxUsedWidth = this._grid.usedWidth(availWidthPerPage);
+ return maxUsedWidth;
+ },
+
+ usedHeight: function() {
+ // Then calculate the real maxUsedHeight
+ return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce());
+ },
+
+ nRowsDisplayedAtOnce: function() {
+ let box = this._containerBox();
+ let availHeightPerPage = box.y2 - box.y1;
+ let availWidthPerPage = box.x2 - box.x1;
+ 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;
+ let availWidthPerPage = box.x2 - box.x1;
+ let maxRowsPerPage = this._grid.rowsForHeight(availHeightPerPage);
+ //Then, we can only show that rows least one.
+ maxRowsPerPage -= 1;
+ return maxRowsPerPage;
+ }
+});
+
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,6 +803,7 @@ const FolderIcon = new Lang.Class({
x_fill: true,
y_fill: true });
this.actor._delegate = this;
+ this._parentView = parentView;
let label = this._dir.get_name();
this.icon = new IconGrid.BaseIcon(label,
@@ -590,7 +812,6 @@ 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();
@@ -607,39 +828,126 @@ const FolderIcon = new Lang.Class({
},
_createIcon: function(size) {
- return this.view.createFolderIcon(size);
+ return this.view.createFolderIcon(size, this);
+ },
+
+ _updatePopupPosition: function() {
+ if(this._popup) {
+ // Position the popup above or below the source icon
+ if (this._side == St.Side.BOTTOM) {
+ let closeButtonOffset = -this._popup.closeButton.translation_y;
+ let y = this.actor.y - this._popup.actor.fixed_height;
+ let yWithButton = y - closeButtonOffset;
+ this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
+ this._popup.actor.y = Math.max(y, closeButtonOffset);
+ } else {
+ this._popup.actor.y = this.actor.y + this.actor.height;
+ }
+ }
+ },
+
+ _popUpWidth: function() {
+ return this.view.usedWidth();
+ },
+
+ _popUpHeight: function() {
+ /*
+ * To maintain the grid of the collection aligned to the main grid, we have to
+ * make the same spacing to each element of the collection as the main grid has, except
+ * for the last row which has to take less space, since the grid of collection is inside a view with
padding (the popup)
+ * and, the arrow of the popup is rising some pixels the collection, we have to calculate how much
real spacing
+ * we have to let under/above the last/first arrow to make let the collection grid aligned with the
main grid
+ */
+ let arrowHeight = this._popup._boxPointer.actor.get_theme_node().get_length('-arrow-rise');
+ let popupPadding = this._popup._boxPointer.bin.get_theme_node().get_length('padding');
+ //It will be negative value, so we have to rest it, instead of plust it.
+ let closeButtonOverlap =
this._popup.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
+ let closeButtonHeight = this._popup.closeButton.height;
+ let usedHeight = this.view.usedHeight();
+ // If we want it corrected aligned with the main grid the calculation will be: usedHeight -
popupPadding - arrowHeight
+ // but, if we do that and the popup needs all the height, the popup will remain outside the
allocation and then clipped. so:
+ if(this.view.nRowsDisplayedAtOnce() == this.view.maxRowsDisplayedAtOnce())
+ usedHeight = usedHeight - popupPadding * 2 - arrowHeight + closeButtonOverlap;
+ else
+ usedHeight = usedHeight - popupPadding - arrowHeight;
+ return usedHeight;
+
},
_ensurePopup: function() {
- if (this._popup)
+ if(this._popup){
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._popup.connect('open-state-changed', Lang.bind(this,
- function(popup, isOpen) {
+ let absoluteActorYPosition = this.actor.get_transformed_position()[1];
+ let spaceTop = absoluteActorYPosition;
+ let spaceBottom = this.actor.get_stage().height - (absoluteActorYPosition + this.actor.height);
+ this._side = spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
+ this._popup = new AppFolderPopup(this, this._side);
+ this._parentView.addFolderPopup(this._popup);
+ /**
+ * 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.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._popUpWidth());
+
+ /*
+ * 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
+ */
+ let usedHeight = this._popUpHeight();
+ this.view.actor.set_height(this._popUpHeight());
+
+ this._updatePopupPosition();
+
+ 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);
+ },
+
});
const AppFolderPopup = new Lang.Class({
@@ -669,6 +977,7 @@ const AppFolderPopup = new Lang.Class({
{ 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';
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 416e659..2fa981e 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
+const Signals = imports.signals;
const Lang = imports.lang;
const Params = imports.misc.params;
@@ -182,10 +183,8 @@ const IconGrid = new Lang.Class({
this._colLimit = params.columnLimit;
this._xAlign = params.xAlign;
this._fillParent = params.fillParent;
-
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
- vertical: true });
-
+ vertical: true });
// Pulled from CSS, but hardcode some defaults here
this._spacing = 0;
this._hItemSize = this._vItemSize = ICON_SIZE;
@@ -208,7 +207,7 @@ const IconGrid = new Lang.Class({
let nColumns = this._colLimit ? Math.min(this._colLimit,
nChildren)
: nChildren;
- let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
+ let totalSpacing = Math.max(0, nColumns - 1) * this.getSpacing();
// Kind of a lie, but not really an issue right now. If
// we wanted to support some sort of hidden/overflow that would
// need higher level design
@@ -216,27 +215,17 @@ const IconGrid = new Lang.Class({
alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
},
- _getVisibleChildren: function() {
- let children = this._grid.get_children();
- children = children.filter(function(actor) {
- return actor.visible;
- });
- return children;
- },
-
_getPreferredHeight: function (grid, forWidth, alloc) {
if (this._fillParent)
// Ignore all size requests of children and request a size of 0;
// later we'll allocate as many children as fit the parent
return;
-
let children = this._getVisibleChildren();
let nColumns, spacing;
if (forWidth < 0) {
nColumns = children.length;
- spacing = this._spacing;
} else {
- [nColumns, , spacing] = this._computeLayout(forWidth);
+ [nColumns, ] = this._computeLayout(forWidth);
}
let nRows;
@@ -246,14 +235,14 @@ const IconGrid = new Lang.Class({
nRows = 0;
if (this._rowLimit)
nRows = Math.min(nRows, this._rowLimit);
- let totalSpacing = Math.max(0, nRows - 1) * spacing;
+ let totalSpacing = Math.max(0, nRows - 1) * this.getSpacing();
let height = nRows * this._vItemSize + totalSpacing;
alloc.min_size = height;
alloc.natural_size = height;
},
_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,8 +252,8 @@ const IconGrid = new Lang.Class({
let children = this._getVisibleChildren();
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
-
- let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
+ let spacing = this.getSpacing();
+ let [nColumns, usedWidth] = this._computeLayout(availWidth);
let leftPadding;
switch(this._xAlign) {
@@ -282,41 +271,25 @@ const IconGrid = new Lang.Class({
let y = box.y1;
let columnIndex = 0;
let rowIndex = 0;
+
for (let i = 0; i < children.length; i++) {
- let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
- = children[i].get_preferred_size();
-
- /* Center the item in its allocation horizontally */
- let width = Math.min(this._hItemSize, childNaturalWidth);
- let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
- let height = Math.min(this._vItemSize, childNaturalHeight);
- let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
-
- let childBox = new Clutter.ActorBox();
- if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
- let _x = box.x2 - (x + width);
- childBox.x1 = Math.floor(_x - childXSpacing);
- } else {
- childBox.x1 = Math.floor(x + childXSpacing);
+ let childBox = this._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._fillParent && childBox.y2 >= availHeight) {
this._grid.set_skip_paint(children[i], true);
} else {
children[i].allocate(childBox, flags);
this._grid.set_skip_paint(children[i], false);
}
-
columnIndex++;
if (columnIndex == nColumns) {
columnIndex = 0;
rowIndex++;
}
-
if (columnIndex == 0) {
y += this._vItemSize + spacing;
x = box.x1 + leftPadding;
@@ -326,25 +299,33 @@ const IconGrid = new Lang.Class({
}
},
- childrenInRow: function(rowWidth) {
- return this._computeLayout(rowWidth)[0];
- },
+ _calculateChildrenBox: function(child, x, y, box) {
+ let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
+ = child.get_preferred_size();
- getRowLimit: function() {
- return this._rowLimit;
+ /* Center the item in its allocation horizontally */
+ let width = Math.min(this._hItemSize, childNaturalWidth);
+ let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
+ let height = Math.min(this._vItemSize, childNaturalHeight);
+ let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
+
+ let childBox = new Clutter.ActorBox();
+ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
+ let _x = box.x2 - (x + width);
+ childBox.x1 = Math.floor(_x - childXSpacing);
+ } else {
+ childBox.x1 = Math.floor(x + childXSpacing);
+ }
+ childBox.y1 = Math.floor(y + childYSpacing);
+ childBox.x2 = childBox.x1 + width;
+ childBox.y2 = childBox.y1 + height;
+ return childBox;
},
_computeLayout: function (forWidth) {
let nColumns = 0;
let usedWidth = 0;
- let spacing = this._spacing;
-
- if (this._colLimit) {
- let itemWidth = this._hItemSize * this._colLimit;
- let emptyArea = forWidth - itemWidth;
- spacing = Math.max(this._spacing, emptyArea / (2 * this._colLimit));
- spacing = Math.round(spacing);
- }
+ let spacing = this.getSpacing();
while ((this._colLimit == null || nColumns < this._colLimit) &&
(usedWidth + this._hItemSize <= forWidth)) {
@@ -354,8 +335,7 @@ const IconGrid = new Lang.Class({
if (nColumns > 0)
usedWidth -= spacing;
-
- return [nColumns, usedWidth, spacing];
+ return [nColumns, usedWidth];
},
_onStyleChanged: function() {
@@ -366,6 +346,62 @@ const IconGrid = new Lang.Class({
this._grid.queue_relayout();
},
+ _getVisibleChildren: function() {
+ let children = this._grid.get_children();
+ children = children.filter(function(actor) {
+ return actor.visible;
+ });
+ return children;
+ },
+
+ childrenInRow: function(rowWidth) {
+ return this._computeLayout(rowWidth)[0];
+ },
+
+ getRowLimit: function() {
+ return this._rowLimit;
+ },
+
+ 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) {
+ 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;
+ },
+
+ usedHeightForNRows: function(nRows) {
+ let spacePerRow = this._vItemSize + this.getSpacing();
+ return spacePerRow * nRows;
+ },
+
+ usedWidth: function(forWidth) {
+ let childrenInRow = this.childrenInRow(forWidth);
+ let usedWidth = childrenInRow * (this._hItemSize + this.getSpacing());
+ usedWidth -= this.getSpacing();
+ return usedWidth;
+ },
+
removeAll: function() {
this._grid.destroy_all_children();
},
@@ -383,5 +419,183 @@ const IconGrid = new Lang.Class({
visibleItemsCount: function() {
return this._grid.get_n_children() - this._grid.get_n_skip_paint();
+ },
+
+ setSpacing: function(spacing) {
+ this._fixedSpacing = spacing;
+ },
+
+ getSpacing: function() {
+ return this._fixedSpacing ? this._fixedSpacing : this._spacing;
+ },
+
+ /**
+ * This 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;
}
});
+Signals.addSignalMethods(IconGrid.prototype);
+
+const PaginatedIconGrid = new Lang.Class({
+ Name: 'PaginatedIconGrid',
+ Extends: IconGrid,
+
+ _init: function(params) {
+ this.parent(params);
+ this._nPages = 0;
+ //Set this variable properly before allocate function is called
+ this._viewForPageSize = null;
+ },
+
+ _getPreferredHeight: function (grid, forWidth, alloc) {
+ if(this._nPages) {
+ alloc.min_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+ alloc.natural_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+ return;
+ }
+ this.parent(grid, forWidth, alloc);
+ },
+
+ _allocate: function (grid, box, flags) {
+ if(this._fillParent) {
+ // Reset the passed in box to fill the parent
+ let parentBox = this.actor.get_parent().allocation;
+ let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
+ box = this._grid.get_theme_node().get_content_box(gridBox);
+ }
+ let children = this._getVisibleChildren();
+ let availWidth = box.x2 - box.x1;
+ let availHeight = box.y2 - box.y1;
+ let spacing = this.getSpacing();
+ let [nColumns, usedWidth] = this._computeLayout(availWidth);
+
+ // ScrollView height
+ let parentBox = this._viewForPageSize.allocation;
+ let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
+ let customBox = this._grid.get_theme_node().get_content_box(gridBox);
+ let availWidth = customBox.x2 - customBox.x1;
+ let availHeightPerPage = customBox.y2 - customBox.y1;
+ let nRows;
+ 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;
+ this._calculatePaginationValues(availHeightPerPage, nColumns, nRows);
+ // 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);
+ }
+
+ let leftPadding;
+ switch(this._xAlign) {
+ case St.Align.START:
+ leftPadding = 0;
+ break;
+ case St.Align.MIDDLE:
+ leftPadding = Math.floor((availWidth - usedWidth) / 2);
+ break;
+ case St.Align.END:
+ leftPadding = availWidth - usedWidth;
+ }
+
+ let x = box.x1 + leftPadding;
+ let y = box.y1;
+ let columnIndex = 0;
+ let rowIndex = 0;
+
+ for (let i = 0; i < children.length; i++) {
+ let childBox = this._calculateChildrenBox(children[i], x, y, box);
+ if(children[i].translate_y) {
+ childBox.y1 += children[i].translate_y;
+ childBox.y2 += children[i].translate_y;
+ }
+ children[i].allocate(childBox, flags);
+ this._grid.set_skip_paint(children[i], false);
+
+ columnIndex++;
+ if (columnIndex == nColumns) {
+ columnIndex = 0;
+ rowIndex++;
+ }
+ if (columnIndex == 0) {
+ y += this._vItemSize + spacing;
+ if((i + 1) % this._childrenPerPage == 0) {
+ y+= this._spaceBetweenPages - spacing;
+ }
+ x = box.x1 + leftPadding;
+ } else {
+ x += this._hItemSize + spacing;
+ }
+ }
+ },
+
+ _calculatePaginationValues: function (availHeightPerPage, nColumns, nRows) {
+ let spacing = this.getSpacing();
+ this._spacePerRow = this._vItemSize + spacing;
+ this._rowsPerPage = Math.floor(availHeightPerPage / this._spacePerRow);
+ // Check if deleting spacing from bottom there's enough space for another row
+ let spaceWithOneMoreRow = (this._rowsPerPage + 1) * this._spacePerRow - spacing;
+ this._rowsPerPage = spaceWithOneMoreRow <= availHeightPerPage? this._rowsPerPage + 1 :
this._rowsPerPage;
+ this._nPages = Math.ceil(nRows / this._rowsPerPage);
+ this._spaceBetweenPages = availHeightPerPage - this.usedHeightPerPage();
+ this._spaceBetweenPagesTotal = this._spaceBetweenPages * this._nPages;
+ this._childrenPerPage = nColumns * this._rowsPerPage;
+ },
+
+ usedHeightPerPage: function() {
+ return this._rowsPerPage * this._spacePerRow - this.getSpacing();
+ },
+
+ nPages: function() {
+ return this._nPages;
+ },
+
+ getPagePosition: function(pageNumber) {
+ if(!this._nPages)
+ return [0,0];
+ if(pageNumber < 0 || pageNumber > this._nPages) {
+ throw new Error('Invalid page number ' + pageNumber);
+ }
+ let childBox = this._getVisibleChildren()[pageNumber *
+ this._childrenPerPage].get_allocation_box();
+
+ return [childBox.x1, childBox.y1];
+ }
+});
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]