switcherPopup: Factor out altTab and ctrlAltTab's common code
https://bugzilla.gnome.org/show_bug.cgi?id=689528
This commit is contained in:
parent
31d14a2fb0
commit
00338bbc4b
@ -1542,7 +1542,7 @@ StScrollBar StButton#vhandle:active {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* App Switcher */
|
/* App Switcher */
|
||||||
#altTabPopup {
|
.switcher-popup {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
spacing: 16px;
|
spacing: 16px;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,7 @@ nobase_dist_js_DATA = \
|
|||||||
ui/status/power.js \
|
ui/status/power.js \
|
||||||
ui/status/volume.js \
|
ui/status/volume.js \
|
||||||
ui/status/bluetooth.js \
|
ui/status/bluetooth.js \
|
||||||
|
ui/switcherPopup.js \
|
||||||
ui/tweener.js \
|
ui/tweener.js \
|
||||||
ui/unlockDialog.js \
|
ui/unlockDialog.js \
|
||||||
ui/userMenu.js \
|
ui/userMenu.js \
|
||||||
|
697
js/ui/altTab.js
697
js/ui/altTab.js
@ -1,117 +1,56 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gdk = imports.gi.Gdk;
|
|
||||||
const Gtk = imports.gi.Gtk;
|
|
||||||
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;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const Signals = imports.signals;
|
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
const Atk = imports.gi.Atk;
|
const Atk = imports.gi.Atk;
|
||||||
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const SwitcherPopup = imports.ui.switcherPopup;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
const POPUP_APPICON_SIZE = 96;
|
|
||||||
const POPUP_SCROLL_TIME = 0.10; // seconds
|
|
||||||
const POPUP_DELAY_TIMEOUT = 150; // milliseconds
|
|
||||||
const POPUP_FADE_OUT_TIME = 0.1; // seconds
|
|
||||||
|
|
||||||
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
|
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
|
||||||
|
|
||||||
const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
|
|
||||||
|
|
||||||
const THUMBNAIL_DEFAULT_SIZE = 256;
|
const THUMBNAIL_DEFAULT_SIZE = 256;
|
||||||
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
|
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
|
||||||
const THUMBNAIL_FADE_TIME = 0.1; // seconds
|
const THUMBNAIL_FADE_TIME = 0.1; // seconds
|
||||||
|
|
||||||
const iconSizes = [96, 64, 48, 32, 22];
|
const iconSizes = [96, 64, 48, 32, 22];
|
||||||
|
|
||||||
function mod(a, b) {
|
|
||||||
return (a + b) % b;
|
|
||||||
}
|
|
||||||
|
|
||||||
function primaryModifier(mask) {
|
|
||||||
if (mask == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
let primary = 1;
|
|
||||||
while (mask > 1) {
|
|
||||||
mask >>= 1;
|
|
||||||
primary <<= 1;
|
|
||||||
}
|
|
||||||
return primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AltTabPopup = new Lang.Class({
|
const AltTabPopup = new Lang.Class({
|
||||||
Name: 'AltTabPopup',
|
Name: 'AltTabPopup',
|
||||||
|
Extends: SwitcherPopup.SwitcherPopup,
|
||||||
|
|
||||||
_init : function() {
|
_init : function() {
|
||||||
this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
|
this.parent();
|
||||||
reactive: true,
|
|
||||||
visible: false });
|
|
||||||
|
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
this._thumbnails = null;
|
||||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
||||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
||||||
|
|
||||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
||||||
|
|
||||||
this._haveModal = false;
|
|
||||||
this._modifierMask = 0;
|
|
||||||
|
|
||||||
this._currentApp = 0;
|
|
||||||
this._currentWindow = -1;
|
|
||||||
this._thumbnailTimeoutId = 0;
|
this._thumbnailTimeoutId = 0;
|
||||||
this._motionTimeoutId = 0;
|
this._currentWindow = -1;
|
||||||
this._initialDelayTimeoutId = 0;
|
|
||||||
|
|
||||||
this.thumbnailsVisible = false;
|
this.thumbnailsVisible = false;
|
||||||
|
|
||||||
// Initially disable hover so we ignore the enter-event if
|
|
||||||
// the switcher appears underneath the current pointer location
|
|
||||||
this._disableHover();
|
|
||||||
|
|
||||||
Main.uiGroup.add_actor(this.actor);
|
|
||||||
},
|
|
||||||
|
|
||||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
|
||||||
alloc.min_size = global.screen_width;
|
|
||||||
alloc.natural_size = global.screen_width;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
|
||||||
alloc.min_size = global.screen_height;
|
|
||||||
alloc.natural_size = global.screen_height;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_allocate: function (actor, box, flags) {
|
_allocate: function (actor, box, flags) {
|
||||||
let childBox = new Clutter.ActorBox();
|
this.parent(actor, box, flags);
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
|
|
||||||
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 bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
|
||||||
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
|
||||||
let hPadding = leftPadding + rightPadding;
|
|
||||||
|
|
||||||
// Allocate the appSwitcher
|
|
||||||
// We select a size based on an icon size that does not overflow the screen
|
|
||||||
let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding);
|
|
||||||
let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight);
|
|
||||||
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.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
|
||||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
|
||||||
this._appSwitcher.actor.allocate(childBox, flags);
|
|
||||||
|
|
||||||
// Allocate the thumbnails
|
// Allocate the thumbnails
|
||||||
// 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 icon = this._appIcons[this._currentApp].actor;
|
let childBox = this._switcherList.actor.get_allocation_box();
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
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 bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||||
|
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
||||||
|
let hPadding = leftPadding + rightPadding;
|
||||||
|
|
||||||
|
let icon = this._items[this._selectedIndex].actor;
|
||||||
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.actor.get_preferred_width(-1);
|
||||||
@ -126,7 +65,7 @@ const AltTabPopup = 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._appSwitcher.actor.allocation.y2 + spacing;
|
childBox.y1 = this._switcherList.actor.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.actor.get_preferred_height(-1);
|
||||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||||
@ -164,280 +103,158 @@ const AltTabPopup = new Lang.Class({
|
|||||||
return [apps, allApps];
|
return [apps, allApps];
|
||||||
},
|
},
|
||||||
|
|
||||||
show : function(backward, binding, mask) {
|
_createSwitcher: function() {
|
||||||
let [localApps, otherApps] = this._getAppLists();
|
let [localApps, otherApps] = this._getAppLists();
|
||||||
|
|
||||||
if (localApps.length == 0 && otherApps.length == 0)
|
if (localApps.length == 0 && otherApps.length == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!Main.pushModal(this.actor)) {
|
this._switcherList = new AppSwitcher(localApps, otherApps, this);
|
||||||
// Probably someone else has a pointer grab, try again with keyboard only
|
this._items = this._switcherList.icons;
|
||||||
if (!Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED })) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._haveModal = true;
|
|
||||||
this._modifierMask = primaryModifier(mask);
|
|
||||||
|
|
||||||
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
return true;
|
||||||
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
},
|
||||||
|
|
||||||
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
|
_initialSelection: function(backward, binding) {
|
||||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
|
|
||||||
|
|
||||||
this._appSwitcher = new AppSwitcher(localApps, otherApps, this);
|
|
||||||
this.actor.add_actor(this._appSwitcher.actor);
|
|
||||||
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
|
|
||||||
this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
|
|
||||||
|
|
||||||
this._appIcons = this._appSwitcher.icons;
|
|
||||||
|
|
||||||
// Need to force an allocation so we can figure out whether we
|
|
||||||
// need to scroll when selecting
|
|
||||||
this.actor.opacity = 0;
|
|
||||||
this.actor.show();
|
|
||||||
this.actor.get_allocation_box();
|
|
||||||
|
|
||||||
// Make the initial selection
|
|
||||||
if (binding == 'switch-group') {
|
if (binding == 'switch-group') {
|
||||||
if (backward) {
|
if (backward) {
|
||||||
this._select(0, this._appIcons[0].cachedWindows.length - 1);
|
this._select(0, this._items[0].cachedWindows.length - 1);
|
||||||
} else {
|
} else {
|
||||||
if (this._appIcons[0].cachedWindows.length > 1)
|
if (this._items[0].cachedWindows.length > 1)
|
||||||
this._select(0, 1);
|
this._select(0, 1);
|
||||||
else
|
else
|
||||||
this._select(0, 0);
|
this._select(0, 0);
|
||||||
}
|
}
|
||||||
} else if (binding == 'switch-group-backward') {
|
} else if (binding == 'switch-group-backward') {
|
||||||
this._select(0, this._appIcons[0].cachedWindows.length - 1);
|
this._select(0, this._items[0].cachedWindows.length - 1);
|
||||||
} else if (binding == 'switch-windows-backward') {
|
} else if (binding == 'switch-windows-backward') {
|
||||||
this._select(this._appIcons.length - 1);
|
this._select(this._items.length - 1);
|
||||||
} else if (this._appIcons.length == 1) {
|
} else if (this._items.length == 1) {
|
||||||
this._select(0);
|
this._select(0);
|
||||||
} else if (backward) {
|
} else if (backward) {
|
||||||
this._select(this._appIcons.length - 1);
|
this._select(this._items.length - 1);
|
||||||
} else {
|
} else {
|
||||||
this._select(1);
|
this._select(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// There's a race condition; if the user released Alt before
|
|
||||||
// we got the grab, then we won't be notified. (See
|
|
||||||
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
|
|
||||||
// details.) So we check now. (Have to do this after updating
|
|
||||||
// selection.)
|
|
||||||
let [x, y, mods] = global.get_pointer();
|
|
||||||
if (!(mods & this._modifierMask)) {
|
|
||||||
this._finish();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We delay showing the popup so that fast Alt+Tab users aren't
|
|
||||||
// disturbed by the popup briefly flashing.
|
|
||||||
this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
|
|
||||||
Lang.bind(this, function () {
|
|
||||||
this.actor.opacity = 255;
|
|
||||||
this._initialDelayTimeoutId = 0;
|
|
||||||
}));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_nextApp : function() {
|
|
||||||
return mod(this._currentApp + 1, this._appIcons.length);
|
|
||||||
},
|
|
||||||
_previousApp : function() {
|
|
||||||
return mod(this._currentApp - 1, this._appIcons.length);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_nextWindow : function() {
|
_nextWindow : function() {
|
||||||
// We actually want the second window if we're in the unset state
|
// We actually want the second window if we're in the unset state
|
||||||
if (this._currentWindow == -1)
|
if (this._currentWindow == -1)
|
||||||
this._currentWindow = 0;
|
this._currentWindow = 0;
|
||||||
return mod(this._currentWindow + 1,
|
return SwitcherPopup.mod(this._currentWindow + 1,
|
||||||
this._appIcons[this._currentApp].cachedWindows.length);
|
this._items[this._selectedIndex].cachedWindows.length);
|
||||||
},
|
},
|
||||||
_previousWindow : function() {
|
_previousWindow : function() {
|
||||||
// Also assume second window here
|
// Also assume second window here
|
||||||
if (this._currentWindow == -1)
|
if (this._currentWindow == -1)
|
||||||
this._currentWindow = 1;
|
this._currentWindow = 1;
|
||||||
return mod(this._currentWindow - 1,
|
return SwitcherPopup.mod(this._currentWindow - 1,
|
||||||
this._appIcons[this._currentApp].cachedWindows.length);
|
this._items[this._selectedIndex].cachedWindows.length);
|
||||||
},
|
},
|
||||||
|
|
||||||
_keyPressEvent : function(actor, event) {
|
_keyPressHandler: function(keysym, backwards, action) {
|
||||||
let keysym = event.get_key_symbol();
|
if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
|
||||||
let event_state = event.get_state();
|
this._select(this._selectedIndex, backwards ? this._previousWindow() : this._nextWindow());
|
||||||
let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
|
|
||||||
let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
|
|
||||||
|
|
||||||
this._disableHover();
|
|
||||||
|
|
||||||
if (keysym == Clutter.Escape) {
|
|
||||||
this.destroy();
|
|
||||||
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
|
|
||||||
this._select(this._currentApp, backwards ? this._previousWindow() : this._nextWindow());
|
|
||||||
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
|
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
|
||||||
this._select(this._currentApp, this._previousWindow());
|
this._select(this._selectedIndex, this._previousWindow());
|
||||||
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
|
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
|
||||||
this._select(backwards ? this._previousApp() : this._nextApp());
|
this._select(backwards ? this._previous() : this._next());
|
||||||
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
|
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
|
||||||
this._select(this._previousApp());
|
this._select(this._previous());
|
||||||
} else if (this._thumbnailsFocused) {
|
} else if (this._thumbnailsFocused) {
|
||||||
if (keysym == Clutter.Left)
|
if (keysym == Clutter.Left)
|
||||||
this._select(this._currentApp, this._previousWindow());
|
this._select(this._selectedIndex, this._previousWindow());
|
||||||
else if (keysym == Clutter.Right)
|
else if (keysym == Clutter.Right)
|
||||||
this._select(this._currentApp, this._nextWindow());
|
this._select(this._selectedIndex, this._nextWindow());
|
||||||
else if (keysym == Clutter.Up)
|
else if (keysym == Clutter.Up)
|
||||||
this._select(this._currentApp, null, true);
|
this._select(this._selectedIndex, null, true);
|
||||||
} else {
|
} else {
|
||||||
if (keysym == Clutter.Left)
|
if (keysym == Clutter.Left)
|
||||||
this._select(this._previousApp());
|
this._select(this._previous());
|
||||||
else if (keysym == Clutter.Right)
|
else if (keysym == Clutter.Right)
|
||||||
this._select(this._nextApp());
|
this._select(this._next());
|
||||||
else if (keysym == Clutter.Down)
|
else if (keysym == Clutter.Down)
|
||||||
this._select(this._currentApp, 0);
|
this._select(this._selectedIndex, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_keyReleaseEvent : function(actor, event) {
|
_scrollHandler: function(direction) {
|
||||||
let [x, y, mods] = global.get_pointer();
|
|
||||||
let state = mods & this._modifierMask;
|
|
||||||
|
|
||||||
if (state == 0)
|
|
||||||
this._finish();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onScroll : function(actor, event) {
|
|
||||||
let direction = event.get_scroll_direction();
|
|
||||||
if (direction == Clutter.ScrollDirection.UP) {
|
if (direction == Clutter.ScrollDirection.UP) {
|
||||||
if (this._thumbnailsFocused) {
|
if (this._thumbnailsFocused) {
|
||||||
if (this._currentWindow == 0 || this._currentWindow == -1)
|
if (this._currentWindow == 0 || this._currentWindow == -1)
|
||||||
this._select(this._previousApp());
|
this._select(this._previous());
|
||||||
else
|
else
|
||||||
this._select(this._currentApp, this._previousWindow());
|
this._select(this._selectedIndex, this._previousWindow());
|
||||||
} else {
|
} else {
|
||||||
let nwindows = this._appIcons[this._currentApp].cachedWindows.length;
|
let nwindows = this._items[this._selectedIndex].cachedWindows.length;
|
||||||
if (nwindows > 1)
|
if (nwindows > 1)
|
||||||
this._select(this._currentApp, nwindows - 1);
|
this._select(this._selectedIndex, nwindows - 1);
|
||||||
else
|
else
|
||||||
this._select(this._previousApp());
|
this._select(this._previous());
|
||||||
}
|
}
|
||||||
} else if (direction == Clutter.ScrollDirection.DOWN) {
|
} else if (direction == Clutter.ScrollDirection.DOWN) {
|
||||||
if (this._thumbnailsFocused) {
|
if (this._thumbnailsFocused) {
|
||||||
if (this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
|
if (this._currentWindow == this._items[this._selectedIndex].cachedWindows.length - 1)
|
||||||
this._select(this._nextApp());
|
this._select(this._next());
|
||||||
else
|
else
|
||||||
this._select(this._currentApp, this._nextWindow());
|
this._select(this._selectedIndex, this._nextWindow());
|
||||||
} else {
|
} else {
|
||||||
let nwindows = this._appIcons[this._currentApp].cachedWindows.length;
|
let nwindows = this._items[this._selectedIndex].cachedWindows.length;
|
||||||
if (nwindows > 1)
|
if (nwindows > 1)
|
||||||
this._select(this._currentApp, 0);
|
this._select(this._selectedIndex, 0);
|
||||||
else
|
else
|
||||||
this._select(this._nextApp());
|
this._select(this._next());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_clickedOutside : function(actor, event) {
|
_itemActivatedHandler: function(n) {
|
||||||
this.destroy();
|
|
||||||
},
|
|
||||||
|
|
||||||
_appActivated : function(appSwitcher, n) {
|
|
||||||
// If the user clicks on the selected app, activate the
|
// If the user clicks on the selected app, activate the
|
||||||
// selected window; otherwise (eg, they click on an app while
|
// selected window; otherwise (eg, they click on an app while
|
||||||
// !mouseActive) activate the clicked-on app.
|
// !mouseActive) activate the clicked-on app.
|
||||||
let window;
|
if (n == this._selectedIndex && this._currentWindow >= 0)
|
||||||
if (n == this._currentApp && this._currentWindow >= 0)
|
this._select(n, this._currentWindow);
|
||||||
window = this._appIcons[n].cachedWindows[this._currentWindow];
|
|
||||||
else
|
else
|
||||||
window = null;
|
this._select(n);
|
||||||
this._appIcons[n].app.activate_window(window, global.get_current_time());
|
|
||||||
this.destroy();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_appEntered : function(appSwitcher, n) {
|
_itemEnteredHandler: function(n) {
|
||||||
if (!this._mouseActive)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._select(n);
|
this._select(n);
|
||||||
},
|
},
|
||||||
|
|
||||||
_windowActivated : function(thumbnailList, n) {
|
_windowActivated : function(thumbnailList, n) {
|
||||||
let appIcon = this._appIcons[this._currentApp];
|
let appIcon = this._items[this._selectedIndex];
|
||||||
Main.activateWindow(appIcon.cachedWindows[n]);
|
Main.activateWindow(appIcon.cachedWindows[n]);
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
_windowEntered : function(thumbnailList, n) {
|
_windowEntered : function(thumbnailList, n) {
|
||||||
if (!this._mouseActive)
|
if (!this.mouseActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._select(this._currentApp, n);
|
this._select(this._selectedIndex, n);
|
||||||
},
|
|
||||||
|
|
||||||
_disableHover : function() {
|
|
||||||
this._mouseActive = false;
|
|
||||||
|
|
||||||
if (this._motionTimeoutId != 0)
|
|
||||||
Mainloop.source_remove(this._motionTimeoutId);
|
|
||||||
|
|
||||||
this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
|
|
||||||
},
|
|
||||||
|
|
||||||
_mouseTimedOut : function() {
|
|
||||||
this._motionTimeoutId = 0;
|
|
||||||
this._mouseActive = true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_finish : function() {
|
_finish : function() {
|
||||||
let app = this._appIcons[this._currentApp];
|
this.parent();
|
||||||
if (this._currentWindow >= 0) {
|
|
||||||
Main.activateWindow(app.cachedWindows[this._currentWindow]);
|
|
||||||
} else {
|
|
||||||
app.app.activate_window(null, global.get_current_time());
|
|
||||||
}
|
|
||||||
this.destroy();
|
|
||||||
},
|
|
||||||
|
|
||||||
_popModal: function() {
|
let appIcon = this._items[this._selectedIndex];
|
||||||
if (this._haveModal) {
|
let window;
|
||||||
Main.popModal(this.actor);
|
if (this._currentWindow >= 0)
|
||||||
this._haveModal = false;
|
window = appIcon.cachedWindows[this._currentWindow];
|
||||||
}
|
else
|
||||||
},
|
window = null;
|
||||||
|
appIcon.app.activate_window(window, global.get_current_time());
|
||||||
destroy : function() {
|
|
||||||
this._popModal();
|
|
||||||
if (this.actor.visible) {
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 0,
|
|
||||||
time: POPUP_FADE_OUT_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this,
|
|
||||||
function() {
|
|
||||||
this.actor.destroy();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
this.actor.destroy();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDestroy : function() {
|
_onDestroy : function() {
|
||||||
this._popModal();
|
this.parent();
|
||||||
|
|
||||||
if (this._thumbnails)
|
if (this._thumbnails)
|
||||||
this._destroyThumbnails();
|
this._destroyThumbnails();
|
||||||
|
|
||||||
if (this._motionTimeoutId != 0)
|
|
||||||
Mainloop.source_remove(this._motionTimeoutId);
|
|
||||||
if (this._thumbnailTimeoutId != 0)
|
if (this._thumbnailTimeoutId != 0)
|
||||||
Mainloop.source_remove(this._thumbnailTimeoutId);
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
||||||
if (this._initialDelayTimeoutId != 0)
|
|
||||||
Mainloop.source_remove(this._initialDelayTimeoutId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,7 +283,7 @@ const AltTabPopup = new Lang.Class({
|
|||||||
* app list will have the keyboard focus.
|
* app list will have the keyboard focus.
|
||||||
*/
|
*/
|
||||||
_select : function(app, window, forceAppFocus) {
|
_select : function(app, window, forceAppFocus) {
|
||||||
if (app != this._currentApp || window == null) {
|
if (app != this._selectedIndex || window == null) {
|
||||||
if (this._thumbnails)
|
if (this._thumbnails)
|
||||||
this._destroyThumbnails();
|
this._destroyThumbnails();
|
||||||
}
|
}
|
||||||
@ -478,16 +295,16 @@ const AltTabPopup = new Lang.Class({
|
|||||||
|
|
||||||
this._thumbnailsFocused = (window != null) && !forceAppFocus;
|
this._thumbnailsFocused = (window != null) && !forceAppFocus;
|
||||||
|
|
||||||
this._currentApp = app;
|
this._selectedIndex = app;
|
||||||
this._currentWindow = window ? window : -1;
|
this._currentWindow = window ? window : -1;
|
||||||
this._appSwitcher.highlight(app, this._thumbnailsFocused);
|
this._switcherList.highlight(app, this._thumbnailsFocused);
|
||||||
|
|
||||||
if (window != null) {
|
if (window != null) {
|
||||||
if (!this._thumbnails)
|
if (!this._thumbnails)
|
||||||
this._createThumbnails();
|
this._createThumbnails();
|
||||||
this._currentWindow = window;
|
this._currentWindow = window;
|
||||||
this._thumbnails.highlight(window, forceAppFocus);
|
this._thumbnails.highlight(window, forceAppFocus);
|
||||||
} else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
|
} else if (this._items[this._selectedIndex].cachedWindows.length > 1 &&
|
||||||
!forceAppFocus) {
|
!forceAppFocus) {
|
||||||
this._thumbnailTimeoutId = Mainloop.timeout_add (
|
this._thumbnailTimeoutId = Mainloop.timeout_add (
|
||||||
THUMBNAIL_POPUP_TIME,
|
THUMBNAIL_POPUP_TIME,
|
||||||
@ -515,11 +332,11 @@ const AltTabPopup = new Lang.Class({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
this._thumbnails = null;
|
this._thumbnails = null;
|
||||||
this._appSwitcher._items[this._currentApp].remove_accessible_state (Atk.StateType.EXPANDED);
|
this._switcherList._items[this._selectedIndex].remove_accessible_state (Atk.StateType.EXPANDED);
|
||||||
},
|
},
|
||||||
|
|
||||||
_createThumbnails : function() {
|
_createThumbnails : function() {
|
||||||
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows);
|
this._thumbnails = new ThumbnailList (this._items[this._selectedIndex].cachedWindows);
|
||||||
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
|
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
|
||||||
this._thumbnails.connect('item-entered', Lang.bind(this, this._windowEntered));
|
this._thumbnails.connect('item-entered', Lang.bind(this, this._windowEntered));
|
||||||
|
|
||||||
@ -537,314 +354,10 @@ const AltTabPopup = new Lang.Class({
|
|||||||
onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
|
onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
|
||||||
});
|
});
|
||||||
|
|
||||||
this._appSwitcher._items[this._currentApp].add_accessible_state (Atk.StateType.EXPANDED);
|
this._switcherList._items[this._selectedIndex].add_accessible_state (Atk.StateType.EXPANDED);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const SwitcherList = new Lang.Class({
|
|
||||||
Name: 'SwitcherList',
|
|
||||||
|
|
||||||
_init : function(squareItems) {
|
|
||||||
this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
|
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
||||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
||||||
this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
|
|
||||||
|
|
||||||
// Here we use a GenericContainer so that we can force all the
|
|
||||||
// children except the separator to have the same width.
|
|
||||||
this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
|
|
||||||
this._list.spacing = 0;
|
|
||||||
this._list.connect('style-changed', Lang.bind(this, function() {
|
|
||||||
this._list.spacing = this._list.get_theme_node().get_length('spacing');
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
||||||
this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
||||||
this._list.connect('allocate', Lang.bind(this, this._allocate));
|
|
||||||
|
|
||||||
this._scrollView = new St.ScrollView({ style_class: 'hfade',
|
|
||||||
enable_mouse_scrolling: false });
|
|
||||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
|
|
||||||
|
|
||||||
let scrollBox = new St.BoxLayout();
|
|
||||||
scrollBox.add_actor(this._list);
|
|
||||||
this._scrollView.add_actor(scrollBox);
|
|
||||||
this.actor.add_actor(this._scrollView);
|
|
||||||
|
|
||||||
// Those arrows indicate whether scrolling in one direction is possible
|
|
||||||
this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
|
||||||
pseudo_class: 'highlighted' });
|
|
||||||
this._leftArrow.connect('repaint', Lang.bind(this,
|
|
||||||
function() { _drawArrow(this._leftArrow, St.Side.LEFT); }));
|
|
||||||
this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
|
||||||
pseudo_class: 'highlighted' });
|
|
||||||
this._rightArrow.connect('repaint', Lang.bind(this,
|
|
||||||
function() { _drawArrow(this._rightArrow, St.Side.RIGHT); }));
|
|
||||||
|
|
||||||
this.actor.add_actor(this._leftArrow);
|
|
||||||
this.actor.add_actor(this._rightArrow);
|
|
||||||
|
|
||||||
this._items = [];
|
|
||||||
this._highlighted = -1;
|
|
||||||
this._separator = null;
|
|
||||||
this._squareItems = squareItems;
|
|
||||||
this._minSize = 0;
|
|
||||||
this._scrollableRight = true;
|
|
||||||
this._scrollableLeft = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_allocateTop: function(actor, box, flags) {
|
|
||||||
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 : function(item, label) {
|
|
||||||
let bbox = new St.Button({ style_class: 'item-box',
|
|
||||||
reactive: true });
|
|
||||||
|
|
||||||
bbox.set_child(item);
|
|
||||||
this._list.add_actor(bbox);
|
|
||||||
|
|
||||||
let n = this._items.length;
|
|
||||||
bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
|
|
||||||
bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
|
|
||||||
|
|
||||||
bbox.label_actor = label;
|
|
||||||
|
|
||||||
this._items.push(bbox);
|
|
||||||
|
|
||||||
return bbox;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onItemClicked: function (index) {
|
|
||||||
this._itemActivated(index);
|
|
||||||
},
|
|
||||||
|
|
||||||
_onItemEnter: function (index) {
|
|
||||||
this._itemEntered(index);
|
|
||||||
},
|
|
||||||
|
|
||||||
addSeparator: function () {
|
|
||||||
let box = new St.Bin({ style_class: 'separator' });
|
|
||||||
this._separator = box;
|
|
||||||
this._list.add_actor(box);
|
|
||||||
},
|
|
||||||
|
|
||||||
highlight: function(index, justOutline) {
|
|
||||||
if (this._highlighted != -1) {
|
|
||||||
this._items[this._highlighted].remove_style_pseudo_class('outlined');
|
|
||||||
this._items[this._highlighted].remove_style_pseudo_class('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._highlighted = index;
|
|
||||||
|
|
||||||
if (this._highlighted != -1) {
|
|
||||||
if (justOutline)
|
|
||||||
this._items[this._highlighted].add_style_pseudo_class('outlined');
|
|
||||||
else
|
|
||||||
this._items[this._highlighted].add_style_pseudo_class('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
let adjustment = this._scrollView.hscroll.adjustment;
|
|
||||||
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
|
||||||
let [absItemX, absItemY] = this._items[index].get_transformed_position();
|
|
||||||
let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
|
|
||||||
let [containerWidth, containerHeight] = this.actor.get_transformed_size();
|
|
||||||
if (posX + this._items[index].get_width() > containerWidth)
|
|
||||||
this._scrollToRight();
|
|
||||||
else if (this._items[index].allocation.x1 - value < 0)
|
|
||||||
this._scrollToLeft();
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
_scrollToLeft : function() {
|
|
||||||
let adjustment = this._scrollView.hscroll.adjustment;
|
|
||||||
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
|
||||||
|
|
||||||
let item = this._items[this._highlighted];
|
|
||||||
|
|
||||||
if (item.allocation.x1 < value)
|
|
||||||
value = Math.min(0, item.allocation.x1);
|
|
||||||
else if (item.allocation.x2 > value + pageSize)
|
|
||||||
value = Math.max(upper, item.allocation.x2 - pageSize);
|
|
||||||
|
|
||||||
this._scrollableRight = true;
|
|
||||||
Tweener.addTween(adjustment,
|
|
||||||
{ value: value,
|
|
||||||
time: POPUP_SCROLL_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this, function () {
|
|
||||||
if (this._highlighted == 0) {
|
|
||||||
this._scrollableLeft = false;
|
|
||||||
this.actor.queue_relayout();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_scrollToRight : function() {
|
|
||||||
let adjustment = this._scrollView.hscroll.adjustment;
|
|
||||||
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
|
||||||
|
|
||||||
let item = this._items[this._highlighted];
|
|
||||||
|
|
||||||
if (item.allocation.x1 < value)
|
|
||||||
value = Math.max(0, item.allocation.x1);
|
|
||||||
else if (item.allocation.x2 > value + pageSize)
|
|
||||||
value = Math.min(upper, item.allocation.x2 - pageSize);
|
|
||||||
|
|
||||||
this._scrollableLeft = true;
|
|
||||||
Tweener.addTween(adjustment,
|
|
||||||
{ value: value,
|
|
||||||
time: POPUP_SCROLL_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this, function () {
|
|
||||||
if (this._highlighted == this._items.length - 1) {
|
|
||||||
this._scrollableRight = false;
|
|
||||||
this.actor.queue_relayout();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_itemActivated: function(n) {
|
|
||||||
this.emit('item-activated', n);
|
|
||||||
},
|
|
||||||
|
|
||||||
_itemEntered: function(n) {
|
|
||||||
this.emit('item-entered', n);
|
|
||||||
},
|
|
||||||
|
|
||||||
_maxChildWidth: function (forHeight) {
|
|
||||||
let maxChildMin = 0;
|
|
||||||
let maxChildNat = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < this._items.length; i++) {
|
|
||||||
let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
|
|
||||||
maxChildMin = Math.max(childMin, maxChildMin);
|
|
||||||
maxChildNat = Math.max(childNat, maxChildNat);
|
|
||||||
|
|
||||||
if (this._squareItems) {
|
|
||||||
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
|
||||||
maxChildMin = Math.max(childMin, maxChildMin);
|
|
||||||
maxChildNat = Math.max(childNat, maxChildNat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [maxChildMin, maxChildNat];
|
|
||||||
},
|
|
||||||
|
|
||||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
|
||||||
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
|
|
||||||
|
|
||||||
let separatorWidth = 0;
|
|
||||||
if (this._separator) {
|
|
||||||
let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
|
|
||||||
separatorWidth = sepNat + this._list.spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
||||||
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
|
|
||||||
alloc.natural_size = alloc.min_size;
|
|
||||||
this._minSize = alloc.min_size;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
|
||||||
let maxChildMin = 0;
|
|
||||||
let maxChildNat = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < this._items.length; i++) {
|
|
||||||
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
|
||||||
maxChildMin = Math.max(childMin, maxChildMin);
|
|
||||||
maxChildNat = Math.max(childNat, maxChildNat);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._squareItems) {
|
|
||||||
let [childMin, childNat] = this._maxChildWidth(-1);
|
|
||||||
maxChildMin = Math.max(childMin, maxChildMin);
|
|
||||||
maxChildNat = maxChildMin;
|
|
||||||
}
|
|
||||||
|
|
||||||
alloc.min_size = maxChildMin;
|
|
||||||
alloc.natural_size = maxChildNat;
|
|
||||||
},
|
|
||||||
|
|
||||||
_allocate: function (actor, box, flags) {
|
|
||||||
let childHeight = box.y2 - box.y1;
|
|
||||||
|
|
||||||
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
|
|
||||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
||||||
|
|
||||||
let separatorWidth = 0;
|
|
||||||
if (this._separator) {
|
|
||||||
let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
|
|
||||||
separatorWidth = sepNat;
|
|
||||||
totalSpacing += this._list.spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
|
|
||||||
|
|
||||||
let x = 0;
|
|
||||||
let children = this._list.get_children();
|
|
||||||
let childBox = new Clutter.ActorBox();
|
|
||||||
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
|
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
if (this._items.indexOf(children[i]) != -1) {
|
|
||||||
let [childMin, childNat] = children[i].get_preferred_height(childWidth);
|
|
||||||
let vSpacing = (childHeight - childNat) / 2;
|
|
||||||
childBox.x1 = x;
|
|
||||||
childBox.y1 = vSpacing;
|
|
||||||
childBox.x2 = x + childWidth;
|
|
||||||
childBox.y2 = childBox.y1 + childNat;
|
|
||||||
children[i].allocate(childBox, flags);
|
|
||||||
|
|
||||||
x += this._list.spacing + childWidth;
|
|
||||||
} else if (children[i] == this._separator) {
|
|
||||||
// We want the separator to be more compact than the rest.
|
|
||||||
childBox.x1 = x;
|
|
||||||
childBox.y1 = 0;
|
|
||||||
childBox.x2 = x + separatorWidth;
|
|
||||||
childBox.y2 = childHeight;
|
|
||||||
children[i].allocate(childBox, flags);
|
|
||||||
x += this._list.spacing + separatorWidth;
|
|
||||||
} else {
|
|
||||||
// Something else, eg, AppSwitcher's arrows;
|
|
||||||
// we don't allocate it.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Signals.addSignalMethods(SwitcherList.prototype);
|
|
||||||
|
|
||||||
const AppIcon = new Lang.Class({
|
const AppIcon = new Lang.Class({
|
||||||
Name: 'AppIcon',
|
Name: 'AppIcon',
|
||||||
|
|
||||||
@ -869,7 +382,7 @@ const AppIcon = new Lang.Class({
|
|||||||
|
|
||||||
const AppSwitcher = new Lang.Class({
|
const AppSwitcher = new Lang.Class({
|
||||||
Name: 'AppSwitcher',
|
Name: 'AppSwitcher',
|
||||||
Extends: SwitcherList,
|
Extends: SwitcherPopup.SwitcherList,
|
||||||
|
|
||||||
_init : function(localApps, otherApps, altTabPopup) {
|
_init : function(localApps, otherApps, altTabPopup) {
|
||||||
this.parent(true);
|
this.parent(true);
|
||||||
@ -1023,7 +536,7 @@ const 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', function() { _drawArrow(arrow, St.Side.BOTTOM); });
|
arrow.connect('repaint', function() { SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM); });
|
||||||
this._list.add_actor(arrow);
|
this._list.add_actor(arrow);
|
||||||
this._arrows.push(arrow);
|
this._arrows.push(arrow);
|
||||||
|
|
||||||
@ -1036,7 +549,7 @@ const AppSwitcher = new Lang.Class({
|
|||||||
|
|
||||||
const ThumbnailList = new Lang.Class({
|
const ThumbnailList = new Lang.Class({
|
||||||
Name: 'ThumbnailList',
|
Name: 'ThumbnailList',
|
||||||
Extends: SwitcherList,
|
Extends: SwitcherPopup.SwitcherList,
|
||||||
|
|
||||||
_init : function(windows) {
|
_init : function(windows) {
|
||||||
this.parent(false);
|
this.parent(false);
|
||||||
@ -1119,45 +632,3 @@ const ThumbnailList = new Lang.Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function _drawArrow(area, side) {
|
|
||||||
let themeNode = area.get_theme_node();
|
|
||||||
let borderColor = themeNode.get_border_color(side);
|
|
||||||
let bodyColor = themeNode.get_foreground_color();
|
|
||||||
|
|
||||||
let [width, height] = area.get_surface_size ();
|
|
||||||
let cr = area.get_context();
|
|
||||||
|
|
||||||
cr.setLineWidth(1.0);
|
|
||||||
Clutter.cairo_set_source_color(cr, borderColor);
|
|
||||||
|
|
||||||
switch (side) {
|
|
||||||
case St.Side.TOP:
|
|
||||||
cr.moveTo(0, height);
|
|
||||||
cr.lineTo(Math.floor(width * 0.5), 0);
|
|
||||||
cr.lineTo(width, height);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case St.Side.BOTTOM:
|
|
||||||
cr.moveTo(width, 0);
|
|
||||||
cr.lineTo(Math.floor(width * 0.5), height);
|
|
||||||
cr.lineTo(0, 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case St.Side.LEFT:
|
|
||||||
cr.moveTo(width, height);
|
|
||||||
cr.lineTo(0, Math.floor(height * 0.5));
|
|
||||||
cr.lineTo(width, 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case St.Side.RIGHT:
|
|
||||||
cr.moveTo(0, 0);
|
|
||||||
cr.lineTo(width, Math.floor(height * 0.5));
|
|
||||||
cr.lineTo(0, height);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cr.strokePreserve();
|
|
||||||
|
|
||||||
Clutter.cairo_set_source_color(cr, bodyColor);
|
|
||||||
cr.fill();
|
|
||||||
}
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gdk = imports.gi.Gdk;
|
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
const AltTab = imports.ui.altTab;
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const SwitcherPopup = imports.ui.switcherPopup;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
return a.x - b.x;
|
return a.x - b.x;
|
||||||
},
|
},
|
||||||
|
|
||||||
popup: function(backwards, mask) {
|
popup: function(backward, binding, mask) {
|
||||||
// Start with the set of focus groups that are currently mapped
|
// Start with the set of focus groups that are currently mapped
|
||||||
let items = this._items.filter(function (item) { return item.proxy.mapped; });
|
let items = this._items.filter(function (item) { return item.proxy.mapped; });
|
||||||
|
|
||||||
@ -123,8 +122,8 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
items.sort(Lang.bind(this, this._sortItems));
|
items.sort(Lang.bind(this, this._sortItems));
|
||||||
|
|
||||||
if (!this._popup) {
|
if (!this._popup) {
|
||||||
this._popup = new CtrlAltTabPopup();
|
this._popup = new CtrlAltTabPopup(items);
|
||||||
this._popup.show(items, backwards, mask);
|
this._popup.show(backward, binding, mask);
|
||||||
|
|
||||||
this._popup.actor.connect('destroy',
|
this._popup.actor.connect('destroy',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
@ -134,176 +133,46 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function mod(a, b) {
|
|
||||||
return (a + b) % b;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CtrlAltTabPopup = new Lang.Class({
|
const CtrlAltTabPopup = new Lang.Class({
|
||||||
Name: 'CtrlAltTabPopup',
|
Name: 'CtrlAltTabPopup',
|
||||||
|
Extends: SwitcherPopup.SwitcherPopup,
|
||||||
|
|
||||||
_init : function() {
|
_createSwitcher: function() {
|
||||||
this.actor = new Shell.GenericContainer({ name: 'ctrlAltTabPopup',
|
this._switcherList = new CtrlAltTabSwitcher(this._items);
|
||||||
reactive: true });
|
return true;
|
||||||
|
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
||||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
||||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
||||||
|
|
||||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
||||||
|
|
||||||
this._haveModal = false;
|
|
||||||
this._modifierMask = 0;
|
|
||||||
this._selection = 0;
|
|
||||||
|
|
||||||
Main.uiGroup.add_actor(this.actor);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
_initialSelection: function(backward, binding) {
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
if (binding == 'switch-panels') {
|
||||||
|
if (backward)
|
||||||
alloc.min_size = primary.width;
|
this._selectedIndex = this._items.length - 1;
|
||||||
alloc.natural_size = primary.width;
|
} else if (binding == 'switch-panels-backward') {
|
||||||
},
|
if (!backward)
|
||||||
|
this._selectedIndex = this._items.length - 1;
|
||||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
|
|
||||||
alloc.min_size = primary.height;
|
|
||||||
alloc.natural_size = primary.height;
|
|
||||||
},
|
|
||||||
|
|
||||||
_allocate: function (actor, box, flags) {
|
|
||||||
let childBox = new Clutter.ActorBox();
|
|
||||||
let primary = Main.layoutManager.primaryMonitor;
|
|
||||||
|
|
||||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
|
||||||
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
|
||||||
let hPadding = this.actor.get_theme_node().get_horizontal_padding();
|
|
||||||
|
|
||||||
let [childMinHeight, childNaturalHeight] = this._switcher.actor.get_preferred_height(primary.width - hPadding);
|
|
||||||
let [childMinWidth, childNaturalWidth] = this._switcher.actor.get_preferred_width(childNaturalHeight);
|
|
||||||
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
|
||||||
childBox.x2 = Math.min(primary.x + primary.width - hPadding, childBox.x1 + childNaturalWidth);
|
|
||||||
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
|
||||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
|
||||||
this._switcher.actor.allocate(childBox, flags);
|
|
||||||
},
|
|
||||||
|
|
||||||
show : function(items, startBackwards, mask) {
|
|
||||||
if (!Main.pushModal(this.actor))
|
|
||||||
return false;
|
|
||||||
this._haveModal = true;
|
|
||||||
this._modifierMask = AltTab.primaryModifier(mask);
|
|
||||||
|
|
||||||
this._keyPressEventId = this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
|
||||||
this._keyReleaseEventId = this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
|
||||||
|
|
||||||
this._items = items;
|
|
||||||
this._switcher = new CtrlAltTabSwitcher(items);
|
|
||||||
this.actor.add_actor(this._switcher.actor);
|
|
||||||
|
|
||||||
if (startBackwards)
|
|
||||||
this._selection = this._items.length - 1;
|
|
||||||
this._select(this._selection);
|
|
||||||
|
|
||||||
let [x, y, mods] = global.get_pointer();
|
|
||||||
if (!(mods & this._modifierMask)) {
|
|
||||||
this._finish();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
this._select(this._selectedIndex);
|
||||||
this.actor.opacity = 0;
|
|
||||||
this.actor.show();
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 255,
|
|
||||||
time: POPUP_FADE_TIME,
|
|
||||||
transition: 'easeOutQuad'
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_next : function() {
|
_keyPressHandler: function(keysym, backwards, action) {
|
||||||
return mod(this._selection + 1, this._items.length);
|
if (action == Meta.KeyBindingAction.SWITCH_PANELS)
|
||||||
},
|
this._select(backwards ? this._previous() : this._next());
|
||||||
|
else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
|
||||||
_previous : function() {
|
this._select(backwards ? this._next() : this._previous());
|
||||||
return mod(this._selection - 1, this._items.length);
|
else if (keysym == Clutter.Left)
|
||||||
},
|
|
||||||
|
|
||||||
_keyPressEvent : function(actor, event) {
|
|
||||||
let keysym = event.get_key_symbol();
|
|
||||||
let shift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK);
|
|
||||||
if (shift && keysym == Clutter.KEY_Tab)
|
|
||||||
keysym = Clutter.ISO_Left_Tab;
|
|
||||||
|
|
||||||
if (keysym == Clutter.KEY_Escape)
|
|
||||||
this.destroy();
|
|
||||||
else if (keysym == Clutter.KEY_Tab)
|
|
||||||
this._select(this._next());
|
|
||||||
else if (keysym == Clutter.KEY_ISO_Left_Tab)
|
|
||||||
this._select(this._previous());
|
this._select(this._previous());
|
||||||
else if (keysym == Clutter.KEY_Left)
|
else if (keysym == Clutter.Right)
|
||||||
this._select(this._previous());
|
|
||||||
else if (keysym == Clutter.KEY_Right)
|
|
||||||
this._select(this._next());
|
this._select(this._next());
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_keyReleaseEvent : function(actor, event) {
|
|
||||||
let [x, y, mods] = global.get_pointer();
|
|
||||||
let state = mods & this._modifierMask;
|
|
||||||
|
|
||||||
if (state == 0)
|
|
||||||
this._finish();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_finish : function() {
|
_finish : function() {
|
||||||
this.destroy();
|
this.parent();
|
||||||
|
Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex]);
|
||||||
Main.ctrlAltTabManager.focusGroup(this._items[this._selection]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_popModal: function() {
|
|
||||||
if (this._haveModal) {
|
|
||||||
Main.popModal(this.actor);
|
|
||||||
this._haveModal = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy : function() {
|
|
||||||
this._popModal();
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 0,
|
|
||||||
time: POPUP_FADE_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this,
|
|
||||||
function() {
|
|
||||||
this.actor.destroy();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onDestroy : function() {
|
|
||||||
this._popModal();
|
|
||||||
if (this._keyPressEventId)
|
|
||||||
this.actor.disconnect(this._keyPressEventId);
|
|
||||||
if (this._keyReleaseEventId)
|
|
||||||
this.actor.disconnect(this._keyReleaseEventId);
|
|
||||||
},
|
|
||||||
|
|
||||||
_select : function(num) {
|
|
||||||
this._selection = num;
|
|
||||||
this._switcher.highlight(num);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CtrlAltTabSwitcher = new Lang.Class({
|
const CtrlAltTabSwitcher = new Lang.Class({
|
||||||
Name: 'CtrlAltTabSwitcher',
|
Name: 'CtrlAltTabSwitcher',
|
||||||
Extends: AltTab.SwitcherList,
|
Extends: SwitcherPopup.SwitcherList,
|
||||||
|
|
||||||
_init : function(items) {
|
_init : function(items) {
|
||||||
this.parent(true);
|
this.parent(true);
|
||||||
|
640
js/ui/switcherPopup.js
Normal file
640
js/ui/switcherPopup.js
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
|
const POPUP_DELAY_TIMEOUT = 150; // milliseconds
|
||||||
|
|
||||||
|
const POPUP_SCROLL_TIME = 0.10; // seconds
|
||||||
|
const POPUP_FADE_OUT_TIME = 0.1; // seconds
|
||||||
|
|
||||||
|
const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
|
||||||
|
|
||||||
|
function mod(a, b) {
|
||||||
|
return (a + b) % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function primaryModifier(mask) {
|
||||||
|
if (mask == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
let primary = 1;
|
||||||
|
while (mask > 1) {
|
||||||
|
mask >>= 1;
|
||||||
|
primary <<= 1;
|
||||||
|
}
|
||||||
|
return primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SwitcherPopup = new Lang.Class({
|
||||||
|
Name: 'SwitcherPopup',
|
||||||
|
Abstract: true,
|
||||||
|
|
||||||
|
_init: function(items) {
|
||||||
|
this._switcherList = null;
|
||||||
|
|
||||||
|
this._items = items || [];
|
||||||
|
this._selectedIndex = 0;
|
||||||
|
|
||||||
|
this.actor = new Shell.GenericContainer({ style_class: 'switcher-popup',
|
||||||
|
reactive: true });
|
||||||
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||||
|
|
||||||
|
Main.uiGroup.add_actor(this.actor);
|
||||||
|
|
||||||
|
this._haveModal = false;
|
||||||
|
this._modifierMask = 0;
|
||||||
|
|
||||||
|
this._motionTimeoutId = 0;
|
||||||
|
this._initialDelayTimeoutId = 0;
|
||||||
|
|
||||||
|
// Initially disable hover so we ignore the enter-event if
|
||||||
|
// the switcher appears underneath the current pointer location
|
||||||
|
this._disableHover();
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
alloc.min_size = primary.width;
|
||||||
|
alloc.natural_size = primary.width;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
alloc.min_size = primary.height;
|
||||||
|
alloc.natural_size = primary.height;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocate: function(actor, box, flags) {
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
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 bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||||
|
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
||||||
|
let hPadding = leftPadding + rightPadding;
|
||||||
|
|
||||||
|
// Allocate the switcherList
|
||||||
|
// 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 [childMinWidth, childNaturalWidth] = this._switcherList.actor.get_preferred_width(childNaturalHeight);
|
||||||
|
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.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
||||||
|
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||||
|
this._switcherList.actor.allocate(childBox, flags);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createSwitcher: function() {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
_initialSelection: function(backward, binding) {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(backward, binding, mask) {
|
||||||
|
if (!this._createSwitcher())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!Main.pushModal(this.actor)) {
|
||||||
|
// Probably someone else has a pointer grab, try again with keyboard only
|
||||||
|
if (!Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED })) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._haveModal = true;
|
||||||
|
this._modifierMask = primaryModifier(mask);
|
||||||
|
|
||||||
|
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||||
|
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||||
|
|
||||||
|
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
|
||||||
|
this.actor.connect('scroll-event', Lang.bind(this, this._scrollEvent));
|
||||||
|
|
||||||
|
this.actor.add_actor(this._switcherList.actor);
|
||||||
|
this._switcherList.connect('item-activated', Lang.bind(this, this._itemActivated));
|
||||||
|
this._switcherList.connect('item-entered', Lang.bind(this, this._itemEntered));
|
||||||
|
|
||||||
|
// Need to force an allocation so we can figure out whether we
|
||||||
|
// need to scroll when selecting
|
||||||
|
this.actor.opacity = 0;
|
||||||
|
this.actor.show();
|
||||||
|
this.actor.get_allocation_box();
|
||||||
|
|
||||||
|
if (this._items.length > 1)
|
||||||
|
this._selectedIndex = 1;
|
||||||
|
else
|
||||||
|
this._selectedIndex = 0;
|
||||||
|
|
||||||
|
this._initialSelection(backward, binding);
|
||||||
|
|
||||||
|
// There's a race condition; if the user released Alt before
|
||||||
|
// we got the grab, then we won't be notified. (See
|
||||||
|
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
|
||||||
|
// details.) So we check now. (Have to do this after updating
|
||||||
|
// selection.)
|
||||||
|
let [x, y, mods] = global.get_pointer();
|
||||||
|
if (!(mods & this._modifierMask)) {
|
||||||
|
this._finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We delay showing the popup so that fast Alt+Tab users aren't
|
||||||
|
// disturbed by the popup briefly flashing.
|
||||||
|
this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
|
||||||
|
Lang.bind(this, function () {
|
||||||
|
this.actor.opacity = 255;
|
||||||
|
this._initialDelayTimeoutId = 0;
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_next: function() {
|
||||||
|
return mod(this._selectedIndex + 1, this._items.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
_previous: function() {
|
||||||
|
return mod(this._selectedIndex - 1, this._items.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyPressHandler: function(keysym, backwards, action) {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyPressEvent: function(actor, event) {
|
||||||
|
let keysym = event.get_key_symbol();
|
||||||
|
let event_state = event.get_state();
|
||||||
|
let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
|
||||||
|
let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
|
||||||
|
|
||||||
|
this._disableHover();
|
||||||
|
|
||||||
|
if (keysym == Clutter.Escape)
|
||||||
|
this.destroy();
|
||||||
|
else
|
||||||
|
this._keyPressHandler(keysym, backwards, action);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_keyReleaseEvent: function(actor, event) {
|
||||||
|
let [x, y, mods] = global.get_pointer();
|
||||||
|
let state = mods & this._modifierMask;
|
||||||
|
|
||||||
|
if (state == 0)
|
||||||
|
this._finish();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_clickedOutside: function(actor, event) {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollHandler: function(direction) {
|
||||||
|
if (direction == Clutter.ScrollDirection.UP)
|
||||||
|
this._select(this._previous());
|
||||||
|
else if (direction == Clutter.ScrollDirection.DOWN)
|
||||||
|
this._select(this._next());
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollEvent: function(actor, event) {
|
||||||
|
this._scrollHandler(event.get_scroll_direction());
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemActivatedHandler: function(n) {
|
||||||
|
this._select(n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemActivated: function(switcher, n) {
|
||||||
|
this._itemActivatedHandler(n);
|
||||||
|
this._finish();
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemEnteredHandler: function(n) {
|
||||||
|
this._select(n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemEntered: function(switcher, n) {
|
||||||
|
if (!this.mouseActive)
|
||||||
|
return;
|
||||||
|
this._itemEnteredHandler(n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_disableHover: function() {
|
||||||
|
this.mouseActive = false;
|
||||||
|
|
||||||
|
if (this._motionTimeoutId != 0)
|
||||||
|
Mainloop.source_remove(this._motionTimeoutId);
|
||||||
|
|
||||||
|
this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseTimedOut: function() {
|
||||||
|
this._motionTimeoutId = 0;
|
||||||
|
this.mouseActive = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_popModal: function() {
|
||||||
|
if (this._haveModal) {
|
||||||
|
Main.popModal(this.actor);
|
||||||
|
this._haveModal = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this._popModal();
|
||||||
|
if (this.actor.visible) {
|
||||||
|
Tweener.addTween(this.actor,
|
||||||
|
{ opacity: 0,
|
||||||
|
time: POPUP_FADE_OUT_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
this.actor.destroy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
this.actor.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_finish: function() {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDestroy: function() {
|
||||||
|
this._popModal();
|
||||||
|
|
||||||
|
if (this._motionTimeoutId != 0)
|
||||||
|
Mainloop.source_remove(this._motionTimeoutId);
|
||||||
|
if (this._initialDelayTimeoutId != 0)
|
||||||
|
Mainloop.source_remove(this._initialDelayTimeoutId);
|
||||||
|
},
|
||||||
|
|
||||||
|
_select: function(num) {
|
||||||
|
this._selectedIndex = num;
|
||||||
|
this._switcherList.highlight(num);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SwitcherList = new Lang.Class({
|
||||||
|
Name: 'SwitcherList',
|
||||||
|
|
||||||
|
_init : function(squareItems) {
|
||||||
|
this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
|
||||||
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
|
this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
|
||||||
|
|
||||||
|
// Here we use a GenericContainer so that we can force all the
|
||||||
|
// children except the separator to have the same width.
|
||||||
|
this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
|
||||||
|
this._list.spacing = 0;
|
||||||
|
this._list.connect('style-changed', Lang.bind(this, function() {
|
||||||
|
this._list.spacing = this._list.get_theme_node().get_length('spacing');
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
|
this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
|
this._list.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
|
|
||||||
|
this._scrollView = new St.ScrollView({ style_class: 'hfade',
|
||||||
|
enable_mouse_scrolling: false });
|
||||||
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
|
||||||
|
|
||||||
|
let scrollBox = new St.BoxLayout();
|
||||||
|
scrollBox.add_actor(this._list);
|
||||||
|
this._scrollView.add_actor(scrollBox);
|
||||||
|
this.actor.add_actor(this._scrollView);
|
||||||
|
|
||||||
|
// Those arrows indicate whether scrolling in one direction is possible
|
||||||
|
this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
||||||
|
pseudo_class: 'highlighted' });
|
||||||
|
this._leftArrow.connect('repaint', Lang.bind(this,
|
||||||
|
function() { drawArrow(this._leftArrow, St.Side.LEFT); }));
|
||||||
|
this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
||||||
|
pseudo_class: 'highlighted' });
|
||||||
|
this._rightArrow.connect('repaint', Lang.bind(this,
|
||||||
|
function() { drawArrow(this._rightArrow, St.Side.RIGHT); }));
|
||||||
|
|
||||||
|
this.actor.add_actor(this._leftArrow);
|
||||||
|
this.actor.add_actor(this._rightArrow);
|
||||||
|
|
||||||
|
this._items = [];
|
||||||
|
this._highlighted = -1;
|
||||||
|
this._separator = null;
|
||||||
|
this._squareItems = squareItems;
|
||||||
|
this._minSize = 0;
|
||||||
|
this._scrollableRight = true;
|
||||||
|
this._scrollableLeft = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocateTop: function(actor, box, flags) {
|
||||||
|
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 : function(item, label) {
|
||||||
|
let bbox = new St.Button({ style_class: 'item-box',
|
||||||
|
reactive: true });
|
||||||
|
|
||||||
|
bbox.set_child(item);
|
||||||
|
this._list.add_actor(bbox);
|
||||||
|
|
||||||
|
let n = this._items.length;
|
||||||
|
bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
|
||||||
|
bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
|
||||||
|
|
||||||
|
bbox.label_actor = label;
|
||||||
|
|
||||||
|
this._items.push(bbox);
|
||||||
|
|
||||||
|
return bbox;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onItemClicked: function (index) {
|
||||||
|
this._itemActivated(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onItemEnter: function (index) {
|
||||||
|
this._itemEntered(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
addSeparator: function () {
|
||||||
|
let box = new St.Bin({ style_class: 'separator' });
|
||||||
|
this._separator = box;
|
||||||
|
this._list.add_actor(box);
|
||||||
|
},
|
||||||
|
|
||||||
|
highlight: function(index, justOutline) {
|
||||||
|
if (this._highlighted != -1) {
|
||||||
|
this._items[this._highlighted].remove_style_pseudo_class('outlined');
|
||||||
|
this._items[this._highlighted].remove_style_pseudo_class('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._highlighted = index;
|
||||||
|
|
||||||
|
if (this._highlighted != -1) {
|
||||||
|
if (justOutline)
|
||||||
|
this._items[this._highlighted].add_style_pseudo_class('outlined');
|
||||||
|
else
|
||||||
|
this._items[this._highlighted].add_style_pseudo_class('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustment = this._scrollView.hscroll.adjustment;
|
||||||
|
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||||
|
let [absItemX, absItemY] = this._items[index].get_transformed_position();
|
||||||
|
let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
|
||||||
|
let [containerWidth, containerHeight] = this.actor.get_transformed_size();
|
||||||
|
if (posX + this._items[index].get_width() > containerWidth)
|
||||||
|
this._scrollToRight();
|
||||||
|
else if (this._items[index].allocation.x1 - value < 0)
|
||||||
|
this._scrollToLeft();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollToLeft : function() {
|
||||||
|
let adjustment = this._scrollView.hscroll.adjustment;
|
||||||
|
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||||
|
|
||||||
|
let item = this._items[this._highlighted];
|
||||||
|
|
||||||
|
if (item.allocation.x1 < value)
|
||||||
|
value = Math.min(0, item.allocation.x1);
|
||||||
|
else if (item.allocation.x2 > value + pageSize)
|
||||||
|
value = Math.max(upper, item.allocation.x2 - pageSize);
|
||||||
|
|
||||||
|
this._scrollableRight = true;
|
||||||
|
Tweener.addTween(adjustment,
|
||||||
|
{ value: value,
|
||||||
|
time: POPUP_SCROLL_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this, function () {
|
||||||
|
if (this._highlighted == 0) {
|
||||||
|
this._scrollableLeft = false;
|
||||||
|
this.actor.queue_relayout();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollToRight : function() {
|
||||||
|
let adjustment = this._scrollView.hscroll.adjustment;
|
||||||
|
let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
|
||||||
|
|
||||||
|
let item = this._items[this._highlighted];
|
||||||
|
|
||||||
|
if (item.allocation.x1 < value)
|
||||||
|
value = Math.max(0, item.allocation.x1);
|
||||||
|
else if (item.allocation.x2 > value + pageSize)
|
||||||
|
value = Math.min(upper, item.allocation.x2 - pageSize);
|
||||||
|
|
||||||
|
this._scrollableLeft = true;
|
||||||
|
Tweener.addTween(adjustment,
|
||||||
|
{ value: value,
|
||||||
|
time: POPUP_SCROLL_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this, function () {
|
||||||
|
if (this._highlighted == this._items.length - 1) {
|
||||||
|
this._scrollableRight = false;
|
||||||
|
this.actor.queue_relayout();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemActivated: function(n) {
|
||||||
|
this.emit('item-activated', n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemEntered: function(n) {
|
||||||
|
this.emit('item-entered', n);
|
||||||
|
},
|
||||||
|
|
||||||
|
_maxChildWidth: function (forHeight) {
|
||||||
|
let maxChildMin = 0;
|
||||||
|
let maxChildNat = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._items.length; i++) {
|
||||||
|
let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = Math.max(childNat, maxChildNat);
|
||||||
|
|
||||||
|
if (this._squareItems) {
|
||||||
|
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = Math.max(childNat, maxChildNat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [maxChildMin, maxChildNat];
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||||
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
|
||||||
|
|
||||||
|
let separatorWidth = 0;
|
||||||
|
if (this._separator) {
|
||||||
|
let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
|
||||||
|
separatorWidth = sepNat + this._list.spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||||
|
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
|
||||||
|
alloc.natural_size = alloc.min_size;
|
||||||
|
this._minSize = alloc.min_size;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||||
|
let maxChildMin = 0;
|
||||||
|
let maxChildNat = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._items.length; i++) {
|
||||||
|
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = Math.max(childNat, maxChildNat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._squareItems) {
|
||||||
|
let [childMin, childNat] = this._maxChildWidth(-1);
|
||||||
|
maxChildMin = Math.max(childMin, maxChildMin);
|
||||||
|
maxChildNat = maxChildMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.min_size = maxChildMin;
|
||||||
|
alloc.natural_size = maxChildNat;
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocate: function (actor, box, flags) {
|
||||||
|
let childHeight = box.y2 - box.y1;
|
||||||
|
|
||||||
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
|
||||||
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||||
|
|
||||||
|
let separatorWidth = 0;
|
||||||
|
if (this._separator) {
|
||||||
|
let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
|
||||||
|
separatorWidth = sepNat;
|
||||||
|
totalSpacing += this._list.spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let children = this._list.get_children();
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
|
||||||
|
let primary = Main.layoutManager.primaryMonitor;
|
||||||
|
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
|
||||||
|
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
if (this._items.indexOf(children[i]) != -1) {
|
||||||
|
let [childMin, childNat] = children[i].get_preferred_height(childWidth);
|
||||||
|
let vSpacing = (childHeight - childNat) / 2;
|
||||||
|
childBox.x1 = x;
|
||||||
|
childBox.y1 = vSpacing;
|
||||||
|
childBox.x2 = x + childWidth;
|
||||||
|
childBox.y2 = childBox.y1 + childNat;
|
||||||
|
children[i].allocate(childBox, flags);
|
||||||
|
|
||||||
|
x += this._list.spacing + childWidth;
|
||||||
|
} else if (children[i] == this._separator) {
|
||||||
|
// We want the separator to be more compact than the rest.
|
||||||
|
childBox.x1 = x;
|
||||||
|
childBox.y1 = 0;
|
||||||
|
childBox.x2 = x + separatorWidth;
|
||||||
|
childBox.y2 = childHeight;
|
||||||
|
children[i].allocate(childBox, flags);
|
||||||
|
x += this._list.spacing + separatorWidth;
|
||||||
|
} else {
|
||||||
|
// Something else, eg, AppSwitcher's arrows;
|
||||||
|
// we don't allocate it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(SwitcherList.prototype);
|
||||||
|
|
||||||
|
function drawArrow(area, side) {
|
||||||
|
let themeNode = area.get_theme_node();
|
||||||
|
let borderColor = themeNode.get_border_color(side);
|
||||||
|
let bodyColor = themeNode.get_foreground_color();
|
||||||
|
|
||||||
|
let [width, height] = area.get_surface_size ();
|
||||||
|
let cr = area.get_context();
|
||||||
|
|
||||||
|
cr.setLineWidth(1.0);
|
||||||
|
Clutter.cairo_set_source_color(cr, borderColor);
|
||||||
|
|
||||||
|
switch (side) {
|
||||||
|
case St.Side.TOP:
|
||||||
|
cr.moveTo(0, height);
|
||||||
|
cr.lineTo(Math.floor(width * 0.5), 0);
|
||||||
|
cr.lineTo(width, height);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case St.Side.BOTTOM:
|
||||||
|
cr.moveTo(width, 0);
|
||||||
|
cr.lineTo(Math.floor(width * 0.5), height);
|
||||||
|
cr.lineTo(0, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case St.Side.LEFT:
|
||||||
|
cr.moveTo(width, height);
|
||||||
|
cr.lineTo(0, Math.floor(height * 0.5));
|
||||||
|
cr.lineTo(width, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case St.Side.RIGHT:
|
||||||
|
cr.moveTo(0, 0);
|
||||||
|
cr.lineTo(width, Math.floor(height * 0.5));
|
||||||
|
cr.lineTo(0, height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.strokePreserve();
|
||||||
|
|
||||||
|
Clutter.cairo_set_source_color(cr, bodyColor);
|
||||||
|
cr.fill();
|
||||||
|
}
|
||||||
|
|
@ -621,7 +621,7 @@ const WindowManager = new Lang.Class({
|
|||||||
_startA11ySwitcher : function(display, screen, window, binding) {
|
_startA11ySwitcher : function(display, screen, window, binding) {
|
||||||
let modifiers = binding.get_modifiers();
|
let modifiers = binding.get_modifiers();
|
||||||
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
|
||||||
Main.ctrlAltTabManager.popup(backwards, binding.get_mask());
|
Main.ctrlAltTabManager.popup(backwards, binding.get_name(), binding.get_mask());
|
||||||
},
|
},
|
||||||
|
|
||||||
_openAppMenu : function(display, screen, window, event, binding) {
|
_openAppMenu : function(display, screen, window, event, binding) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user