8886b7433c
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
291 lines
10 KiB
JavaScript
291 lines
10 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
|
|
const Lang = imports.lang;
|
|
const Params = imports.misc.params;
|
|
|
|
const ICON_SIZE = 48;
|
|
|
|
|
|
function BaseIcon(label, createIcon) {
|
|
this._init(label, createIcon);
|
|
}
|
|
|
|
BaseIcon.prototype = {
|
|
_init : function(label, params) {
|
|
params = Params.parse(params, { createIcon: null,
|
|
setSizeManually: false });
|
|
this.actor = new St.Bin({ style_class: 'overview-icon',
|
|
x_fill: true,
|
|
y_fill: true });
|
|
this.actor._delegate = this;
|
|
this.actor.connect('style-changed',
|
|
Lang.bind(this, this._onStyleChanged));
|
|
|
|
this._spacing = 0;
|
|
|
|
let box = new Shell.GenericContainer();
|
|
box.connect('allocate', Lang.bind(this, this._allocate));
|
|
box.connect('get-preferred-width',
|
|
Lang.bind(this, this._getPreferredWidth));
|
|
box.connect('get-preferred-height',
|
|
Lang.bind(this, this._getPreferredHeight));
|
|
this.actor.set_child(box);
|
|
|
|
this.iconSize = ICON_SIZE;
|
|
this._iconBin = new St.Bin();
|
|
|
|
box.add_actor(this._iconBin);
|
|
|
|
this._name = new St.Label({ text: label });
|
|
box.add_actor(this._name);
|
|
|
|
if (params.createIcon)
|
|
this.createIcon = params.createIcon;
|
|
this._setSizeManually = params.setSizeManually;
|
|
|
|
this.icon = this.createIcon(this.iconSize);
|
|
this._iconBin.set_child(this.icon);
|
|
},
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
let availWidth = box.x2 - box.x1;
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
let [labelMinHeight, labelNatHeight] = this._name.get_preferred_height(-1);
|
|
let [iconMinHeight, iconNatHeight] = this._iconBin.get_preferred_height(-1);
|
|
let preferredHeight = labelNatHeight + this._spacing + iconNatHeight;
|
|
let labelHeight = availHeight >= preferredHeight ? labelNatHeight
|
|
: labelMinHeight;
|
|
let iconSize = availHeight - this._spacing - labelHeight;
|
|
let iconPadding = (availWidth - iconSize) / 2;
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
childBox.x1 = iconPadding;
|
|
childBox.y1 = 0;
|
|
childBox.x2 = availWidth - iconPadding;
|
|
childBox.y2 = iconSize;
|
|
this._iconBin.allocate(childBox, flags);
|
|
|
|
childBox.x1 = 0;
|
|
childBox.x2 = availWidth;
|
|
childBox.y1 = iconSize + this._spacing;
|
|
childBox.y2 = childBox.y1 + labelHeight;
|
|
this._name.allocate(childBox, flags);
|
|
},
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
this._getPreferredHeight(actor, -1, alloc);
|
|
},
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
let [iconMinHeight, iconNatHeight] = this._iconBin.get_preferred_height(forWidth);
|
|
let [labelMinHeight, labelNatHeight] = this._name.get_preferred_height(forWidth);
|
|
alloc.min_size = iconMinHeight + this._spacing + labelMinHeight;
|
|
alloc.natural_size = iconNatHeight + this._spacing + labelNatHeight;
|
|
},
|
|
|
|
// This can be overridden by a subclass, or by the createIcon
|
|
// parameter to _init()
|
|
createIcon: function(size) {
|
|
throw new Error('no implementation of createIcon in ' + this);
|
|
},
|
|
|
|
setIconSize: function(size) {
|
|
if (!this._setSizeManually)
|
|
throw new Error('setSizeManually has to be set to use setIconsize');
|
|
|
|
this._setIconSize(size);
|
|
},
|
|
|
|
_setIconSize: function(size) {
|
|
if (size == this.iconSize)
|
|
return;
|
|
|
|
this.icon.destroy();
|
|
this.iconSize = size;
|
|
this.icon = this.createIcon(this.iconSize);
|
|
this._iconBin.child = this.icon;
|
|
},
|
|
|
|
_onStyleChanged: function() {
|
|
let node = this.actor.get_theme_node();
|
|
this._spacing = node.get_length('spacing');
|
|
|
|
if (this._setSizeManually)
|
|
return;
|
|
|
|
let len = node.get_length('icon-size');
|
|
if (len > 0)
|
|
this._setIconSize(len);
|
|
}
|
|
};
|
|
|
|
function IconGrid(params) {
|
|
this._init(params);
|
|
}
|
|
|
|
IconGrid.prototype = {
|
|
_init: function(params) {
|
|
params = Params.parse(params, { rowLimit: null,
|
|
columnLimit: null,
|
|
xAlign: St.Align.MIDDLE });
|
|
this._rowLimit = params.rowLimit;
|
|
this._colLimit = params.columnLimit;
|
|
this._xAlign = params.xAlign;
|
|
|
|
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
|
vertical: true });
|
|
// Pulled from CSS, but hardcode some defaults here
|
|
this._spacing = 0;
|
|
this._item_size = ICON_SIZE;
|
|
this._grid = new Shell.GenericContainer();
|
|
this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
|
|
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
|
|
|
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
this._grid.connect('allocate', Lang.bind(this, this._allocate));
|
|
},
|
|
|
|
_getPreferredWidth: function (grid, forHeight, alloc) {
|
|
let children = this._grid.get_children();
|
|
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
|
children.length)
|
|
: children.length;
|
|
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
|
|
// Kind of a lie, but not really an issue right now. If
|
|
// we wanted to support some sort of hidden/overflow that would
|
|
// need higher level design
|
|
alloc.min_size = this._item_size;
|
|
alloc.natural_size = nColumns * this._item_size + totalSpacing;
|
|
},
|
|
|
|
_getPreferredHeight: function (grid, forWidth, alloc) {
|
|
let children = this._grid.get_children();
|
|
let [nColumns, usedWidth] = 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);
|
|
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
|
|
let height = nRows * this._item_size + totalSpacing;
|
|
alloc.min_size = height;
|
|
alloc.natural_size = height;
|
|
},
|
|
|
|
_allocate: function (grid, box, flags) {
|
|
let children = this._grid.get_children();
|
|
let availWidth = box.x2 - box.x1;
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
|
|
|
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 [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
|
|
= children[i].get_preferred_size();
|
|
|
|
/* Center the item in its allocation horizontally */
|
|
let width = Math.min(this._item_size, childNaturalWidth);
|
|
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
|
|
let height = Math.min(this._item_size, childNaturalHeight);
|
|
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
if (St.Widget.get_default_direction() == St.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;
|
|
|
|
if (this._rowLimit && rowIndex >= this._rowLimit) {
|
|
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._item_size + this._spacing;
|
|
x = box.x1 + leftPadding;
|
|
} else {
|
|
x += this._item_size + this._spacing;
|
|
}
|
|
}
|
|
},
|
|
|
|
_computeLayout: function (forWidth) {
|
|
let children = this._grid.get_children();
|
|
let nColumns = 0;
|
|
let usedWidth = 0;
|
|
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
|
(usedWidth + this._item_size <= forWidth)) {
|
|
usedWidth += this._item_size + this._spacing;
|
|
nColumns += 1;
|
|
}
|
|
|
|
if (nColumns > 0)
|
|
usedWidth -= this._spacing;
|
|
|
|
return [nColumns, usedWidth];
|
|
},
|
|
|
|
_onStyleChanged: function() {
|
|
let themeNode = this.actor.get_theme_node();
|
|
this._spacing = themeNode.get_length('spacing');
|
|
this._item_size = themeNode.get_length('-shell-grid-item-size');
|
|
this._grid.queue_relayout();
|
|
},
|
|
|
|
removeAll: function () {
|
|
this._grid.get_children().forEach(Lang.bind(this, function (child) {
|
|
child.destroy();
|
|
}));
|
|
},
|
|
|
|
addItem: function(actor) {
|
|
this._grid.add_actor(actor);
|
|
},
|
|
|
|
getItemAtIndex: function(index) {
|
|
return this._grid.get_children()[index];
|
|
},
|
|
|
|
visibleItemsCount: function() {
|
|
return this._grid.get_children().length - this._grid.get_n_skip_paint();
|
|
}
|
|
};
|