switcherList: Stop using Shell.GenericContainer

This commit removes all the uses of Shell.GenericContainer from
SwitcherPopup.SwitcherList. Compared to the other patches, this
one was specially trickier to get right, and a few invasive
changes needed to be done.

The most noticeable one is that the allocation of the items is
done entirely by St.BoxLayout -- we don't manually allocate them
anymore. To make it work, get_preferred_width() had to calculate
the correct value. It now assumes that:

 * Minimum width: the minimum width of the widest child.
 * Natural width: the minimum width of the StBoxLayout (use it
   instead of the natural width to force the labels to ellipsize
   when too long.)

The AppIcon class became a St.Widget subclass as well, to override
get_preferred_width() and be able to keep the squared shape.

Besides that, add a new SwitcherButton class to reimplement squared
icons without having to resort to hacks in the size allocation
machinery. This class has a single vfunc override to ensure that it
is squared when the SwitcherList is.

The arrows indicating multiple windows are now in this._list
actor to the SwitcherPopup itself, since this._list automatically
manages its own children now.

At last, adapt (but preserve) the hack in CyclerPopup.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/153
This commit is contained in:
Georges Basile Stavracas Neto 2018-06-28 12:34:01 -03:00
parent 9a47b4b343
commit c6cea277eb
No known key found for this signature in database
GPG Key ID: 886C17EE170D1385
3 changed files with 183 additions and 151 deletions

View File

@ -3,6 +3,7 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
@ -87,7 +88,7 @@ var AppSwitcherPopup = new Lang.Class({
// We try to avoid overflowing the screen so we base the resulting size on // We try to avoid overflowing the screen so we base the resulting size on
// those calculations // those calculations
if (this._thumbnails) { if (this._thumbnails) {
let childBox = this._switcherList.actor.get_allocation_box(); let childBox = this._switcherList.get_allocation_box();
let primary = Main.layoutManager.primaryMonitor; let primary = Main.layoutManager.primaryMonitor;
let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT); let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT);
@ -95,10 +96,10 @@ var AppSwitcherPopup = new Lang.Class({
let bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM); let bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM);
let hPadding = leftPadding + rightPadding; let hPadding = leftPadding + rightPadding;
let icon = this._items[this._selectedIndex].actor; let icon = this._items[this._selectedIndex];
let [posX, posY] = icon.get_transformed_position(); let [posX, posY] = icon.get_transformed_position();
let thumbnailCenter = posX + icon.width / 2; let thumbnailCenter = posX + icon.width / 2;
let [childMinWidth, childNaturalWidth] = this._thumbnails.actor.get_preferred_width(-1); let [childMinWidth, childNaturalWidth] = this._thumbnails.get_preferred_width(-1);
childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2)); childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) { if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) {
let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding; let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding;
@ -110,11 +111,11 @@ var AppSwitcherPopup = new Lang.Class({
childBox.x2 = childBox.x1 + childNaturalWidth; childBox.x2 = childBox.x1 + childNaturalWidth;
if (childBox.x2 > primary.x + primary.width - rightPadding) if (childBox.x2 > primary.x + primary.width - rightPadding)
childBox.x2 = primary.x + primary.width - rightPadding; childBox.x2 = primary.x + primary.width - rightPadding;
childBox.y1 = this._switcherList.actor.allocation.y2 + spacing; childBox.y1 = this._switcherList.allocation.y2 + spacing;
this._thumbnails.addClones(primary.y + primary.height - bottomPadding - childBox.y1); this._thumbnails.addClones(primary.y + primary.height - bottomPadding - childBox.y1);
let [childMinHeight, childNaturalHeight] = this._thumbnails.actor.get_preferred_height(-1); let [childMinHeight, childNaturalHeight] = this._thumbnails.get_preferred_height(-1);
childBox.y2 = childBox.y1 + childNaturalHeight; childBox.y2 = childBox.y1 + childNaturalHeight;
this._thumbnails.actor.allocate(childBox, flags); this._thumbnails.allocate(childBox, flags);
} }
}, },
@ -367,7 +368,7 @@ var AppSwitcherPopup = new Lang.Class({
}, },
_destroyThumbnails() { _destroyThumbnails() {
let thumbnailsActor = this._thumbnails.actor; let thumbnailsActor = this._thumbnails;
Tweener.addTween(thumbnailsActor, Tweener.addTween(thumbnailsActor,
{ opacity: 0, { opacity: 0,
time: THUMBNAIL_FADE_TIME, time: THUMBNAIL_FADE_TIME,
@ -387,19 +388,19 @@ var AppSwitcherPopup = new Lang.Class({
this._thumbnails.connect('item-activated', this._windowActivated.bind(this)); this._thumbnails.connect('item-activated', this._windowActivated.bind(this));
this._thumbnails.connect('item-entered', this._windowEntered.bind(this)); this._thumbnails.connect('item-entered', this._windowEntered.bind(this));
this._thumbnails.connect('item-removed', this._windowRemoved.bind(this)); this._thumbnails.connect('item-removed', this._windowRemoved.bind(this));
this._thumbnails.actor.connect('destroy', () => { this._thumbnails.connect('destroy', () => {
this._thumbnails = null; this._thumbnails = null;
this._thumbnailsFocused = false; this._thumbnailsFocused = false;
}); });
this.add_actor(this._thumbnails.actor); this.add_actor(this._thumbnails);
// Need to force an allocation so we can figure out whether we // Need to force an allocation so we can figure out whether we
// need to scroll when selecting // need to scroll when selecting
this._thumbnails.actor.get_allocation_box(); this._thumbnails.get_allocation_box();
this._thumbnails.actor.opacity = 0; this._thumbnails.opacity = 0;
Tweener.addTween(this._thumbnails.actor, Tweener.addTween(this._thumbnails,
{ opacity: 255, { opacity: 255,
time: THUMBNAIL_FADE_TIME, time: THUMBNAIL_FADE_TIME,
transition: 'easeOutQuad', transition: 'easeOutQuad',
@ -471,6 +472,21 @@ var CyclerHighlight = new Lang.Class({
} }
}); });
// We don't show an actual popup, so just provide what SwitcherPopup
// expects instead of inheriting from SwitcherList
var CyclerList = new Lang.Class({
Name: 'CyclerList',
Extends: St.Widget,
Signals: { 'item-activated': { param_types: [GObject.TYPE_INT] },
'item-entered': { param_types: [GObject.TYPE_INT] },
'item-removed': { param_types: [GObject.TYPE_INT] },
'item-highlighted': { param_types: [GObject.TYPE_INT] } },
highlight(index, justOutline) {
this.emit('item-highlighted', index);
}
});
var CyclerPopup = new Lang.Class({ var CyclerPopup = new Lang.Class({
Name: 'CyclerPopup', Name: 'CyclerPopup',
Extends: SwitcherPopup.SwitcherPopup, Extends: SwitcherPopup.SwitcherPopup,
@ -487,11 +503,10 @@ var CyclerPopup = new Lang.Class({
this._highlight = new CyclerHighlight(); this._highlight = new CyclerHighlight();
global.window_group.add_actor(this._highlight.actor); global.window_group.add_actor(this._highlight.actor);
// We don't show an actual popup, so just provide what SwitcherPopup this._switcherList = new CyclerList();
// expects instead of inheriting from SwitcherList this._switcherList.connect('item-highlighted', (list, index) => {
this._switcherList = { actor: new St.Widget(), this._highlightItem(index);
highlight: this._highlightItem.bind(this), });
connect() {} };
}, },
_highlightItem(index, justOutline) { _highlightItem(index, justOutline) {
@ -653,22 +668,32 @@ var WindowCyclerPopup = new Lang.Class({
var AppIcon = new Lang.Class({ var AppIcon = new Lang.Class({
Name: 'AppIcon', Name: 'AppIcon',
Extends: St.BoxLayout,
_init(app) { _init(app) {
this.app = app; this.parent({ style_class: 'alt-tab-app',
this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
vertical: true }); vertical: true });
this.app = app;
this.icon = null; this.icon = null;
this._iconBin = new St.Bin({ x_fill: true, y_fill: true }); this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
this.actor.add(this._iconBin, { x_fill: false, y_fill: false } ); this.add(this._iconBin, { x_fill: false, y_fill: false } );
this.label = new St.Label({ text: this.app.get_name() }); this.label = new St.Label({ text: this.app.get_name() });
this.actor.add(this.label, { x_fill: false }); this.add(this.label, { x_fill: false });
}, },
set_size(size) { set_size(size) {
this.icon = this.app.create_icon_texture(size); this.icon = this.app.create_icon_texture(size);
this._iconBin.child = this.icon; this._iconBin.child = this.icon;
this._iconBin.set_size(size, size);
},
vfunc_get_preferred_width(forHeight) {
let [minWidth, ] = this.parent(forHeight);
minWidth = Math.max(minWidth, forHeight);
return [minWidth, minWidth];
} }
}); });
@ -707,11 +732,10 @@ var AppSwitcher = new Lang.Class({
} }
this._curApp = -1; this._curApp = -1;
this._iconSize = 0;
this._altTabPopup = altTabPopup; this._altTabPopup = altTabPopup;
this._mouseTimeOutId = 0; this._mouseTimeOutId = 0;
this.actor.connect('destroy', this._onDestroy.bind(this)); this.connect('destroy', this._onDestroy.bind(this));
}, },
_onDestroy() { _onDestroy() {
@ -738,17 +762,16 @@ var AppSwitcher = new Lang.Class({
// We just assume the whole screen here due to weirdness happing with the passed width // We just assume the whole screen here due to weirdness happing with the passed width
let primary = Main.layoutManager.primaryMonitor; let primary = Main.layoutManager.primaryMonitor;
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding(); let parentPadding = this.get_parent().get_theme_node().get_horizontal_padding();
let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding(); let availWidth = primary.width - parentPadding - this.get_theme_node().get_horizontal_padding();
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
let iconSizes = baseIconSizes.map(s => s * scaleFactor); let iconSizes = baseIconSizes.map(s => s * scaleFactor);
let iconSize = baseIconSizes[0];
if (this._items.length == 1) { if (this._items.length > 1) {
this._iconSize = baseIconSizes[0];
} else {
for(let i = 0; i < baseIconSizes.length; i++) { for(let i = 0; i < baseIconSizes.length; i++) {
this._iconSize = baseIconSizes[i]; iconSize = baseIconSizes[i];
let height = iconSizes[i] + iconSpacing; let height = iconSizes[i] + iconSpacing;
let w = height * this._items.length + totalSpacing; let w = height * this._items.length + totalSpacing;
if (w <= availWidth) if (w <= availWidth)
@ -756,32 +779,36 @@ var AppSwitcher = new Lang.Class({
} }
} }
this._iconSize = iconSize;
for(let i = 0; i < this.icons.length; i++) { for(let i = 0; i < this.icons.length; i++) {
if (this.icons[i].icon != null) if (this.icons[i].icon != null)
break; break;
this.icons[i].set_size(this._iconSize); this.icons[i].set_size(iconSize);
} }
}, },
_getPreferredHeight(actor, forWidth, alloc) { vfunc_get_preferred_height(forWidth) {
this._setIconSize(); this._setIconSize();
this.parent(actor, forWidth, alloc); return this.parent(forWidth);
}, },
_allocate(actor, box, flags) { vfunc_allocate(box, flags) {
// Allocate the main list items // Allocate the main list items
this.parent(actor, box, flags); this.parent(box, flags);
let arrowHeight = Math.floor(this.actor.get_theme_node().get_padding(St.Side.BOTTOM) / 3); let contentBox = this.get_theme_node().get_content_box(box);
let arrowHeight = Math.floor(this.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
let arrowWidth = arrowHeight * 2; let arrowWidth = arrowHeight * 2;
// Now allocate each arrow underneath its item // Now allocate each arrow underneath its item
let childBox = new Clutter.ActorBox(); let childBox = new Clutter.ActorBox();
for (let i = 0; i < this._items.length; i++) { for (let i = 0; i < this._items.length; i++) {
let itemBox = this._items[i].allocation; let itemBox = this._items[i].allocation;
childBox.x1 = Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2); childBox.x1 = contentBox.x1 + Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
childBox.x2 = childBox.x1 + arrowWidth; childBox.x2 = childBox.x1 + arrowWidth;
childBox.y1 = itemBox.y2 + arrowHeight; childBox.y1 = contentBox.y1 + itemBox.y2 + arrowHeight;
childBox.y2 = childBox.y1 + arrowHeight; childBox.y2 = childBox.y1 + arrowHeight;
this._arrows[i].allocate(childBox, flags); this._arrows[i].allocate(childBox, flags);
} }
@ -839,7 +866,7 @@ var AppSwitcher = new Lang.Class({
_addIcon(appIcon) { _addIcon(appIcon) {
this.icons.push(appIcon); this.icons.push(appIcon);
let item = this.addItem(appIcon.actor, appIcon.label); let item = this.addItem(appIcon, appIcon.label);
appIcon._stateChangedId = appIcon.app.connect('notify::state', app => { appIcon._stateChangedId = appIcon.app.connect('notify::state', app => {
if (app.state != Shell.AppState.RUNNING) if (app.state != Shell.AppState.RUNNING)
@ -849,7 +876,7 @@ var AppSwitcher = new Lang.Class({
let n = this._arrows.length; let n = this._arrows.length;
let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' }); let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
arrow.connect('repaint', () => { SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM); }); arrow.connect('repaint', () => { SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM); });
this._list.add_actor(arrow); this.add_actor(arrow);
this._arrows.push(arrow); this._arrows.push(arrow);
if (appIcon.cachedWindows.length == 1) if (appIcon.cachedWindows.length == 1)
@ -907,21 +934,21 @@ var ThumbnailList = new Lang.Class({
} }
this.actor.connect('destroy', this._onDestroy.bind(this)); this.connect('destroy', this._onDestroy.bind(this));
}, },
addClones(availHeight) { addClones(availHeight) {
if (!this._thumbnailBins.length) if (!this._thumbnailBins.length)
return; return;
let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding(); let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
totalPadding += this.actor.get_theme_node().get_horizontal_padding() + this.actor.get_theme_node().get_vertical_padding(); totalPadding += this.get_theme_node().get_horizontal_padding() + this.get_theme_node().get_vertical_padding();
let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1); let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
let spacing = this._items[0].child.get_theme_node().get_length('spacing'); let spacing = this._items[0].child.get_theme_node().get_length('spacing');
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor; let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor;
availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, thumbnailSize); availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, thumbnailSize);
let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.actor.get_theme_node().get_vertical_padding() - spacing; let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.get_theme_node().get_vertical_padding() - spacing;
binHeight = Math.min(thumbnailSize, binHeight); binHeight = Math.min(thumbnailSize, binHeight);
for (let i = 0; i < this._thumbnailBins.length; i++) { for (let i = 0; i < this._thumbnailBins.length; i++) {
@ -956,7 +983,7 @@ var ThumbnailList = new Lang.Class({
if (this._clones.length > 0) if (this._clones.length > 0)
this.highlight(SwitcherPopup.mod(index, this._clones.length)); this.highlight(SwitcherPopup.mod(index, this._clones.length));
else else
this.actor.destroy(); this.destroy();
}, },
_onDestroy() { _onDestroy() {
@ -1034,7 +1061,7 @@ var WindowList = new Lang.Class({
this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER, this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER }); y_align: Clutter.ActorAlign.CENTER });
this.actor.add_actor(this._label); this.add_actor(this._label);
this.windows = windows; this.windows = windows;
this.icons = []; this.icons = [];
@ -1051,7 +1078,7 @@ var WindowList = new Lang.Class({
}); });
} }
this.actor.connect('destroy', () => { this._onDestroy(); }); this.connect('destroy', this._onDestroy.bind(this));
}, },
_onDestroy() { _onDestroy() {
@ -1060,26 +1087,40 @@ var WindowList = new Lang.Class({
}); });
}, },
_getPreferredHeight(actor, forWidth, alloc) { vfunc_get_preferred_height(forWidth) {
this.parent(actor, forWidth, alloc); let [minHeight, natHeight] = this.parent(forWidth);
let spacing = this.actor.get_theme_node().get_padding(St.Side.BOTTOM); let spacing = this.get_theme_node().get_padding(St.Side.BOTTOM);
let [labelMin, labelNat] = this._label.get_preferred_height(-1); let [labelMin, labelNat] = this._label.get_preferred_height(-1);
alloc.min_size += labelMin + spacing;
alloc.natural_size += labelNat + spacing; minHeight += labelMin + spacing;
natHeight += labelNat + spacing;
return [minHeight, natHeight];
}, },
_allocateTop(actor, box, flags) { vfunc_allocate(box, flags) {
let themeNode = this.get_theme_node();
let contentBox = themeNode.get_content_box(box);
let childBox = new Clutter.ActorBox(); let childBox = new Clutter.ActorBox();
childBox.x1 = box.x1; childBox.x1 = contentBox.x1;
childBox.x2 = box.x2; childBox.x2 = contentBox.x2;
childBox.y2 = box.y2; childBox.y2 = contentBox.y2;
childBox.y1 = childBox.y2 - this._label.height; childBox.y1 = childBox.y2 - this._label.height;
this._label.allocate(childBox, flags); this._label.allocate(childBox, flags);
let spacing = this.actor.get_theme_node().get_padding(St.Side.BOTTOM); let totalLabelHeight = this._label.height + themeNode.get_padding(St.Side.BOTTOM)
box.y2 -= this._label.height + spacing; childBox.x1 = box.x1;
this.parent(actor, box, flags); childBox.x2 = box.x2;
childBox.y1 = box.y1;
childBox.y2 = box.y2 - totalLabelHeight;
this.parent(childBox, flags);
// Hooking up the parent vfunc will call this.set_allocation() with
// the height without the label height, so call it again with the
// correct size here.
this.set_allocation(box, flags);
}, },
highlight(index, justOutline) { highlight(index, justOutline) {

View File

@ -125,7 +125,7 @@ var CtrlAltTabManager = new Lang.Class({
this._popup = new CtrlAltTabPopup(items); this._popup = new CtrlAltTabPopup(items);
this._popup.show(backward, binding, mask); this._popup.show(backward, binding, mask);
this._popup.actor.connect('destroy', this._popup.connect('destroy',
() => { () => {
this._popup = null; this._popup = null;
}); });

View File

@ -2,6 +2,7 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
@ -83,13 +84,13 @@ var SwitcherPopup = new Lang.Class({
// Allocate the switcherList // Allocate the switcherList
// We select a size based on an icon size that does not overflow the screen // We select a size based on an icon size that does not overflow the screen
let [childMinHeight, childNaturalHeight] = this._switcherList.actor.get_preferred_height(primary.width - hPadding); let [childMinHeight, childNaturalHeight] = this._switcherList.get_preferred_height(primary.width - hPadding);
let [childMinWidth, childNaturalWidth] = this._switcherList.actor.get_preferred_width(childNaturalHeight); let [childMinWidth, childNaturalWidth] = this._switcherList.get_preferred_width(childNaturalHeight);
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2)); childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth); childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2); childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
childBox.y2 = childBox.y1 + childNaturalHeight; childBox.y2 = childBox.y1 + childNaturalHeight;
this._switcherList.actor.allocate(childBox, flags); this._switcherList.allocate(childBox, flags);
}, },
_initialSelection(backward, binding) { _initialSelection(backward, binding) {
@ -119,7 +120,7 @@ var SwitcherPopup = new Lang.Class({
this.connect('button-press-event', this._clickedOutside.bind(this)); this.connect('button-press-event', this._clickedOutside.bind(this));
this.connect('scroll-event', this._scrollEvent.bind(this)); this.connect('scroll-event', this._scrollEvent.bind(this));
this.add_actor(this._switcherList.actor); this.add_actor(this._switcherList);
this._switcherList.connect('item-activated', this._itemActivated.bind(this)); this._switcherList.connect('item-activated', this._itemActivated.bind(this));
this._switcherList.connect('item-entered', this._itemEntered.bind(this)); this._switcherList.connect('item-entered', this._itemEntered.bind(this));
this._switcherList.connect('item-removed', this._itemRemoved.bind(this)); this._switcherList.connect('item-removed', this._itemRemoved.bind(this));
@ -324,35 +325,53 @@ var SwitcherPopup = new Lang.Class({
} }
}); });
var SwitcherButton = new Lang.Class({
Name: 'SwitcherButton',
Extends: St.Button,
_init(square) {
this.parent({ style_class: 'item-box',
reactive: true });
this._square = square;
},
vfunc_get_preferred_width(forHeight) {
if (this._square)
return this.get_preferred_height(-1);
else
return this.parent(forHeight);
}
});
var SwitcherList = new Lang.Class({ var SwitcherList = new Lang.Class({
Name: 'SwitcherList', Name: 'SwitcherList',
Extends: St.Widget,
Signals: { 'item-activated': { param_types: [GObject.TYPE_INT] },
'item-entered': { param_types: [GObject.TYPE_INT] },
'item-removed': { param_types: [GObject.TYPE_INT] } },
_init(squareItems) { _init(squareItems) {
this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' }); this.parent({ style_class: 'switcher-list' });
this.actor.connect('get-preferred-width', this._getPreferredWidth.bind(this));
this.actor.connect('get-preferred-height', this._getPreferredHeight.bind(this)); this._list = new St.BoxLayout({ style_class: 'switcher-list-item-container',
this.actor.connect('allocate', this._allocateTop.bind(this)); vertical: false,
x_expand: true,
y_expand: true });
let layoutManager = this._list.get_layout_manager();
// Here we use a GenericContainer so that we can force all the
// children to have the same width.
this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
this._list.spacing = 0; this._list.spacing = 0;
this._list.connect('style-changed', () => { this._list.connect('style-changed', () => {
this._list.spacing = this._list.get_theme_node().get_length('spacing'); this._list.spacing = this._list.get_theme_node().get_length('spacing');
}); });
this._list.connect('get-preferred-width', this._getPreferredWidth.bind(this));
this._list.connect('get-preferred-height', this._getPreferredHeight.bind(this));
this._list.connect('allocate', this._allocate.bind(this));
this._scrollView = new St.ScrollView({ style_class: 'hfade', this._scrollView = new St.ScrollView({ style_class: 'hfade',
enable_mouse_scrolling: false }); enable_mouse_scrolling: false });
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER); this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
let scrollBox = new St.BoxLayout(); this._scrollView.add_actor(this._list);
scrollBox.add_actor(this._list); this.add_actor(this._scrollView);
this._scrollView.add_actor(scrollBox);
this.actor.add_actor(this._scrollView);
// Those arrows indicate whether scrolling in one direction is possible // Those arrows indicate whether scrolling in one direction is possible
this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow', this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
@ -366,50 +385,20 @@ var SwitcherList = new Lang.Class({
drawArrow(this._rightArrow, St.Side.RIGHT); drawArrow(this._rightArrow, St.Side.RIGHT);
}); });
this.actor.add_actor(this._leftArrow); this.add_actor(this._leftArrow);
this.actor.add_actor(this._rightArrow); this.add_actor(this._rightArrow);
this._items = []; this._items = [];
this._highlighted = -1; this._highlighted = -1;
this._squareItems = squareItems; this._squareItems = squareItems;
this._minSize = 0;
this._scrollableRight = true; this._scrollableRight = true;
this._scrollableLeft = false; this._scrollableLeft = false;
},
_allocateTop(actor, box, flags) { layoutManager.homogeneous = squareItems;
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
let childBox = new Clutter.ActorBox();
let scrollable = this._minSize > box.x2 - box.x1;
box.y1 -= this.actor.get_theme_node().get_padding(St.Side.TOP);
box.y2 += this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
this._scrollView.allocate(box, flags);
let arrowWidth = Math.floor(leftPadding / 3);
let arrowHeight = arrowWidth * 2;
childBox.x1 = leftPadding / 2;
childBox.y1 = this.actor.height / 2 - arrowWidth;
childBox.x2 = childBox.x1 + arrowWidth;
childBox.y2 = childBox.y1 + arrowHeight;
this._leftArrow.allocate(childBox, flags);
this._leftArrow.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
arrowWidth = Math.floor(rightPadding / 3);
arrowHeight = arrowWidth * 2;
childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
childBox.y1 = this.actor.height / 2 - arrowWidth;
childBox.x2 = childBox.x1 + arrowWidth;
childBox.y2 = childBox.y1 + arrowHeight;
this._rightArrow.allocate(childBox, flags);
this._rightArrow.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
}, },
addItem(item, label) { addItem(item, label) {
let bbox = new St.Button({ style_class: 'item-box', let bbox = new SwitcherButton(this._squareItems);
reactive: true });
bbox.set_child(item); bbox.set_child(item);
this._list.add_actor(bbox); this._list.add_actor(bbox);
@ -462,8 +451,8 @@ var SwitcherList = new Lang.Class({
let adjustment = this._scrollView.hscroll.adjustment; let adjustment = this._scrollView.hscroll.adjustment;
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values(); let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
let [absItemX, absItemY] = this._items[index].get_transformed_position(); let [absItemX, absItemY] = this._items[index].get_transformed_position();
let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0); let [result, posX, posY] = this.transform_stage_point(absItemX, 0);
let [containerWidth, containerHeight] = this.actor.get_transformed_size(); let [containerWidth, containerHeight] = this.get_transformed_size();
if (posX + this._items[index].get_width() > containerWidth) if (posX + this._items[index].get_width() > containerWidth)
this._scrollToRight(); this._scrollToRight();
else if (this._items[index].allocation.x1 - value < 0) else if (this._items[index].allocation.x1 - value < 0)
@ -490,7 +479,7 @@ var SwitcherList = new Lang.Class({
onComplete: () => { onComplete: () => {
if (this._highlighted == 0) if (this._highlighted == 0)
this._scrollableLeft = false; this._scrollableLeft = false;
this.actor.queue_relayout(); this.queue_relayout();
} }
}); });
}, },
@ -514,7 +503,7 @@ var SwitcherList = new Lang.Class({
onComplete: () => { onComplete: () => {
if (this._highlighted == this._items.length - 1) if (this._highlighted == this._items.length - 1)
this._scrollableRight = false; this._scrollableRight = false;
this.actor.queue_relayout(); this.queue_relayout();
} }
}); });
}, },
@ -546,16 +535,15 @@ var SwitcherList = new Lang.Class({
return [maxChildMin, maxChildNat]; return [maxChildMin, maxChildNat];
}, },
_getPreferredWidth(actor, forHeight, alloc) { vfunc_get_preferred_width(forHeight) {
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight); let themeNode = this.get_theme_node();
let [maxChildMin, ] = this._maxChildWidth(forHeight);
let [minListWidth, ] = this._list.get_preferred_width(forHeight);
let totalSpacing = Math.max(this._list.spacing * (this._items.length - 1), 0); return themeNode.adjust_preferred_width(maxChildMin, minListWidth);
alloc.min_size = this._items.length * maxChildMin + totalSpacing;
alloc.natural_size = alloc.min_size;
this._minSize = alloc.min_size;
}, },
_getPreferredHeight(actor, forWidth, alloc) { vfunc_get_preferred_height(forWidth) {
let maxChildMin = 0; let maxChildMin = 0;
let maxChildNat = 0; let maxChildNat = 0;
@ -571,43 +559,46 @@ var SwitcherList = new Lang.Class({
maxChildNat = maxChildMin; maxChildNat = maxChildMin;
} }
alloc.min_size = maxChildMin; let themeNode = this.get_theme_node();
alloc.natural_size = maxChildNat; return themeNode.adjust_preferred_height(maxChildMin, maxChildNat);
}, },
_allocate(actor, box, flags) { vfunc_allocate(box, flags) {
let childHeight = box.y2 - box.y1; this.set_allocation(box, flags);
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight); let contentBox = this.get_theme_node().get_content_box(box);
let totalSpacing = Math.max(this._list.spacing * (this._items.length - 1), 0); let width = contentBox.x2 - contentBox.x1;
let height = contentBox.y2 - contentBox.y1;
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing) / this._items.length); let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT);
let rightPadding = this.get_theme_node().get_padding(St.Side.RIGHT);
let [, natScrollViewWidth] = this._scrollView.get_preferred_width(height);
let x = 0;
let children = this._list.get_children();
let childBox = new Clutter.ActorBox(); let childBox = new Clutter.ActorBox();
let scrollable = natScrollViewWidth > width;
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT); this._scrollView.allocate(contentBox, flags);
for (let i = 0; i < children.length; i++) { let arrowWidth = Math.floor(leftPadding / 3);
if (this._items.indexOf(children[i]) != -1) { let arrowHeight = arrowWidth * 2;
let [childMin, childNat] = children[i].get_preferred_height(childWidth); childBox.x1 = leftPadding / 2;
let vSpacing = (childHeight - childNat) / 2; childBox.y1 = this.height / 2 - arrowWidth;
childBox.x1 = x; childBox.x2 = childBox.x1 + arrowWidth;
childBox.y1 = vSpacing; childBox.y2 = childBox.y1 + arrowHeight;
childBox.x2 = x + childWidth; this._leftArrow.allocate(childBox, flags);
childBox.y2 = childBox.y1 + childNat; this._leftArrow.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
children[i].allocate(childBox, flags);
x += this._list.spacing + childWidth; arrowWidth = Math.floor(rightPadding / 3);
} else { arrowHeight = arrowWidth * 2;
// Something else, eg, AppSwitcher's arrows; childBox.x1 = this.width - arrowWidth - rightPadding / 2;
// we don't allocate it. childBox.y1 = this.height / 2 - arrowWidth;
} childBox.x2 = childBox.x1 + arrowWidth;
} childBox.y2 = childBox.y1 + arrowHeight;
this._rightArrow.allocate(childBox, flags);
this._rightArrow.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
} }
}); });
Signals.addSignalMethods(SwitcherList.prototype);
function drawArrow(area, side) { function drawArrow(area, side) {
let themeNode = area.get_theme_node(); let themeNode = area.get_theme_node();