iconGrid: Introduce IconGridLayout

IconGridLayout is a new layout manager that aims to replace the
current paginated layout algorithm implemented by the icon grid.

There are a few outstanding aspects of this new layout manager
that are worth highlighting. IconGridLayout implements all the
mechanisms necessary for a paginated icon grid, but doesn't
implement any policies around it. The reason behind this decision
is that this layout manager will be used by other places (e.g.
the login dialog) that demand different policies to how the
grid should look like.

Another important aspect of this grid is that it does not queue
any relayouts when changing its properties. If a relayout is
required, the actor should manually queue it. This is necessary
to avoid layout loops.

Add the IconGridLayout class. Next commits will do the surgery
to IconGrid and any related code to use this new layout manager.

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
This commit is contained in:
Georges Basile Stavracas Neto 2020-05-19 10:07:24 -03:00
parent 8e05fa2728
commit 3555550d5e

View File

@ -23,6 +23,14 @@ var AnimationDirection = {
OUT: 1,
};
var IconSize = {
HUGE: 128,
LARGE: 96,
MEDIUM: 64,
SMALL: 32,
TINY: 16,
};
var APPICON_ANIMATION_OUT_SCALE = 3;
var APPICON_ANIMATION_OUT_TIME = 250;
@ -210,6 +218,911 @@ function animateIconPosition(icon, box, nChangedIcons) {
return true;
}
function swap(value, length) {
return length - value - 1;
}
var IconGridLayout = GObject.registerClass({
Properties: {
'allow-incomplete-pages': GObject.ParamSpec.boolean('allow-incomplete-pages',
'Allow incomplete pages', 'Allow incomplete pages',
GObject.ParamFlags.READWRITE,
true),
'column-spacing': GObject.ParamSpec.int('column-spacing',
'Column spacing', 'Column spacing',
GObject.ParamFlags.READWRITE,
0, GLib.MAXINT32, 0),
'columns-per-page': GObject.ParamSpec.int('columns-per-page',
'Columns per page', 'Columns per page',
GObject.ParamFlags.READWRITE,
1, GLib.MAXINT32, 1),
'fixed-icon-size': GObject.ParamSpec.int('fixed-icon-size',
'Fixed icon size', 'Fixed icon size',
GObject.ParamFlags.READWRITE,
-1, GLib.MAXINT32, -1),
'icon-size': GObject.ParamSpec.int('icon-size',
'Icon size', 'Icon size',
GObject.ParamFlags.READABLE,
0, GLib.MAXINT32, 0),
'last-row-align': GObject.ParamSpec.enum('last-row-align',
'Last row align', 'Last row align',
GObject.ParamFlags.READWRITE,
Clutter.ActorAlign.$gtype,
Clutter.ActorAlign.FILL),
'max-column-spacing': GObject.ParamSpec.int('max-column-spacing',
'Maximum column spacing', 'Maximum column spacing',
GObject.ParamFlags.READWRITE,
-1, GLib.MAXINT32, -1),
'max-row-spacing': GObject.ParamSpec.int('max-row-spacing',
'Maximum row spacing', 'Maximum row spacing',
GObject.ParamFlags.READWRITE,
-1, GLib.MAXINT32, -1),
'orientation': GObject.ParamSpec.enum('orientation',
'Orientation', 'Orientation',
GObject.ParamFlags.READWRITE,
Clutter.Orientation.$gtype,
Clutter.Orientation.VERTICAL),
'page-halign': GObject.ParamSpec.enum('page-halign',
'Horizontal page align',
'Horizontal page align',
GObject.ParamFlags.READWRITE,
Clutter.ActorAlign.$gtype,
Clutter.ActorAlign.FILL),
'page-valign': GObject.ParamSpec.enum('page-valign',
'Vertical page align',
'Vertical page align',
GObject.ParamFlags.READWRITE,
Clutter.ActorAlign.$gtype,
Clutter.ActorAlign.FILL),
'row-spacing': GObject.ParamSpec.int('row-spacing',
'Row spacing', 'Row spacing',
GObject.ParamFlags.READWRITE,
0, GLib.MAXINT32, 0),
'rows-per-page': GObject.ParamSpec.int('rows-per-page',
'Rows per page', 'Rows per page',
GObject.ParamFlags.READWRITE,
1, GLib.MAXINT32, 1),
},
Signals: {
'pages-changed': {},
},
}, class IconGridLayout extends Clutter.LayoutManager {
_init(params = {}) {
params = Params.parse(params, {
allow_incomplete_pages: true,
column_spacing: 0,
columns_per_page: 6,
fixed_icon_size: -1,
last_row_align: Clutter.ActorAlign.FILL,
max_column_spacing: -1,
max_row_spacing: -1,
orientation: Clutter.Orientation.VERTICAL,
page_halign: Clutter.ActorAlign.FILL,
page_valign: Clutter.ActorAlign.FILL,
row_spacing: 0,
rows_per_page: 4,
});
this._allowIncompletePages = params.allow_incomplete_pages;
this._columnSpacing = params.column_spacing;
this._columnsPerPage = params.columns_per_page;
this._fixedIconSize = params.fixed_icon_size;
this._lastRowAlign = params.last_row_align;
this._maxColumnSpacing = params.max_column_spacing;
this._maxRowSpacing = params.max_row_spacing;
this._orientation = params.orientation;
this._pageHAlign = params.page_halign;
this._pageVAlign = params.page_valign;
this._rowSpacing = params.row_spacing;
this._rowsPerPage = params.rows_per_page;
super._init(params);
this._iconSize = this._fixedIconSize !== -1
? this._fixedIconSize
: IconSize.LARGE;
this._pageSizeChanged = false;
this._pageHeight = 0;
this._pageWidth = 0;
this._nPages = -1;
// [
// {
// children: [ itemData, itemData, itemData, ... ],
// },
// {
// children: [ itemData, itemData, itemData, ... ],
// },
// {
// children: [ itemData, itemData, itemData, ... ],
// },
// ]
this._pages = [];
// {
// item: {
// actor: Clutter.Actor,
// pageIndex: <index>,
// },
// item: {
// actor: Clutter.Actor,
// pageIndex: <index>,
// },
// }
this._items = new Map();
this._containerDestroyedId = 0;
this._updateIconSizesLaterId = 0;
}
_findBestIconSize() {
const nColumns = this._columnsPerPage;
const nRows = this._rowsPerPage;
const columnSpacingPerPage = this._columnSpacing * (nColumns - 1);
const rowSpacingPerPage = this._rowSpacing * (nRows - 1);
const [firstItem] = this._container;
if (this._fixedIconSize !== -1)
return this._fixedIconSize;
let bestSize;
const iconSizes = Object.values(IconSize).sort((a, b) => b - a);
for (const size of iconSizes) {
let usedWidth, usedHeight;
if (firstItem) {
firstItem.icon.setIconSize(size);
const [firstItemWidth, firstItemHeight] =
firstItem.get_preferred_size();
const itemSize = Math.max(firstItemWidth, firstItemHeight);
usedWidth = itemSize * nColumns;
usedHeight = itemSize * nRows;
} else {
usedWidth = size * nColumns;
usedHeight = size * nRows;
}
const emptyHSpace =
this._pageWidth - usedWidth - columnSpacingPerPage;
const emptyVSpace =
this._pageHeight - usedHeight - rowSpacingPerPage;
if (emptyHSpace >= 0 && emptyVSpace > 0) {
bestSize = size;
break;
}
}
return bestSize;
}
_getChildrenMaxSize() {
let minWidth = 0;
let minHeight = 0;
for (const child of this._container) {
if (!child.visible)
continue;
const [childMinHeight] = child.get_preferred_height(-1);
const [childMinWidth] = child.get_preferred_width(-1);
minWidth = Math.max(minWidth, childMinWidth);
minHeight = Math.max(minHeight, childMinHeight);
}
return Math.max(minWidth, minHeight);
}
_getVisibleChildrenForPage(pageIndex) {
return this._pages[pageIndex].children.filter(actor => actor.visible);
}
_updatePages() {
for (let i = 0; i < this._pages.length; i++)
this._relocateSurplusItems(i);
}
_unlinkItem(item) {
const itemData = this._items.get(item);
item.disconnect(itemData.destroyId);
item.disconnect(itemData.visibleId);
this._items.delete(item);
}
_removePage(pageIndex) {
// Make sure to not leave any icon left here
this._pages[pageIndex].children.forEach(item => {
this._unlinkItem(item);
});
this._pages.splice(pageIndex, 1);
this.emit('pages-changed');
}
_fillItemVacancies(pageIndex) {
if (pageIndex >= this._pages.length - 1)
return;
const visiblePageItems = this._getVisibleChildrenForPage(pageIndex);
const itemsPerPage = this._columnsPerPage * this._rowsPerPage;
// No reduce needed
if (visiblePageItems.length === itemsPerPage)
return;
const visibleNextPageItems = this._getVisibleChildrenForPage(pageIndex + 1);
const nMissingItems = Math.min(itemsPerPage - visiblePageItems.length, visibleNextPageItems.length);
// Append to the current page the first items of the next page
for (let i = 0; i < nMissingItems; i++) {
const reducedItem = visibleNextPageItems[i];
this._removeItemData(reducedItem);
this._addItemToPage(reducedItem, pageIndex, -1);
}
}
_removeItemData(item) {
const itemData = this._items.get(item);
const pageIndex = itemData.pageIndex;
const page = this._pages[pageIndex];
const itemIndex = page.children.indexOf(item);
this._unlinkItem(item);
page.children.splice(itemIndex, 1);
// Delete the page if this is the last icon in it
const visibleItems = this._getVisibleChildrenForPage(pageIndex);
if (visibleItems.length === 0)
this._removePage(pageIndex);
if (!this._allowIncompletePages)
this._fillItemVacancies(pageIndex);
}
_relocateSurplusItems(pageIndex) {
const visiblePageItems = this._getVisibleChildrenForPage(pageIndex);
const itemsPerPage = this._columnsPerPage * this._rowsPerPage;
// No overflow needed
if (visiblePageItems.length <= itemsPerPage)
return;
const nExtraItems = visiblePageItems.length - itemsPerPage;
for (let i = 0; i < nExtraItems; i++) {
const overflowIndex = visiblePageItems.length - i - 1;
const overflowItem = visiblePageItems[overflowIndex];
this._removeItemData(overflowItem);
this._addItemToPage(overflowItem, pageIndex + 1, 0);
}
}
_appendPage() {
this._pages.push({ children: [] });
this.emit('pages-changed');
}
_addItemToPage(item, pageIndex, index) {
// Ensure we have at least one page
if (this._pages.length === 0)
this._appendPage();
// Append a new page if necessary
if (pageIndex === this._pages.length)
this._appendPage();
if (pageIndex === -1)
pageIndex = this._pages.length - 1;
if (index === -1)
index = this._pages[pageIndex].children.length;
this._items.set(item, {
actor: item,
pageIndex,
destroyId: item.connect('destroy', () => this._removeItemData(item)),
visibleId: item.connect('notify::visible', () => {
const itemData = this._items.get(item);
if (item.visible)
this._relocateSurplusItems(itemData.pageIndex);
else if (!this._allowIncompletePages)
this._fillItemVacancies(itemData.pageIndex);
}),
});
item.icon.setIconSize(this._iconSize);
this._pages[pageIndex].children.splice(index, 0, item);
this._relocateSurplusItems(pageIndex);
}
_calculateSpacing(childSize) {
const nColumns = this._columnsPerPage;
const nRows = this._rowsPerPage;
const usedWidth = childSize * nColumns;
const usedHeight = childSize * nRows;
const columnSpacingPerPage = this._columnSpacing * (nColumns - 1);
const rowSpacingPerPage = this._rowSpacing * (nRows - 1);
let emptyHSpace = this._pageWidth - usedWidth - columnSpacingPerPage;
let emptyVSpace = this._pageHeight - usedHeight - rowSpacingPerPage;
let leftEmptySpace;
let topEmptySpace;
let hSpacing;
let vSpacing;
switch (this._pageHAlign) {
case Clutter.ActorAlign.START:
leftEmptySpace = 0;
hSpacing = this._columnSpacing;
break;
case Clutter.ActorAlign.CENTER:
leftEmptySpace = Math.floor(emptyHSpace / 2);
hSpacing = this._columnSpacing;
break;
case Clutter.ActorAlign.END:
leftEmptySpace = emptyHSpace;
hSpacing = this._columnSpacing;
break;
case Clutter.ActorAlign.FILL:
leftEmptySpace = 0;
hSpacing = this._columnSpacing + emptyHSpace / (nColumns - 1);
// Maybe constraint horizontal spacing
if (this._maxColumnSpacing !== -1 && hSpacing > this._maxColumnSpacing) {
const extraHSpacing =
(this._maxColumnSpacing - this._columnSpacing) * (nColumns - 1);
hSpacing = this._maxColumnSpacing;
leftEmptySpace =
Math.max((emptyHSpace - extraHSpacing) / 2, 0);
}
break;
}
switch (this._pageVAlign) {
case Clutter.ActorAlign.START:
topEmptySpace = 0;
vSpacing = this._rowSpacing;
break;
case Clutter.ActorAlign.CENTER:
topEmptySpace = Math.floor(emptyVSpace / 2);
vSpacing = this._rowSpacing;
break;
case Clutter.ActorAlign.END:
topEmptySpace = emptyVSpace;
vSpacing = this._rowSpacing;
break;
case Clutter.ActorAlign.FILL:
topEmptySpace = 0;
vSpacing = this._rowSpacing + emptyVSpace / (nRows - 1);
// Maybe constraint vertical spacing
if (this._maxRowSpacing !== -1 && vSpacing > this._maxRowSpacing) {
const extraVSpacing =
(this._maxRowSpacing - this._rowSpacing) * (nRows - 1);
vSpacing = this._maxRowSpacing;
topEmptySpace =
Math.max((emptyVSpace - extraVSpacing) / 2, 0);
}
break;
}
return [leftEmptySpace, topEmptySpace, hSpacing, vSpacing];
}
_getRowPadding(items, itemIndex, childSize, spacing) {
const nRows = Math.ceil(items.length / this._columnsPerPage);
let rowAlign = 0;
const row = Math.floor(itemIndex / this._columnsPerPage);
// Only apply to the last row
if (row < nRows - 1)
return 0;
const rowStart = row * this._columnsPerPage;
const rowEnd = Math.min((row + 1) * this._columnsPerPage - 1, items.length - 1);
const itemsInThisRow = rowEnd - rowStart + 1;
const nEmpty = this._columnsPerPage - itemsInThisRow;
const availableWidth = nEmpty * (spacing + childSize);
const isRtl =
Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
switch (this._lastRowAlign) {
case Clutter.ActorAlign.START:
rowAlign = 0;
break;
case Clutter.ActorAlign.CENTER:
rowAlign = availableWidth / 2;
break;
case Clutter.ActorAlign.END:
rowAlign = availableWidth;
break;
case Clutter.ActorAlign.FILL:
rowAlign = 0;
break;
}
return isRtl ? rowAlign * -1 : rowAlign;
}
_onDestroy() {
if (this._updateIconSizesLaterId >= 0) {
Meta.later_remove(this._updateIconSizesLaterId);
this._updateIconSizesLaterId = 0;
}
}
vfunc_set_container(container) {
if (this._container)
this._container.disconnect(this._containerDestroyedId);
this._container = container;
if (this._container)
this._containerDestroyedId = this._container.connect('destroy', this._onDestroy.bind(this));
}
vfunc_get_preferred_width(_container, _forHeight) {
let minWidth = -1;
let natWidth = -1;
switch (this._orientation) {
case Clutter.Orientation.VERTICAL:
minWidth = IconSize.TINY;
natWidth = this._pageWidth;
break;
case Clutter.Orientation.HORIZONTAL:
minWidth = this._pageWidth * this._pages.length;
natWidth = minWidth;
break;
}
return [minWidth, natWidth];
}
vfunc_get_preferred_height(_container, _forWidth) {
let minHeight = -1;
let natHeight = -1;
switch (this._orientation) {
case Clutter.Orientation.VERTICAL:
minHeight = this._pageHeight * this._pages.length;
natHeight = minHeight;
break;
case Clutter.Orientation.HORIZONTAL:
minHeight = IconSize.TINY;
natHeight = this._pageHeight;
break;
}
return [minHeight, natHeight];
}
vfunc_allocate() {
if (this._pageWidth === 0 || this._pageHeight === 0)
throw new Error('IconGridLayout.adaptToSize wasn\'t called before allocation');
this._updatePages();
const isRtl =
Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
const childSize = this._getChildrenMaxSize();
const [leftEmptySpace, topEmptySpace, hSpacing, vSpacing] =
this._calculateSpacing(childSize);
const childBox = new Clutter.ActorBox();
childBox.set_size(childSize, childSize);
let nChangedIcons = 0;
this._pages.forEach((page, pageIndex) => {
const visibleItems =
page.children.filter(actor => actor.visible);
if (isRtl && this._orientation === Clutter.Orientation.HORIZONTAL)
pageIndex = swap(pageIndex, this._pages.length);
visibleItems.forEach((item, itemIndex) => {
const row = Math.floor(itemIndex / this._columnsPerPage);
let column = itemIndex % this._columnsPerPage;
if (isRtl)
column = swap(column, this._columnsPerPage);
const rowPadding = this._getRowPadding(visibleItems, itemIndex,
childSize, hSpacing);
// Icon position
let x = leftEmptySpace + rowPadding + column * (childSize + hSpacing);
let y = topEmptySpace + row * (childSize + vSpacing);
// Page start
switch (this._orientation) {
case Clutter.Orientation.HORIZONTAL:
x += pageIndex * this._pageWidth;
break;
case Clutter.Orientation.VERTICAL:
y += pageIndex * this._pageHeight;
break;
}
childBox.set_origin(x, y);
// Only ease icons when the page size didn't change
if (this._pageSizeChanged)
item.allocate(childBox);
else if (animateIconPosition(item, childBox, nChangedIcons))
nChangedIcons++;
});
});
this._pageSizeChanged = false;
}
/**
* addItem:
* @param {Clutter.Actor} item: item to append to the grid
* @param {int} page: page number
* @param {int} index: position in the page
*
* 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, page = -1, index = -1) {
if (this._items.has(item))
throw new Error(`Item ${item} already added to IconGridLayout`);
if (page > this._pages.length)
throw new Error(`Cannot add ${item} to page ${page}`);
if (!this._container)
return;
this._container.add_child(item);
this._addItemToPage(item, page, index);
}
/**
* 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.addItem(item);
}
/**
* 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._items.has(item))
throw new Error(`Item ${item} is not part of the IconGridLayout`);
if (!this._container)
return;
this._container.remove_child(item);
this._removeItemData(item);
}
/**
* getItemsAtPage:
* @param {int} pageIndex: page index
*
* Retrieves the children at page @pageIndex. Children may be invisible.
*
* @returns {Array} an array of {Clutter.Actor}s
*/
getItemsAtPage(pageIndex) {
if (pageIndex >= this._pages.length)
throw new Error(`IconGridLayout does not have page ${pageIndex}`);
return [...this._pages[pageIndex].children];
}
/**
* getItemPosition:
* @param {BaseIcon} item: the item
*
* Retrieves the position of @item is its page, or -1 if @item is not
* part of the grid.
*
* @returns {[int, int]} the page and position of @item
*/
getItemPosition(item) {
if (!this._items.has(item))
return [-1, -1];
const itemData = this._items.get(item);
const visibleItems = this._getVisibleChildrenForPage(itemData.pageIndex);
return [itemData.pageIndex, visibleItems.indexOf(item)];
}
/**
* getItemAt:
* @param {int} page: the page
* @param {int} position: the position in page
*
* Retrieves the item at @page and @position.
*
* @returns {BaseItem} the item at @page and @position, or null
*/
getItemAt(page, position) {
if (page < 0 || page >= this._pages.length)
return null;
const visibleItems = this._getVisibleChildrenForPage(page);
if (position < 0 || position >= visibleItems.length)
return null;
return visibleItems[position];
}
/**
* 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;
}
adaptToSize(pageWidth, pageHeight) {
if (this._pageWidth === pageWidth && this._pageHeight === pageHeight)
return;
this._pageWidth = pageWidth;
this._pageHeight = pageHeight;
this._pageSizeChanged = true;
if (this._updateIconSizesLaterId === 0) {
this._updateIconSizesLaterId =
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
const iconSize = this._findBestIconSize();
if (this._iconSize !== iconSize) {
this._iconSize = iconSize;
for (const child of this._container)
child.icon.setIconSize(iconSize);
this.notify('icon-size');
}
this._updateIconSizesLaterId = 0;
return GLib.SOURCE_REMOVE;
});
}
}
// eslint-disable-next-line camelcase
get allow_incomplete_pages() {
return this._allowIncompletePages;
}
// eslint-disable-next-line camelcase
set allow_incomplete_pages(v) {
if (this._allowIncompletePages === v)
return;
this._allowIncompletePages = v;
this.notify('allow-incomplete-pages');
}
// eslint-disable-next-line camelcase
get column_spacing() {
return this._columnSpacing;
}
// eslint-disable-next-line camelcase
set column_spacing(v) {
if (this._columnSpacing === v)
return;
this._columnSpacing = v;
this.notify('column-spacing');
}
// eslint-disable-next-line camelcase
get columns_per_page() {
return this._columnsPerPage;
}
// eslint-disable-next-line camelcase
set columns_per_page(v) {
if (this._columnsPerPage === v)
return;
this._columnsPerPage = v;
this.notify('columns-per-page');
}
// eslint-disable-next-line camelcase
get fixed_icon_size() {
return this._fixedIconSize;
}
// eslint-disable-next-line camelcase
set fixed_icon_size(v) {
if (this._fixedIconSize === v)
return;
this._fixedIconSize = v;
this.notify('fixed-icon-size');
}
// eslint-disable-next-line camelcase
get icon_size() {
return this._iconSize;
}
// eslint-disable-next-line camelcase
get last_row_align() {
return this._lastRowAlign;
}
// eslint-disable-next-line camelcase
get max_column_spacing() {
return this._maxColumnSpacing;
}
// eslint-disable-next-line camelcase
set max_column_spacing(v) {
if (this._maxColumnSpacing === v)
return;
this._maxColumnSpacing = v;
this.notify('max-column-spacing');
}
// eslint-disable-next-line camelcase
get max_row_spacing() {
return this._maxRowSpacing;
}
// eslint-disable-next-line camelcase
set max_row_spacing(v) {
if (this._maxRowSpacing === v)
return;
this._maxRowSpacing = v;
this.notify('max-row-spacing');
}
// eslint-disable-next-line camelcase
set last_row_align(v) {
if (this._lastRowAlign === v)
return;
this._lastRowAlign = v;
this.notify('last-row-align');
}
get nPages() {
return this._pages.length;
}
// eslint-disable-next-line camelcase
get page_halign() {
return this._pageHAlign;
}
// eslint-disable-next-line camelcase
set page_halign(v) {
if (this._pageHAlign === v)
return;
this._pageHAlign = v;
this.notify('page-halign');
}
// eslint-disable-next-line camelcase
get page_valign() {
return this._pageVAlign;
}
// eslint-disable-next-line camelcase
set page_valign(v) {
if (this._pageVAlign === v)
return;
this._pageVAlign = v;
this.notify('page-valign');
}
// eslint-disable-next-line camelcase
get row_spacing() {
return this._rowSpacing;
}
// eslint-disable-next-line camelcase
set row_spacing(v) {
if (this._rowSpacing === v)
return;
this._rowSpacing = v;
this.notify('row-spacing');
}
// eslint-disable-next-line camelcase
get rows_per_page() {
return this._rowsPerPage;
}
// eslint-disable-next-line camelcase
set rows_per_page(v) {
if (this._rowsPerPage === v)
return;
this._rowsPerPage = v;
this.notify('rows-per-page');
}
get orientation() {
return this._orientation;
}
set orientation(v) {
if (this._orientation === v)
return;
switch (v) {
case Clutter.Orientation.VERTICAL:
this.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
break;
case Clutter.Orientation.HORIZONTAL:
this.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT;
break;
}
this._orientation = v;
this.notify('orientation');
}
get pageHeight() {
return this._pageHeight;
}
get pageWidth() {
return this._pageWidth;
}
});
var IconGrid = GObject.registerClass({
Signals: { 'animation-done': {},
'child-focused': { param_types: [Clutter.Actor.$gtype] } },