c32e928fc6
Change the "overlay" actor to be a group of 4 actors that we can rearrange so as to have a hole in the middle (to cover up the whole screen except for the highlighted icon). For non-iconified windows, we still highlight them the old way (raising them above the overlay), because we don't want square highlights around shaped windows.
292 lines
11 KiB
JavaScript
292 lines
11 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Big = imports.gi.Big;
|
|
const Clutter = imports.gi.Clutter;
|
|
const Lang = imports.lang;
|
|
const Meta = imports.gi.Meta;
|
|
const Pango = imports.gi.Pango;
|
|
const Shell = imports.gi.Shell;
|
|
|
|
const Main = imports.ui.main;
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
const POPUP_BG_COLOR = new Clutter.Color();
|
|
POPUP_BG_COLOR.from_pixel(0x00000080);
|
|
const POPUP_INDICATOR_COLOR = new Clutter.Color();
|
|
POPUP_INDICATOR_COLOR.from_pixel(0xf0f0f0ff);
|
|
const POPUP_TRANSPARENT = new Clutter.Color();
|
|
POPUP_TRANSPARENT.from_pixel(0x00000000);
|
|
|
|
const POPUP_INDICATOR_WIDTH = 4;
|
|
const POPUP_GRID_SPACING = 8;
|
|
const POPUP_ICON_SIZE = 48;
|
|
const POPUP_NUM_COLUMNS = 5;
|
|
|
|
const POPUP_LABEL_MAX_WIDTH = POPUP_NUM_COLUMNS * (POPUP_ICON_SIZE + POPUP_GRID_SPACING);
|
|
|
|
const OVERLAY_COLOR = new Clutter.Color();
|
|
OVERLAY_COLOR.from_pixel(0x00000044);
|
|
|
|
const SHOW_TIME = 0.05;
|
|
const SWITCH_TIME = 0.1;
|
|
|
|
function AltTabPopup() {
|
|
this._init();
|
|
}
|
|
|
|
AltTabPopup.prototype = {
|
|
_init : function() {
|
|
let global = Shell.Global.get();
|
|
|
|
this.actor = new Big.Box({ background_color : POPUP_BG_COLOR,
|
|
corner_radius: POPUP_GRID_SPACING,
|
|
padding: POPUP_GRID_SPACING,
|
|
spacing: POPUP_GRID_SPACING,
|
|
orientation: Big.BoxOrientation.VERTICAL });
|
|
|
|
// Icon grid. It would be nice to use Tidy.Grid for the this,
|
|
// but Tidy.Grid is lame in various ways. (Eg, it seems to
|
|
// have a minimum size of 200x200.) So we create a vertical
|
|
// Big.Box containing multiple horizontal Big.Boxes.
|
|
this._grid = new Big.Box({ spacing: POPUP_GRID_SPACING,
|
|
orientation: Big.BoxOrientation.VERTICAL });
|
|
let gcenterbox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
x_align: Big.BoxAlignment.CENTER });
|
|
gcenterbox.append(this._grid, Big.BoxPackFlags.NONE);
|
|
this.actor.append(gcenterbox, Big.BoxPackFlags.NONE);
|
|
|
|
// Selected-window label
|
|
this._label = new Clutter.Text({ font_name: "Sans 16px",
|
|
ellipsize: Pango.EllipsizeMode.END });
|
|
|
|
let labelbox = new Big.Box({ background_color: POPUP_INDICATOR_COLOR,
|
|
corner_radius: POPUP_GRID_SPACING / 2,
|
|
padding: POPUP_GRID_SPACING / 2 });
|
|
labelbox.append(this._label, Big.BoxPackFlags.EXPAND);
|
|
let lcenterbox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
x_align: Big.BoxAlignment.CENTER,
|
|
width: POPUP_LABEL_MAX_WIDTH + POPUP_GRID_SPACING });
|
|
lcenterbox.append(labelbox, Big.BoxPackFlags.NONE);
|
|
this.actor.append(lcenterbox, Big.BoxPackFlags.NONE);
|
|
|
|
// Indicator around selected icon
|
|
this._indicator = new Big.Rectangle({ border_width: POPUP_INDICATOR_WIDTH,
|
|
corner_radius: POPUP_INDICATOR_WIDTH / 2,
|
|
border_color: POPUP_INDICATOR_COLOR,
|
|
color: POPUP_TRANSPARENT });
|
|
this.actor.append(this._indicator, Big.BoxPackFlags.FIXED);
|
|
|
|
this._items = [];
|
|
this._toplevels = global.window_group.get_children();
|
|
|
|
global.stage.add_actor(this.actor);
|
|
|
|
// Dark translucent window used to cover all but the
|
|
// currently-selected window while Alt-Tabbing. Actually
|
|
// contains four actors which can we rearrange to create
|
|
// a hole in the overlay.
|
|
this._overlay = new Clutter.Group({ reactive: true });
|
|
this._overlay_top = new Clutter.Rectangle({ color: OVERLAY_COLOR,
|
|
border_width: 0 });
|
|
this._overlay_bottom = new Clutter.Rectangle({ color: OVERLAY_COLOR,
|
|
border_width: 0 });
|
|
this._overlay_left = new Clutter.Rectangle({ color: OVERLAY_COLOR,
|
|
border_width: 0 });
|
|
this._overlay_right = new Clutter.Rectangle({ color: OVERLAY_COLOR,
|
|
border_width: 0 });
|
|
this._overlay.add_actor(this._overlay_top);
|
|
this._overlay.add_actor(this._overlay_bottom);
|
|
this._overlay.add_actor(this._overlay_left);
|
|
this._overlay.add_actor(this._overlay_right);
|
|
},
|
|
|
|
addWindow : function(win) {
|
|
let item = { window: win,
|
|
metaWindow: win.get_meta_window() };
|
|
|
|
let pixbuf = item.metaWindow.icon;
|
|
item.icon = new Clutter.Texture({ width: POPUP_ICON_SIZE,
|
|
height: POPUP_ICON_SIZE,
|
|
keep_aspect_ratio: true });
|
|
Shell.clutter_texture_set_from_pixbuf(item.icon, pixbuf);
|
|
|
|
item.box = new Big.Box({ padding: POPUP_INDICATOR_WIDTH * 2 });
|
|
item.box.append(item.icon, Big.BoxPackFlags.NONE);
|
|
|
|
item.above = null;
|
|
for (let i = 1; i < this._toplevels.length; i++) {
|
|
if (this._toplevels[i] == win) {
|
|
item.above = this._toplevels[i - 1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
item.visible = item.metaWindow.showing_on_its_workspace();
|
|
|
|
if (!item.visible) {
|
|
let rect = new Meta.Rectangle();
|
|
if (item.metaWindow.get_icon_geometry(rect))
|
|
item.icon_rect = rect;
|
|
}
|
|
|
|
item.n = this._items.length;
|
|
this._items.push(item);
|
|
|
|
// Add it to the grid
|
|
if (!this._gridRow || this._gridRow.get_children().length == POPUP_NUM_COLUMNS) {
|
|
this._gridRow = new Big.Box({ spacing: POPUP_GRID_SPACING,
|
|
orientation: Big.BoxOrientation.HORIZONTAL });
|
|
this._grid.append(this._gridRow, Big.BoxPackFlags.NONE);
|
|
}
|
|
this._gridRow.append(item.box, Big.BoxPackFlags.NONE);
|
|
},
|
|
|
|
show : function(initialSelection) {
|
|
let global = Shell.Global.get();
|
|
|
|
Main.startModal();
|
|
|
|
global.window_group.add_actor(this._overlay);
|
|
this._overlay.raise_top();
|
|
this._overlay.show();
|
|
this.actor.opacity = 0;
|
|
Tweener.addTween(this.actor, { opacity: 255,
|
|
time: SHOW_TIME,
|
|
transition: "easeOutQuad" });
|
|
|
|
this.actor.show_all();
|
|
this.actor.x = (global.screen_width - this.actor.width) / 2;
|
|
this.actor.y = (global.screen_height - this.actor.height) / 2;
|
|
|
|
this.select(initialSelection);
|
|
},
|
|
|
|
destroy : function() {
|
|
this.actor.destroy();
|
|
this._overlay.destroy();
|
|
|
|
Main.endModal();
|
|
},
|
|
|
|
select : function(n) {
|
|
if (this._selected) {
|
|
// Unselect previous
|
|
|
|
if (this._allocationChangedId) {
|
|
this._selected.box.disconnect(this._allocationChangedId);
|
|
delete this._allocationChangedId;
|
|
}
|
|
|
|
if (this._selected.above)
|
|
this._selected.window.raise(this._selected.above);
|
|
else
|
|
this._selected.window.lower_bottom();
|
|
}
|
|
|
|
let item = this._items[n];
|
|
let changed = this._selected && item != this._selected;
|
|
this._selected = item;
|
|
|
|
if (this._selected) {
|
|
this._label.set_size(-1, -1);
|
|
this._label.text = this._selected.metaWindow.title;
|
|
if (this._label.width > POPUP_LABEL_MAX_WIDTH)
|
|
this._label.width = POPUP_LABEL_MAX_WIDTH;
|
|
|
|
// Figure out this._selected.box's coordinates in terms of
|
|
// this.actor
|
|
let bx = this._selected.box.x, by = this._selected.box.y;
|
|
let actor = this._selected.box.get_parent();
|
|
while (actor != this.actor) {
|
|
bx += actor.x;
|
|
by += actor.y;
|
|
actor = actor.get_parent();
|
|
}
|
|
|
|
if (changed) {
|
|
Tweener.addTween(this._indicator,
|
|
{ x: bx,
|
|
y: by,
|
|
width: this._selected.box.width,
|
|
height: this._selected.box.height,
|
|
time: SWITCH_TIME,
|
|
transition: "easeOutQuad" });
|
|
} else {
|
|
Tweener.removeTweens(this.indicator);
|
|
this._indicator.set_position(bx, by);
|
|
this._indicator.set_size(this._selected.box.width,
|
|
this._selected.box.height);
|
|
}
|
|
this._indicator.show();
|
|
|
|
if (this._overlay.visible) {
|
|
if (this._selected.visible)
|
|
this._selected.window.raise(this._overlay);
|
|
this._adjust_overlay();
|
|
}
|
|
|
|
this._allocationChangedId =
|
|
this._selected.box.connect('notify::allocation',
|
|
Lang.bind(this, this._allocationChanged));
|
|
} else {
|
|
this._label.text = "";
|
|
this._indicator.hide();
|
|
}
|
|
},
|
|
|
|
_allocationChanged : function() {
|
|
if (this._selected)
|
|
this.select(this._selected.n);
|
|
},
|
|
|
|
_adjust_overlay : function() {
|
|
let global = Shell.Global.get();
|
|
|
|
if (this._selected && this._selected.icon_rect) {
|
|
// We want to highlight a specific rectangle within the
|
|
// task bar, so rearrange the pieces of the overlay to
|
|
// cover the whole screen except that rectangle
|
|
|
|
let rect = this._selected.icon_rect;
|
|
|
|
this._overlay_top.x = 0;
|
|
this._overlay_top.y = 0;
|
|
this._overlay_top.width = global.screen_width;
|
|
this._overlay_top.height = rect.y;
|
|
|
|
this._overlay_left.x = 0;
|
|
this._overlay_left.y = rect.y;
|
|
this._overlay_left.width = rect.x;
|
|
this._overlay_left.height = rect.height;
|
|
this._overlay_left.show();
|
|
|
|
this._overlay_right.x = rect.x + rect.width;
|
|
this._overlay_right.y = rect.y;
|
|
this._overlay_right.width = global.screen_width - rect.x - rect.width;
|
|
this._overlay_right.height = rect.height;
|
|
this._overlay_right.show();
|
|
|
|
this._overlay_bottom.x = 0;
|
|
this._overlay_bottom.y = rect.y + rect.height;
|
|
this._overlay_bottom.width = global.screen_width;
|
|
this._overlay_bottom.height = global.screen_height - rect.y - rect.height;
|
|
this._overlay_bottom.show();
|
|
} else {
|
|
// Either there's no current selection, or the selection
|
|
// is a visible window. Make the overlay cover the whole
|
|
// screen. select() will raise the selected window over
|
|
// the overlay.
|
|
|
|
this._overlay_top.x = 0;
|
|
this._overlay_top.y = 0;
|
|
this._overlay_top.width = global.screen_width;
|
|
this._overlay_top.height = global.screen_height;
|
|
this._overlay_top.show();
|
|
|
|
this._overlay_left.hide();
|
|
this._overlay_right.hide();
|
|
this._overlay_bottom.hide();
|
|
}
|
|
}
|
|
};
|