2011-09-28 09:16:26 -04:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
/* exported BaseIcon, IconGrid, PaginatedIconGrid */
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2019-02-20 08:01:07 -05:00
|
|
|
const { Clutter, GLib, GObject, Graphene, Meta, St } = imports.gi;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
|
|
|
const Params = imports.misc.params;
|
2014-06-17 06:47:00 -04:00
|
|
|
const Main = imports.ui.main;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var ICON_SIZE = 96;
|
|
|
|
var MIN_ICON_SIZE = 16;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2019-08-01 19:13:10 -04:00
|
|
|
var ANIMATION_TIME_IN = 350;
|
2019-01-28 20:27:05 -05:00
|
|
|
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;
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var ANIMATION_BOUNCE_ICON_SCALE = 1.1;
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var AnimationDirection = {
|
2014-06-17 13:10:54 -04:00
|
|
|
IN: 0,
|
2019-08-20 17:43:54 -04:00
|
|
|
OUT: 1,
|
2014-06-17 06:47:00 -04:00
|
|
|
};
|
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var APPICON_ANIMATION_OUT_SCALE = 3;
|
2019-08-01 19:13:10 -04:00
|
|
|
var APPICON_ANIMATION_OUT_TIME = 250;
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2019-12-04 14:34:34 -05:00
|
|
|
const ICON_POSITION_DELAY = 25;
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
var BaseIcon = GObject.registerClass(
|
|
|
|
class BaseIcon extends St.Bin {
|
2017-10-30 20:03:21 -04:00
|
|
|
_init(label, params) {
|
2010-08-30 08:59:45 -04:00
|
|
|
params = Params.parse(params, { createIcon: null,
|
2011-01-20 09:25:25 -05:00
|
|
|
setSizeManually: false,
|
|
|
|
showLabel: true });
|
2013-08-31 09:05:53 -04:00
|
|
|
|
|
|
|
let styleClass = 'overview-icon';
|
|
|
|
if (params.showLabel)
|
|
|
|
styleClass += ' overview-icon-with-label';
|
|
|
|
|
2019-10-17 17:40:24 -04:00
|
|
|
super._init({ style_class: styleClass });
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2018-06-30 14:00:08 -04:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
|
2019-10-17 17:40:24 -04:00
|
|
|
this._box = new St.BoxLayout({
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
});
|
2018-06-30 14:00:08 -04:00
|
|
|
this.set_child(this._box);
|
2010-07-20 22:22:19 -04:00
|
|
|
|
|
|
|
this.iconSize = ICON_SIZE;
|
2019-12-20 16:47:01 -05:00
|
|
|
this._iconBin = new St.Bin({ x_align: Clutter.ActorAlign.CENTER });
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2018-06-30 14:00:08 -04:00
|
|
|
this._box.add_actor(this._iconBin);
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2011-01-20 09:25:25 -05:00
|
|
|
if (params.showLabel) {
|
2011-03-08 13:33:57 -05:00
|
|
|
this.label = new St.Label({ text: label });
|
2019-07-03 12:27:21 -04:00
|
|
|
this.label.clutter_text.set({
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
2019-08-20 17:43:54 -04:00
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2019-07-03 12:27:21 -04:00
|
|
|
});
|
2018-06-30 14:00:08 -04:00
|
|
|
this._box.add_actor(this.label);
|
2011-01-20 09:25:25 -05:00
|
|
|
} else {
|
2011-03-08 13:33:57 -05:00
|
|
|
this.label = null;
|
2011-01-20 09:25:25 -05:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2010-08-30 08:59:45 -04:00
|
|
|
if (params.createIcon)
|
|
|
|
this.createIcon = params.createIcon;
|
|
|
|
this._setSizeManually = params.setSizeManually;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2011-03-02 18:25:23 -05:00
|
|
|
this.icon = null;
|
2012-11-30 20:35:04 -05:00
|
|
|
|
|
|
|
let cache = St.TextureCache.get_default();
|
2017-12-01 19:27:35 -05:00
|
|
|
this._iconThemeChangedId = cache.connect('icon-theme-changed', this._onIconThemeChanged.bind(this));
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2019-01-31 09:08:10 -05:00
|
|
|
vfunc_get_preferred_width(_forHeight) {
|
2018-06-30 14:00:08 -04:00
|
|
|
// Return the actual height to keep the squared aspect
|
|
|
|
return this.get_preferred_height(-1);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-08-30 08:59:36 -04:00
|
|
|
|
2010-07-20 22:22:19 -04:00
|
|
|
// This can be overridden by a subclass, or by the createIcon
|
|
|
|
// parameter to _init()
|
2019-01-31 09:08:10 -05:00
|
|
|
createIcon(_size) {
|
2019-05-21 16:28:07 -04:00
|
|
|
throw new GObject.NotImplementedError(`createIcon in ${this.constructor.name}`);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setIconSize(size) {
|
2010-08-30 08:59:45 -04:00
|
|
|
if (!this._setSizeManually)
|
|
|
|
throw new Error('setSizeManually has to be set to use setIconsize');
|
|
|
|
|
|
|
|
if (size == this.iconSize)
|
|
|
|
return;
|
|
|
|
|
2011-03-02 18:25:23 -05:00
|
|
|
this._createIconTexture(size);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2011-03-02 18:25:23 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_createIconTexture(size) {
|
2011-03-02 18:25:23 -05:00
|
|
|
if (this.icon)
|
|
|
|
this.icon.destroy();
|
2010-08-30 08:59:45 -04:00
|
|
|
this.iconSize = size;
|
|
|
|
this.icon = this.createIcon(this.iconSize);
|
2011-02-10 10:30:28 -05:00
|
|
|
|
2011-12-20 13:11:07 -05:00
|
|
|
this._iconBin.child = this.icon;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-08-30 08:59:45 -04:00
|
|
|
|
2018-06-30 14:00:08 -04:00
|
|
|
vfunc_style_changed() {
|
2019-03-27 12:50:20 -04:00
|
|
|
super.vfunc_style_changed();
|
2018-06-30 14:00:08 -04:00
|
|
|
let node = this.get_theme_node();
|
2010-08-30 08:59:36 -04:00
|
|
|
|
2011-03-02 18:25:23 -05:00
|
|
|
let size;
|
|
|
|
if (this._setSizeManually) {
|
|
|
|
size = this.iconSize;
|
|
|
|
} else {
|
2020-04-03 14:28:57 -04:00
|
|
|
const { scaleFactor } =
|
|
|
|
St.ThemeContext.get_for_stage(global.stage);
|
|
|
|
|
2011-03-02 18:25:23 -05:00
|
|
|
let [found, len] = node.lookup_length('icon-size', false);
|
2020-04-03 14:28:57 -04:00
|
|
|
size = found ? len / scaleFactor : ICON_SIZE;
|
2011-03-02 18:25:23 -05:00
|
|
|
}
|
2010-08-30 08:59:36 -04:00
|
|
|
|
2012-11-30 20:35:04 -05:00
|
|
|
if (this.iconSize == size && this._iconBin.child)
|
|
|
|
return;
|
|
|
|
|
2011-03-02 18:25:23 -05:00
|
|
|
this._createIconTexture(size);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2012-11-30 20:35:04 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onDestroy() {
|
2012-11-30 20:35:04 -05:00
|
|
|
if (this._iconThemeChangedId > 0) {
|
|
|
|
let cache = St.TextureCache.get_default();
|
|
|
|
cache.disconnect(this._iconThemeChangedId);
|
|
|
|
this._iconThemeChangedId = 0;
|
|
|
|
}
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2012-11-30 20:35:04 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onIconThemeChanged() {
|
2012-11-30 20:35:04 -05:00
|
|
|
this._createIconTexture(this.iconSize);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animateZoomOut() {
|
2014-06-17 15:31:53 -04:00
|
|
|
// Animate only the child instead of the entire actor, so the
|
|
|
|
// styles like hover and running are not applied while
|
|
|
|
// animating.
|
2018-06-30 14:00:08 -04:00
|
|
|
zoomOutActor(this.child);
|
2010-07-20 22:22:19 -04:00
|
|
|
}
|
2019-06-28 18:49:18 -04:00
|
|
|
|
2019-09-15 05:40:27 -04:00
|
|
|
animateZoomOutAtPos(x, y) {
|
|
|
|
zoomOutActorAtPos(this.child, x, y);
|
|
|
|
}
|
|
|
|
|
2019-06-28 18:49:18 -04:00
|
|
|
update() {
|
|
|
|
this._createIconTexture(this.iconSize);
|
|
|
|
}
|
2011-11-20 11:07:14 -05:00
|
|
|
});
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2014-06-17 15:31:53 -04:00
|
|
|
function clamp(value, min, max) {
|
|
|
|
return Math.max(Math.min(value, max), min);
|
2019-01-28 20:18:52 -05:00
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
|
|
|
function zoomOutActor(actor) {
|
2019-09-15 05:40:27 -04:00
|
|
|
let [x, y] = actor.get_transformed_position();
|
|
|
|
zoomOutActorAtPos(actor, x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
function zoomOutActorAtPos(actor, x, y) {
|
2014-06-17 15:31:53 -04:00
|
|
|
let actorClone = new Clutter.Clone({ source: actor,
|
|
|
|
reactive: false });
|
|
|
|
let [width, height] = actor.get_transformed_size();
|
2019-09-15 05:40:27 -04:00
|
|
|
|
2014-06-17 15:31:53 -04:00
|
|
|
actorClone.set_size(width, height);
|
|
|
|
actorClone.set_position(x, y);
|
|
|
|
actorClone.opacity = 255;
|
|
|
|
actorClone.set_pivot_point(0.5, 0.5);
|
|
|
|
|
|
|
|
Main.uiGroup.add_actor(actorClone);
|
|
|
|
|
|
|
|
// Avoid monitor edges to not zoom outside the current monitor
|
|
|
|
let monitor = Main.layoutManager.findMonitorForActor(actor);
|
|
|
|
let scaledWidth = width * APPICON_ANIMATION_OUT_SCALE;
|
|
|
|
let scaledHeight = height * APPICON_ANIMATION_OUT_SCALE;
|
|
|
|
let scaledX = x - (scaledWidth - width) / 2;
|
|
|
|
let scaledY = y - (scaledHeight - height) / 2;
|
|
|
|
let containedX = clamp(scaledX, monitor.x, monitor.x + monitor.width - scaledWidth);
|
|
|
|
let containedY = clamp(scaledY, monitor.y, monitor.y + monitor.height - scaledHeight);
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
actorClone.ease({
|
|
|
|
scale_x: APPICON_ANIMATION_OUT_SCALE,
|
|
|
|
scale_y: APPICON_ANIMATION_OUT_SCALE,
|
|
|
|
translation_x: containedX - scaledX,
|
|
|
|
translation_y: containedY - scaledY,
|
|
|
|
opacity: 0,
|
|
|
|
duration: APPICON_ANIMATION_OUT_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
onComplete: () => actorClone.destroy(),
|
2018-07-20 15:46:19 -04:00
|
|
|
});
|
2014-06-17 15:31:53 -04:00
|
|
|
}
|
|
|
|
|
2020-05-09 15:30:26 -04:00
|
|
|
function animateIconPosition(icon, box, nChangedIcons) {
|
2020-02-07 04:23:11 -05:00
|
|
|
if (!icon.has_allocation() || icon.allocation.equal(box) || icon.opacity === 0) {
|
2020-05-09 15:30:26 -04:00
|
|
|
icon.allocate(box);
|
2019-12-04 14:34:34 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
icon.save_easing_state();
|
|
|
|
icon.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
|
|
|
|
icon.set_easing_delay(nChangedIcons * ICON_POSITION_DELAY);
|
|
|
|
|
2020-05-09 15:30:26 -04:00
|
|
|
icon.allocate(box);
|
2019-12-04 14:34:34 -05:00
|
|
|
|
|
|
|
icon.restore_easing_state();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
var IconGrid = GObject.registerClass({
|
2019-01-28 20:27:05 -05:00
|
|
|
Signals: { 'animation-done': {},
|
|
|
|
'child-focused': { param_types: [Clutter.Actor.$gtype] } },
|
2017-10-30 21:23:39 -04:00
|
|
|
}, class IconGrid extends St.Widget {
|
2017-10-30 20:03:21 -04:00
|
|
|
_init(params) {
|
2017-10-30 21:23:39 -04:00
|
|
|
super._init({ style_class: 'icon-grid',
|
2018-07-02 10:03:07 -04:00
|
|
|
y_align: Clutter.ActorAlign.START });
|
|
|
|
|
2010-10-14 08:27:22 -04:00
|
|
|
params = Params.parse(params, { rowLimit: null,
|
|
|
|
columnLimit: null,
|
2013-08-27 15:23:13 -04:00
|
|
|
minRows: 1,
|
|
|
|
minColumns: 1,
|
2013-08-23 05:14:21 -04:00
|
|
|
xAlign: St.Align.MIDDLE,
|
|
|
|
padWithSpacing: false });
|
2010-07-20 22:22:19 -04:00
|
|
|
this._rowLimit = params.rowLimit;
|
|
|
|
this._colLimit = params.columnLimit;
|
2013-08-27 15:23:13 -04:00
|
|
|
this._minRows = params.minRows;
|
|
|
|
this._minColumns = params.minColumns;
|
2010-10-14 08:27:22 -04:00
|
|
|
this._xAlign = params.xAlign;
|
2013-08-23 05:14:21 -04:00
|
|
|
this._padWithSpacing = params.padWithSpacing;
|
|
|
|
|
|
|
|
this.topPadding = 0;
|
|
|
|
this.bottomPadding = 0;
|
|
|
|
this.rightPadding = 0;
|
|
|
|
this.leftPadding = 0;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
this._nPages = 0;
|
|
|
|
this.currentPage = 0;
|
|
|
|
this._rowsPerPage = 0;
|
|
|
|
this._spaceBetweenPages = 0;
|
|
|
|
this._childrenPerPage = 0;
|
|
|
|
|
2019-07-22 11:06:30 -04:00
|
|
|
this._updateIconSizesLaterId = 0;
|
|
|
|
|
2013-08-29 17:25:13 -04:00
|
|
|
this._items = [];
|
2014-09-05 13:33:37 -04:00
|
|
|
this._clonesAnimating = [];
|
2010-07-20 22:22:19 -04:00
|
|
|
// Pulled from CSS, but hardcode some defaults here
|
|
|
|
this._spacing = 0;
|
2012-02-14 10:30:40 -05:00
|
|
|
this._hItemSize = this._vItemSize = ICON_SIZE;
|
2013-08-15 04:38:17 -04:00
|
|
|
this._fixedHItemSize = this._fixedVItemSize = undefined;
|
2018-07-02 10:03:07 -04:00
|
|
|
this.connect('style-changed', this._onStyleChanged.bind(this));
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2018-07-02 10:03:07 -04:00
|
|
|
this.connect('actor-added', this._childAdded.bind(this));
|
|
|
|
this.connect('actor-removed', this._childRemoved.bind(this));
|
2019-07-22 11:06:30 -04:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
}
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_unmap() {
|
|
|
|
// Cancel animations when hiding the overview, to avoid icons
|
|
|
|
// swarming into the void ...
|
|
|
|
this._resetAnimationActors();
|
|
|
|
super.vfunc_unmap();
|
|
|
|
}
|
|
|
|
|
2019-07-22 11:06:30 -04:00
|
|
|
_onDestroy() {
|
|
|
|
if (this._updateIconSizesLaterId) {
|
2019-08-19 13:55:49 -04:00
|
|
|
Meta.later_remove(this._updateIconSizesLaterId);
|
2019-07-22 11:06:30 -04:00
|
|
|
this._updateIconSizesLaterId = 0;
|
|
|
|
}
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-03-20 09:48:58 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_keyFocusIn(actor) {
|
2018-08-21 05:21:26 -04:00
|
|
|
this.emit('child-focused', actor);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-03-20 09:48:58 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_childAdded(grid, child) {
|
2017-12-01 19:27:35 -05:00
|
|
|
child._iconGridKeyFocusInId = child.connect('key-focus-in', this._keyFocusIn.bind(this));
|
2019-09-06 05:50:52 -04:00
|
|
|
|
|
|
|
child._paintVisible = child.opacity > 0;
|
|
|
|
child._opacityChangedId = child.connect('notify::opacity', () => {
|
|
|
|
let paintVisible = child._paintVisible;
|
|
|
|
child._paintVisible = child.opacity > 0;
|
|
|
|
if (paintVisible !== child._paintVisible)
|
|
|
|
this.queue_relayout();
|
|
|
|
});
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-03-20 09:48:58 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_childRemoved(grid, child) {
|
2014-03-20 09:48:58 -04:00
|
|
|
child.disconnect(child._iconGridKeyFocusInId);
|
2019-09-08 12:09:23 -04:00
|
|
|
delete child._iconGridKeyFocusInId;
|
2019-09-06 05:50:52 -04:00
|
|
|
|
|
|
|
child.disconnect(child._opacityChangedId);
|
|
|
|
delete child._opacityChangedId;
|
|
|
|
delete child._paintVisible;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2019-01-31 09:08:10 -05:00
|
|
|
vfunc_get_preferred_width(_forHeight) {
|
2018-07-02 10:03:07 -04:00
|
|
|
let nChildren = this.get_n_children();
|
2019-08-19 15:33:15 -04:00
|
|
|
let nColumns = this._colLimit
|
|
|
|
? Math.min(this._colLimit, nChildren)
|
|
|
|
: nChildren;
|
2013-08-21 13:31:09 -04:00
|
|
|
let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing();
|
2010-07-20 22:22:19 -04:00
|
|
|
// 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
|
2018-07-02 10:03:07 -04:00
|
|
|
let minSize = this._getHItemSize() + this.leftPadding + this.rightPadding;
|
|
|
|
let natSize = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding;
|
|
|
|
|
|
|
|
return this.get_theme_node().adjust_preferred_width(minSize, natSize);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_getVisibleChildren() {
|
2018-07-02 10:03:07 -04:00
|
|
|
return this.get_children().filter(actor => actor.visible);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-12-18 14:09:58 -05:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
_availableHeightPerPageForItems() {
|
|
|
|
return this.usedHeightForNRows(this._rowsPerPage) - (this.topPadding + this.bottomPadding);
|
|
|
|
}
|
2018-07-02 10:03:07 -04:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
vfunc_get_preferred_height() {
|
|
|
|
const height = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
|
|
|
|
return [height, height];
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2020-05-09 15:30:26 -04:00
|
|
|
vfunc_allocate(box) {
|
2020-05-18 22:01:41 -04:00
|
|
|
if (this._childrenPerPage === 0)
|
|
|
|
log('computePages() must be called before allocate(); pagination will not work.');
|
2018-07-02 10:03:07 -04:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
this.set_allocation(box);
|
2018-07-02 10:03:07 -04:00
|
|
|
|
2010-12-18 14:09:58 -05:00
|
|
|
let children = this._getVisibleChildren();
|
2010-07-20 22:22:19 -04:00
|
|
|
let availWidth = box.x2 - box.x1;
|
2013-08-21 13:31:09 -04:00
|
|
|
let spacing = this._getSpacing();
|
|
|
|
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2013-08-23 05:14:21 -04:00
|
|
|
let leftEmptySpace;
|
2019-01-28 20:27:05 -05:00
|
|
|
switch (this._xAlign) {
|
2019-02-01 07:21:00 -05:00
|
|
|
case St.Align.START:
|
|
|
|
leftEmptySpace = 0;
|
|
|
|
break;
|
|
|
|
case St.Align.MIDDLE:
|
|
|
|
leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
|
|
|
|
break;
|
|
|
|
case St.Align.END:
|
|
|
|
leftEmptySpace = availWidth - usedWidth;
|
2010-10-14 08:27:22 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2013-08-23 05:14:21 -04:00
|
|
|
let x = box.x1 + leftEmptySpace + this.leftPadding;
|
|
|
|
let y = box.y1 + this.topPadding;
|
2010-07-20 22:22:19 -04:00
|
|
|
let columnIndex = 0;
|
2020-05-18 22:01:41 -04:00
|
|
|
|
2019-12-04 14:34:34 -05:00
|
|
|
let nChangedIcons = 0;
|
2010-07-20 22:22:19 -04:00
|
|
|
for (let i = 0; i < children.length; i++) {
|
2013-08-21 13:16:58 -04:00
|
|
|
let childBox = this._calculateChildBox(children[i], x, y, box);
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
if (animateIconPosition(children[i], childBox, nChangedIcons))
|
|
|
|
nChangedIcons++;
|
2019-12-04 14:34:34 -05:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
children[i].show();
|
2010-07-20 22:22:19 -04:00
|
|
|
|
|
|
|
columnIndex++;
|
2020-05-18 22:01:41 -04:00
|
|
|
if (columnIndex === nColumns)
|
2010-07-20 22:22:19 -04:00
|
|
|
columnIndex = 0;
|
|
|
|
|
|
|
|
if (columnIndex == 0) {
|
2013-08-15 04:38:17 -04:00
|
|
|
y += this._getVItemSize() + spacing;
|
2020-05-18 22:01:41 -04:00
|
|
|
if ((i + 1) % this._childrenPerPage === 0)
|
|
|
|
y += this._spaceBetweenPages - spacing + this.bottomPadding + this.topPadding;
|
2013-08-23 05:14:21 -04:00
|
|
|
x = box.x1 + leftEmptySpace + this.leftPadding;
|
2010-07-20 22:22:19 -04:00
|
|
|
} else {
|
2013-08-15 04:38:17 -04:00
|
|
|
x += this._getHItemSize() + spacing;
|
2010-07-20 22:22:19 -04:00
|
|
|
}
|
|
|
|
}
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2018-07-02 10:03:07 -04:00
|
|
|
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);
|
|
|
|
|
2019-02-20 08:01:07 -05:00
|
|
|
let origin = new Graphene.Point3D();
|
2018-07-02 10:03:07 -04:00
|
|
|
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();
|
2019-01-29 14:36:54 -05:00
|
|
|
child != null;
|
|
|
|
child = child.get_next_sibling()) {
|
2018-07-02 10:03:07 -04:00
|
|
|
|
2019-06-06 17:21:13 -04:00
|
|
|
if (!child.visible || !child.opacity)
|
2018-07-02 10:03:07 -04:00
|
|
|
continue;
|
|
|
|
|
|
|
|
let childVolume = child.get_transformed_paint_volume(this);
|
|
|
|
if (!childVolume)
|
2019-01-28 20:18:52 -05:00
|
|
|
return false;
|
2018-07-02 10:03:07 -04:00
|
|
|
|
|
|
|
paintVolume.union(childVolume);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2018-07-02 10:03:07 -04:00
|
|
|
|
2019-10-17 13:49:18 -04:00
|
|
|
/*
|
2014-06-17 06:47:00 -04:00
|
|
|
* Intended to be override by subclasses if they need a different
|
|
|
|
* set of items to be animated.
|
|
|
|
*/
|
2017-10-30 20:03:21 -04:00
|
|
|
_getChildrenToAnimate() {
|
2020-05-18 22:01:41 -04:00
|
|
|
const children = this._getVisibleChildren().filter(child => child.opacity > 0);
|
|
|
|
const firstIndex = this._childrenPerPage * this.currentPage;
|
|
|
|
const lastIndex = firstIndex + this._childrenPerPage;
|
|
|
|
|
|
|
|
return children.slice(firstIndex, lastIndex);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2019-08-08 18:15:48 -04:00
|
|
|
_resetAnimationActors() {
|
2018-10-05 05:09:22 -04:00
|
|
|
this._clonesAnimating.forEach(clone => {
|
|
|
|
clone.source.reactive = true;
|
|
|
|
clone.source.opacity = 255;
|
|
|
|
clone.destroy();
|
|
|
|
});
|
2014-09-05 13:33:37 -04:00
|
|
|
this._clonesAnimating = [];
|
2019-08-08 18:15:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_animationDone() {
|
|
|
|
this._resetAnimationActors();
|
2014-06-17 06:47:00 -04:00
|
|
|
this.emit('animation-done');
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animatePulse(animationDirection) {
|
2019-08-19 20:51:42 -04:00
|
|
|
if (animationDirection != AnimationDirection.IN) {
|
2019-05-21 16:28:07 -04:00
|
|
|
throw new GObject.NotImplementedError("Pulse animation only implements " +
|
|
|
|
"'in' animation direction");
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2019-08-08 18:15:48 -04:00
|
|
|
this._resetAnimationActors();
|
2014-06-17 06:47:00 -04:00
|
|
|
|
|
|
|
let actors = this._getChildrenToAnimate();
|
|
|
|
if (actors.length == 0) {
|
|
|
|
this._animationDone();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-09-19 13:25:35 -04:00
|
|
|
// 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);
|
|
|
|
|
2014-06-17 06:47:00 -04:00
|
|
|
for (let index = 0; index < actors.length; index++) {
|
|
|
|
let actor = actors[index];
|
2014-09-18 08:29:35 -04:00
|
|
|
actor.set_scale(0, 0);
|
|
|
|
actor.set_pivot_point(0.5, 0.5);
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2014-09-19 13:25:35 -04:00
|
|
|
let delay = index / actors.length * maxDelay;
|
2014-06-17 06:47:00 -04:00
|
|
|
let bounceUpTime = ANIMATION_TIME_IN / 4;
|
|
|
|
let isLastItem = index == actors.length - 1;
|
2018-07-20 15:46:19 -04:00
|
|
|
actor.ease({
|
|
|
|
scale_x: ANIMATION_BOUNCE_ICON_SCALE,
|
|
|
|
scale_y: ANIMATION_BOUNCE_ICON_SCALE,
|
|
|
|
duration: bounceUpTime,
|
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2019-08-19 15:06:04 -04:00
|
|
|
delay,
|
2018-07-20 15:46:19 -04:00
|
|
|
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;
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2018-07-20 15:46:19 -04:00
|
|
|
});
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2018-07-20 15:46:19 -04:00
|
|
|
});
|
2014-06-17 06:47:00 -04:00
|
|
|
}
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animateSpring(animationDirection, sourceActor) {
|
2019-08-08 18:15:48 -04:00
|
|
|
this._resetAnimationActors();
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
|
|
let actors = this._getChildrenToAnimate();
|
|
|
|
if (actors.length == 0) {
|
|
|
|
this._animationDone();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let [sourceX, sourceY] = sourceActor.get_transformed_position();
|
|
|
|
let [sourceWidth, sourceHeight] = sourceActor.get_size();
|
|
|
|
// Get the center
|
|
|
|
let [sourceCenterX, sourceCenterY] = [sourceX + sourceWidth / 2, sourceY + sourceHeight / 2];
|
|
|
|
// Design decision, 1/2 of the source actor size.
|
|
|
|
let [sourceScaledWidth, sourceScaledHeight] = [sourceWidth / 2, sourceHeight / 2];
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
actors.forEach(actor => {
|
2014-06-17 13:10:54 -04:00
|
|
|
let [actorX, actorY] = actor._transformedPosition = actor.get_transformed_position();
|
|
|
|
let [x, y] = [actorX - sourceX, actorY - sourceY];
|
|
|
|
actor._distance = Math.sqrt(x * x + y * y);
|
|
|
|
});
|
2017-10-30 20:38:18 -04:00
|
|
|
let maxDist = actors.reduce((prev, cur) => {
|
2014-06-17 13:10:54 -04:00
|
|
|
return Math.max(prev, cur._distance);
|
|
|
|
}, 0);
|
2017-10-30 20:38:18 -04:00
|
|
|
let minDist = actors.reduce((prev, cur) => {
|
2014-06-17 13:10:54 -04:00
|
|
|
return Math.min(prev, cur._distance);
|
|
|
|
}, Infinity);
|
|
|
|
let normalization = maxDist - minDist;
|
|
|
|
|
2020-02-14 05:24:58 -05:00
|
|
|
actors.forEach(actor => {
|
|
|
|
let clone = new Clutter.Clone({ source: actor });
|
|
|
|
this._clonesAnimating.push(clone);
|
|
|
|
Main.uiGroup.add_actor(clone);
|
|
|
|
});
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ^
|
|
|
|
* | These need to be separate loops because Main.uiGroup.add_actor
|
|
|
|
* | is excessively slow if done inside the below loop and we want the
|
|
|
|
* | below loop to complete within one frame interval (#2065, !1002).
|
|
|
|
* v
|
|
|
|
*/
|
|
|
|
|
|
|
|
this._clonesAnimating.forEach(actorClone => {
|
|
|
|
let actor = actorClone.source;
|
2014-06-17 13:10:54 -04:00
|
|
|
actor.opacity = 0;
|
2014-09-02 16:49:10 -04:00
|
|
|
actor.reactive = false;
|
2014-06-17 13:10:54 -04:00
|
|
|
|
2019-01-29 14:20:09 -05:00
|
|
|
let [width, height] = this._getAllocatedChildSizeAndSpacing(actor);
|
2014-06-17 13:10:54 -04:00
|
|
|
actorClone.set_size(width, height);
|
|
|
|
let scaleX = sourceScaledWidth / width;
|
|
|
|
let scaleY = sourceScaledHeight / height;
|
2014-09-02 17:24:19 -04:00
|
|
|
let [adjustedSourcePositionX, adjustedSourcePositionY] = [sourceCenterX - sourceScaledWidth / 2, sourceCenterY - sourceScaledHeight / 2];
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
|
|
let movementParams, fadeParams;
|
|
|
|
if (animationDirection == AnimationDirection.IN) {
|
2014-09-06 06:47:33 -04:00
|
|
|
let isLastItem = actor._distance == minDist;
|
|
|
|
|
2014-06-17 13:10:54 -04:00
|
|
|
actorClone.opacity = 0;
|
|
|
|
actorClone.set_scale(scaleX, scaleY);
|
2020-01-10 04:37:26 -05:00
|
|
|
actorClone.set_translation(
|
|
|
|
adjustedSourcePositionX, adjustedSourcePositionY, 0);
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
|
|
let delay = (1 - (actor._distance - minDist) / normalization) * ANIMATION_MAX_DELAY_FOR_ITEM;
|
|
|
|
let [finalX, finalY] = actor._transformedPosition;
|
2018-07-20 15:46:19 -04:00
|
|
|
movementParams = {
|
2020-01-10 04:37:26 -05:00
|
|
|
translation_x: finalX,
|
|
|
|
translation_y: finalY,
|
2018-07-20 15:46:19 -04:00
|
|
|
scale_x: 1,
|
|
|
|
scale_y: 1,
|
|
|
|
duration: ANIMATION_TIME_IN,
|
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
delay,
|
2018-07-20 15:46:19 -04:00
|
|
|
};
|
2019-08-08 18:18:38 -04:00
|
|
|
|
|
|
|
if (isLastItem)
|
|
|
|
movementParams.onComplete = this._animationDone.bind(this);
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
fadeParams = {
|
|
|
|
opacity: 255,
|
|
|
|
duration: ANIMATION_FADE_IN_TIME_FOR_ITEM,
|
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
delay,
|
2018-07-20 15:46:19 -04:00
|
|
|
};
|
2014-06-17 13:10:54 -04:00
|
|
|
} else {
|
2014-09-06 08:07:22 -04:00
|
|
|
let isLastItem = actor._distance == maxDist;
|
2014-09-06 06:47:33 -04:00
|
|
|
|
2014-06-17 13:10:54 -04:00
|
|
|
let [startX, startY] = actor._transformedPosition;
|
2020-01-10 04:37:26 -05:00
|
|
|
actorClone.set_translation(startX, startY, 0);
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
|
|
let delay = (actor._distance - minDist) / normalization * ANIMATION_MAX_DELAY_OUT_FOR_ITEM;
|
2018-07-20 15:46:19 -04:00
|
|
|
movementParams = {
|
2020-01-10 04:37:26 -05:00
|
|
|
translation_x: adjustedSourcePositionX,
|
|
|
|
translation_y: adjustedSourcePositionY,
|
2018-07-20 15:46:19 -04:00
|
|
|
scale_x: scaleX,
|
|
|
|
scale_y: scaleY,
|
|
|
|
duration: ANIMATION_TIME_OUT,
|
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
delay,
|
2018-07-20 15:46:19 -04:00
|
|
|
};
|
2019-08-08 18:18:38 -04:00
|
|
|
|
|
|
|
if (isLastItem)
|
|
|
|
movementParams.onComplete = this._animationDone.bind(this);
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
fadeParams = {
|
|
|
|
opacity: 0,
|
|
|
|
duration: ANIMATION_FADE_IN_TIME_FOR_ITEM,
|
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM,
|
2018-07-20 15:46:19 -04:00
|
|
|
};
|
2014-06-17 13:10:54 -04:00
|
|
|
}
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
actorClone.ease(movementParams);
|
|
|
|
actorClone.ease(fadeParams);
|
2020-02-14 05:24:58 -05:00
|
|
|
});
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_getAllocatedChildSizeAndSpacing(child) {
|
2014-06-17 06:47:00 -04:00
|
|
|
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];
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_calculateChildBox(child, x, y, box) {
|
2013-08-21 13:16:58 -04:00
|
|
|
/* Center the item in its allocation horizontally */
|
2014-06-17 06:47:00 -04:00
|
|
|
let [width, height, childXSpacing, childYSpacing] =
|
|
|
|
this._getAllocatedChildSizeAndSpacing(child);
|
2013-08-21 13:16:58 -04:00
|
|
|
|
|
|
|
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;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-21 13:16:58 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
columnsForWidth(rowWidth) {
|
2011-03-21 18:53:07 -04:00
|
|
|
return this._computeLayout(rowWidth)[0];
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2011-03-21 18:53:07 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getRowLimit() {
|
2012-05-08 16:03:17 -04:00
|
|
|
return this._rowLimit;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2012-05-08 16:03:17 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_computeLayout(forWidth) {
|
2018-11-24 18:57:00 -05:00
|
|
|
this.ensure_style();
|
|
|
|
|
2010-07-20 22:22:19 -04:00
|
|
|
let nColumns = 0;
|
2013-08-23 05:14:21 -04:00
|
|
|
let usedWidth = this.leftPadding + this.rightPadding;
|
2013-08-21 13:31:09 -04:00
|
|
|
let spacing = this._getSpacing();
|
2013-02-19 18:38:11 -05:00
|
|
|
|
2010-07-20 22:22:19 -04:00
|
|
|
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
2013-08-15 04:38:17 -04:00
|
|
|
(usedWidth + this._getHItemSize() <= forWidth)) {
|
|
|
|
usedWidth += this._getHItemSize() + spacing;
|
2010-07-20 22:22:19 -04:00
|
|
|
nColumns += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nColumns > 0)
|
2013-02-19 18:38:11 -05:00
|
|
|
usedWidth -= spacing;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2013-08-21 13:31:09 -04:00
|
|
|
return [nColumns, usedWidth];
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onStyleChanged() {
|
2018-07-02 10:03:07 -04:00
|
|
|
let themeNode = this.get_theme_node();
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 17:38:36 -04:00
|
|
|
this._spacing = themeNode.get_length('spacing');
|
2012-02-14 10:30:40 -05:00
|
|
|
this._hItemSize = themeNode.get_length('-shell-grid-horizontal-item-size') || ICON_SIZE;
|
|
|
|
this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
|
2018-07-02 10:03:07 -04:00
|
|
|
this.queue_relayout();
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
nRows(forWidth) {
|
2013-08-30 12:50:35 -04:00
|
|
|
let children = this._getVisibleChildren();
|
2019-08-19 15:38:51 -04:00
|
|
|
let nColumns = forWidth < 0 ? children.length : this._computeLayout(forWidth)[0];
|
|
|
|
let nRows = nColumns > 0 ? Math.ceil(children.length / nColumns) : 0;
|
2013-08-30 12:50:35 -04:00
|
|
|
if (this._rowLimit)
|
|
|
|
nRows = Math.min(nRows, this._rowLimit);
|
|
|
|
return nRows;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
rowsForHeight(forHeight) {
|
2013-08-15 04:38:17 -04:00
|
|
|
return Math.floor((forHeight - (this.topPadding + this.bottomPadding) + this._getSpacing()) / (this._getVItemSize() + this._getSpacing()));
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
usedHeightForNRows(nRows) {
|
2013-08-15 04:38:17 -04:00
|
|
|
return (this._getVItemSize() + this._getSpacing()) * nRows - this._getSpacing() + this.topPadding + this.bottomPadding;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
usedWidth(forWidth) {
|
2013-08-15 04:38:17 -04:00
|
|
|
return this.usedWidthForNColumns(this.columnsForWidth(forWidth));
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-15 04:38:17 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
usedWidthForNColumns(columns) {
|
2013-08-15 04:38:17 -04:00
|
|
|
let usedWidth = columns * (this._getHItemSize() + this._getSpacing());
|
2013-08-30 12:50:35 -04:00
|
|
|
usedWidth -= this._getSpacing();
|
|
|
|
return usedWidth + this.leftPadding + this.rightPadding;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
addItem(item, index) {
|
2019-01-29 16:43:41 -05:00
|
|
|
if (!(item.icon instanceof BaseIcon))
|
2013-10-30 13:05:20 -04:00
|
|
|
throw new Error('Only items with a BaseIcon icon property can be added to IconGrid');
|
2013-08-29 17:25:13 -04:00
|
|
|
|
|
|
|
this._items.push(item);
|
2012-06-11 13:53:32 -04:00
|
|
|
if (index !== undefined)
|
2019-07-16 05:24:13 -04:00
|
|
|
this.insert_child_at_index(item, index);
|
2012-06-11 13:53:32 -04:00
|
|
|
else
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(item);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2010-07-20 22:22:19 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
removeItem(item) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.remove_child(item);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setSpacing(spacing) {
|
2013-08-21 13:31:09 -04:00
|
|
|
this._fixedSpacing = spacing;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-21 13:31:09 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_getSpacing() {
|
2013-08-21 13:31:09 -04:00
|
|
|
return this._fixedSpacing ? this._fixedSpacing : this._spacing;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-21 13:31:09 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_getHItemSize() {
|
2013-08-15 04:38:17 -04:00
|
|
|
return this._fixedHItemSize ? this._fixedHItemSize : this._hItemSize;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-15 04:38:17 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_getVItemSize() {
|
2013-08-15 04:38:17 -04:00
|
|
|
return this._fixedVItemSize ? this._fixedVItemSize : this._vItemSize;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-15 04:38:17 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateSpacingForSize(availWidth, availHeight) {
|
2013-08-15 04:38:17 -04:00
|
|
|
let maxEmptyVArea = availHeight - this._minRows * this._getVItemSize();
|
|
|
|
let maxEmptyHArea = availWidth - this._minColumns * this._getHItemSize();
|
2013-08-27 15:23:13 -04:00
|
|
|
let maxHSpacing, maxVSpacing;
|
|
|
|
|
2013-08-23 05:14:21 -04:00
|
|
|
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
|
2019-01-28 20:27:05 -05:00
|
|
|
maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows + 1));
|
|
|
|
maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns + 1));
|
2013-08-23 05:14:21 -04:00
|
|
|
} 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));
|
|
|
|
}
|
2013-08-27 15:23:13 -04:00
|
|
|
|
|
|
|
let maxSpacing = Math.min(maxHSpacing, maxVSpacing);
|
|
|
|
// Limit spacing to the item size
|
2013-08-15 04:38:17 -04:00
|
|
|
maxSpacing = Math.min(maxSpacing, Math.min(this._getVItemSize(), this._getHItemSize()));
|
|
|
|
// The minimum spacing, regardless of whether it satisfies the row/columng minima,
|
2013-08-27 15:23:13 -04:00
|
|
|
// is the spacing we get from CSS.
|
2013-08-23 05:14:21 -04:00
|
|
|
let spacing = Math.max(this._spacing, maxSpacing);
|
|
|
|
this.setSpacing(spacing);
|
|
|
|
if (this._padWithSpacing)
|
|
|
|
this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-15 04:38:17 -04:00
|
|
|
|
2020-05-18 22:01:41 -04:00
|
|
|
_computePages(availWidthPerPage, availHeightPerPage) {
|
|
|
|
const [nColumns] = this._computeLayout(availWidthPerPage);
|
|
|
|
const children = this._getVisibleChildren();
|
|
|
|
let nRows;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-10-17 13:49:18 -04:00
|
|
|
/*
|
2013-08-15 04:38:17 -04:00
|
|
|
* This function must to be called before iconGrid allocation,
|
|
|
|
* to know how much spacing can the grid has
|
|
|
|
*/
|
2017-10-30 20:03:21 -04:00
|
|
|
adaptToSize(availWidth, availHeight) {
|
2013-08-15 04:38:17 -04:00
|
|
|
this._fixedHItemSize = this._hItemSize;
|
|
|
|
this._fixedVItemSize = this._vItemSize;
|
|
|
|
this._updateSpacingForSize(availWidth, availHeight);
|
|
|
|
|
|
|
|
if (this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < this._minRows) {
|
2019-01-28 20:27:05 -05:00
|
|
|
let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth;
|
|
|
|
let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight;
|
2013-08-15 04:38:17 -04:00
|
|
|
|
2019-08-19 15:38:51 -04:00
|
|
|
let neededSpacePerItem = neededWidth > neededHeight
|
2019-08-19 15:33:15 -04:00
|
|
|
? Math.ceil(neededWidth / this._minColumns)
|
|
|
|
: Math.ceil(neededHeight / this._minRows);
|
2013-08-15 04:38:17 -04:00
|
|
|
this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE);
|
|
|
|
this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE);
|
|
|
|
|
|
|
|
this._updateSpacingForSize(availWidth, availHeight);
|
|
|
|
}
|
2019-08-19 20:51:42 -04:00
|
|
|
if (!this._updateIconSizesLaterId) {
|
2019-07-22 11:06:30 -04:00
|
|
|
this._updateIconSizesLaterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
|
|
|
|
this._updateIconSizes.bind(this));
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2020-05-18 22:01:41 -04:00
|
|
|
this._computePages(availWidth, availHeight);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-15 04:38:17 -04:00
|
|
|
|
|
|
|
// Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateIconSizes() {
|
2019-07-22 11:06:30 -04:00
|
|
|
this._updateIconSizesLaterId = 0;
|
2014-08-17 13:44:52 -04:00
|
|
|
let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize);
|
2014-06-17 18:04:34 -04:00
|
|
|
let newIconSize = Math.floor(ICON_SIZE * scale);
|
2019-08-19 20:51:42 -04:00
|
|
|
for (let i in this._items)
|
2013-08-15 04:38:17 -04:00
|
|
|
this._items[i].icon.setIconSize(newIconSize);
|
2019-08-19 20:51:42 -04:00
|
|
|
|
2019-07-22 14:37:33 -04:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2013-08-27 15:23:13 -04:00
|
|
|
}
|
2013-08-20 04:14:25 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
nPages() {
|
2013-08-20 04:14:25 -04:00
|
|
|
return this._nPages;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-20 04:14:25 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getPageHeight() {
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
return this._availableHeightPerPageForItems();
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getPageY(pageNumber) {
|
2013-08-20 04:14:25 -04:00
|
|
|
if (!this._nPages)
|
|
|
|
return 0;
|
|
|
|
|
2019-01-28 20:18:52 -05:00
|
|
|
let firstPageItem = pageNumber * this._childrenPerPage;
|
2013-08-20 04:14:25 -04:00
|
|
|
let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
|
2013-08-23 05:14:21 -04:00
|
|
|
return childBox.y1 - this.topPadding;
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getItemPage(item) {
|
2013-08-27 15:22:16 -04:00
|
|
|
let children = this._getVisibleChildren();
|
|
|
|
let index = children.indexOf(item);
|
2019-01-29 17:00:46 -05:00
|
|
|
if (index == -1)
|
2013-08-27 15:22:16 -04:00
|
|
|
throw new Error('Item not found.');
|
|
|
|
return Math.floor(index / this._childrenPerPage);
|
2017-10-30 21:23:39 -04:00
|
|
|
}
|
2011-11-20 12:56:27 -05:00
|
|
|
});
|