gnome-shell/js/ui/windowManager.js
Florian Müllner ef8c9e0abb workspace-switcher: Switch to vertical orientation
With workspaces now being stacked vertically, the horizontal
indicators in the workspace switcher are rather odd. There are
some designs for an improved workspace switch animation, but
it may take a while to implement them, so for now just change
the orientation of the existing switcher.

https://bugzilla.gnome.org/show_bug.cgi?id=641931
2011-02-09 20:02:51 +01:00

604 lines
22 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const St = imports.gi.St;
const AltTab = imports.ui.altTab;
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const WINDOW_ANIMATION_TIME = 0.25;
const DIM_TIME = 0.500;
const UNDIM_TIME = 0.250;
var dimShader = undefined;
function getDimShader() {
if (dimShader === null)
return null;
if (!dimShader) {
let [success, source, length] = GLib.file_get_contents(global.datadir +
'/shaders/dim-window.glsl');
try {
let shader = new Clutter.Shader();
shader.set_fragment_source(source, -1);
shader.compile();
dimShader = shader;
} catch (e) {
log(e.message);
dimShader = null;
}
}
return dimShader;
}
function WindowDimmer(actor) {
this._init(actor);
}
WindowDimmer.prototype = {
_init: function(actor) {
this.actor = actor;
},
set dimFraction(fraction) {
this._dimFraction = fraction;
let shader = getDimShader();
if (!Meta.prefs_get_attach_modal_dialogs() || !shader) {
this.actor.set_shader(null);
return;
}
if (fraction > 0.01) {
this.actor.set_shader(shader);
this.actor.set_shader_param_float('height', this.actor.get_height());
this.actor.set_shader_param_float('fraction', fraction);
} else
this.actor.set_shader(null);
},
get dimFraction() {
return this._dimFraction;
},
_dimFraction: 0.0
};
function getWindowDimmer(texture) {
if (!texture._windowDimmer)
texture._windowDimmer = new WindowDimmer(texture);
return texture._windowDimmer;
}
function WindowManager() {
this._init();
}
WindowManager.prototype = {
_init : function() {
this._shellwm = global.window_manager;
this._keyBindingHandlers = [];
this._minimizing = [];
this._maximizing = [];
this._unmaximizing = [];
this._mapping = [];
this._destroying = [];
this._dimmedWindows = [];
this._switchData = null;
this._shellwm.connect('kill-switch-workspace', Lang.bind(this, this._switchWorkspaceDone));
this._shellwm.connect('kill-window-effects', Lang.bind(this, function (shellwm, actor) {
this._minimizeWindowDone(shellwm, actor);
this._maximizeWindowDone(shellwm, actor);
this._unmaximizeWindowDone(shellwm, actor);
this._mapWindowDone(shellwm, actor);
this._destroyWindowDone(shellwm, actor);
}));
this._shellwm.connect('switch-workspace', Lang.bind(this, this._switchWorkspace));
this._shellwm.connect('minimize', Lang.bind(this, this._minimizeWindow));
this._shellwm.connect('maximize', Lang.bind(this, this._maximizeWindow));
this._shellwm.connect('unmaximize', Lang.bind(this, this._unmaximizeWindow));
this._shellwm.connect('map', Lang.bind(this, this._mapWindow));
this._shellwm.connect('destroy', Lang.bind(this, this._destroyWindow));
this._workspaceSwitcherPopup = null;
this.setKeybindingHandler('switch_to_workspace_left', Lang.bind(this, this._showWorkspaceSwitcher));
this.setKeybindingHandler('switch_to_workspace_right', Lang.bind(this, this._showWorkspaceSwitcher));
this.setKeybindingHandler('switch_to_workspace_up', Lang.bind(this, this._showWorkspaceSwitcher));
this.setKeybindingHandler('switch_to_workspace_down', Lang.bind(this, this._showWorkspaceSwitcher));
this.setKeybindingHandler('switch_windows', Lang.bind(this, this._startAppSwitcher));
Main.overview.connect('showing', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._undimParentWindow(this._dimmedWindows[i], true);
}));
Main.overview.connect('hiding', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._dimParentWindow(this._dimmedWindows[i], true);
}));
},
setKeybindingHandler: function(keybinding, handler){
if (this._keyBindingHandlers[keybinding])
this._shellwm.disconnect(this._keyBindingHandlers[keybinding]);
else
this._shellwm.takeover_keybinding(keybinding);
this._keyBindingHandlers[keybinding] =
this._shellwm.connect('keybinding::' + keybinding, handler);
},
_shouldAnimate : function(actor) {
if (Main.overview.visible)
return false;
if (actor && (actor.meta_window.get_window_type() != Meta.WindowType.NORMAL))
return false;
return true;
},
_removeEffect : function(list, actor) {
let idx = list.indexOf(actor);
if (idx != -1) {
list.splice(idx, 1);
return true;
}
return false;
},
_minimizeWindow : function(shellwm, actor) {
if (!this._shouldAnimate(actor)) {
shellwm.completed_minimize(actor);
return;
}
actor.set_scale(1.0, 1.0);
actor.move_anchor_point_from_gravity(Clutter.Gravity.CENTER);
/* scale window down to 0x0.
* maybe TODO: get icon geometry passed through and move the window towards it?
*/
this._minimizing.push(actor);
let primary = global.get_primary_monitor();
let xDest = primary.x;
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
xDest += primary.width;
Tweener.addTween(actor,
{ scale_x: 0.0,
scale_y: 0.0,
x: xDest,
y: 0,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._minimizeWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._minimizeWindowOverwritten,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
},
_minimizeWindowDone : function(shellwm, actor) {
if (this._removeEffect(this._minimizing, actor)) {
Tweener.removeTweens(actor);
actor.set_scale(1.0, 1.0);
actor.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST);
shellwm.completed_minimize(actor);
}
},
_minimizeWindowOverwritten : function(shellwm, actor) {
if (this._removeEffect(this._minimizing, actor)) {
shellwm.completed_minimize(actor);
}
},
_maximizeWindow : function(shellwm, actor, targetX, targetY, targetWidth, targetHeight) {
shellwm.completed_maximize(actor);
},
_maximizeWindowDone : function(shellwm, actor) {
},
_maximizeWindowOverwrite : function(shellwm, actor) {
},
_unmaximizeWindow : function(shellwm, actor, targetX, targetY, targetWidth, targetHeight) {
shellwm.completed_unmaximize(actor);
},
_unmaximizeWindowDone : function(shellwm, actor) {
},
_parentHasOtherAttachedDialog: function(parent, self) {
var count = 0;
parent.foreach_transient(function(win) {
if (win.get_window_type() == Meta.WindowType.MODAL_DIALOG && win != self)
count++;
return false;
});
return count != 0;
},
_markParentWindowAsDimmable: function(actor, animate) {
if (Meta.prefs_get_attach_modal_dialogs()) {
this._dimmedWindows.push(actor);
if (this._shouldAnimate())
this._dimParentWindow(actor, animate);
}
},
_unmarkParentWindowAsDimmable: function(actor, animate) {
if (!Main.overview.visible)
this._undimParentWindow(actor, true);
this._dimmedWindows = this._dimmedWindows.filter(function(win) {
return win != actor;
});
},
_dimParentWindow: function(actor, animate) {
let meta = actor.get_meta_window();
let parent = meta.get_transient_for();
if (!parent)
return;
let parentActor = parent.get_compositor_private();
if (!parentActor || this._parentHasOtherAttachedDialog(parent, meta))
return;
let texture = parentActor.get_texture();
if (animate)
Tweener.addTween(getWindowDimmer(texture),
{ dimFraction: 1.0,
time: DIM_TIME,
transition: 'linear'
});
else
getWindowDimmer(texture).dimFraction = 1.0;
},
_undimParentWindow: function(actor, animate) {
let meta = actor.get_meta_window();
let parent = meta.get_transient_for();
if (!parent)
return;
let parentActor = parent.get_compositor_private();
if (!parentActor || this._parentHasOtherAttachedDialog(parent, meta))
return;
let texture = parentActor.get_texture();
if (animate)
Tweener.addTween(getWindowDimmer(texture),
{ dimFraction: 0.0,
time: UNDIM_TIME,
transition: 'linear'
});
else
getWindowDimmer(texture).dimFraction = 0.0;
},
_mapWindow : function(shellwm, actor) {
actor._windowType = actor.meta_window.get_window_type();
actor._notifyWindowTypeSignalId = actor.meta_window.connect('notify::window-type', Lang.bind(this, function () {
let type = actor.meta_window.get_window_type();
if (type == actor._windowType)
return;
if (type == Meta.WindowType.MODAL_DIALOG)
this._markParentWindowAsDimmable(actor, true);
else if (actor._windowType == Meta.WindowType.MODAL_DIALOG)
this._unmarkParentWindowAsDimmable(actor, true);
actor._windowType = type;
}));
if (actor.meta_window.get_window_type() == Meta.WindowType.MODAL_DIALOG
&& Meta.prefs_get_attach_modal_dialogs()
&& actor.get_meta_window().get_transient_for()) {
this._markParentWindowAsDimmable(actor, true);
if (this._shouldAnimate()) {
actor.set_scale(1.0, 0.0);
actor.show();
this._mapping.push(actor);
Tweener.addTween(actor,
{ scale_y: 1,
time: WINDOW_ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: this._mapWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._mapWindowOverwrite,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
return;
}
shellwm.completed_map(actor);
return;
}
if (!this._shouldAnimate(actor)) {
shellwm.completed_map(actor);
return;
}
actor.opacity = 0;
actor.show();
/* Fade window in */
this._mapping.push(actor);
Tweener.addTween(actor,
{ opacity: 255,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._mapWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._mapWindowOverwrite,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
},
_mapWindowDone : function(shellwm, actor) {
if (this._removeEffect(this._mapping, actor)) {
Tweener.removeTweens(actor);
actor.opacity = 255;
shellwm.completed_map(actor);
}
},
_mapWindowOverwrite : function(shellwm, actor) {
if (this._removeEffect(this._mapping, actor)) {
shellwm.completed_map(actor);
}
},
_destroyWindow : function(shellwm, actor) {
let parent = actor.meta_window.get_transient_for();
if (actor._notifyWindowTypeSignalId) {
actor.meta_window.disconnect(actor._notifyWindowTypeSignalId);
actor._notifyWindowTypeSignalId = 0;
}
while (actor.meta_window.get_window_type() == Meta.WindowType.MODAL_DIALOG
&& parent) {
this._unmarkParentWindowAsDimmable(actor, true);
if (!Meta.prefs_get_attach_modal_dialogs()
|| !this._shouldAnimate())
break;
actor.set_scale(1.0, 1.0);
actor.show();
this._destroying.push(actor);
actor._parentDestroyId = parent.connect('unmanaged', Lang.bind(this, function () {
Tweener.removeTweens(actor);
this._destroyWindowDone(shellwm, actor);
}));
Tweener.addTween(actor,
{ scale_y: 0,
time: WINDOW_ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: this._destroyWindowDone,
onCompleteScope: this,
onCompleteParams: [shellwm, actor],
onOverwrite: this._destroyWindowDone,
onOverwriteScope: this,
onOverwriteParams: [shellwm, actor]
});
return;
}
shellwm.completed_destroy(actor);
},
_destroyWindowDone : function(shellwm, actor) {
if (this._removeEffect(this._destroying, actor)) {
let parent = actor.get_meta_window().get_transient_for();
if (parent && actor._parentDestroyId) {
parent.disconnect(actor._parentDestroyId);
actor._parentDestroyId = 0;
}
shellwm.completed_destroy(actor);
}
},
_switchWorkspace : function(shellwm, from, to, direction) {
if (!this._shouldAnimate()) {
shellwm.completed_switch_workspace();
return;
}
let windows = global.get_window_actors();
/* @direction is the direction that the "camera" moves, so the
* screen contents have to move one screen's worth in the
* opposite direction.
*/
let xDest = 0, yDest = 0;
if (direction == Meta.MotionDirection.UP ||
direction == Meta.MotionDirection.UP_LEFT ||
direction == Meta.MotionDirection.UP_RIGHT)
yDest = global.screen_height;
else if (direction == Meta.MotionDirection.DOWN ||
direction == Meta.MotionDirection.DOWN_LEFT ||
direction == Meta.MotionDirection.DOWN_RIGHT)
yDest = -global.screen_height;
if (direction == Meta.MotionDirection.LEFT ||
direction == Meta.MotionDirection.UP_LEFT ||
direction == Meta.MotionDirection.DOWN_LEFT)
xDest = global.screen_width;
else if (direction == Meta.MotionDirection.RIGHT ||
direction == Meta.MotionDirection.UP_RIGHT ||
direction == Meta.MotionDirection.DOWN_RIGHT)
xDest = -global.screen_width;
let switchData = {};
this._switchData = switchData;
switchData.inGroup = new Clutter.Group();
switchData.outGroup = new Clutter.Group();
switchData.windows = [];
let wgroup = global.window_group;
wgroup.add_actor(switchData.inGroup);
wgroup.add_actor(switchData.outGroup);
for (let i = 0; i < windows.length; i++) {
let window = windows[i];
if (!window.meta_window.showing_on_its_workspace())
continue;
if (window.get_workspace() == from) {
switchData.windows.push({ window: window,
parent: window.get_parent() });
window.reparent(switchData.outGroup);
} else if (window.get_workspace() == to) {
switchData.windows.push({ window: window,
parent: window.get_parent() });
window.reparent(switchData.inGroup);
window.show_all();
}
}
switchData.inGroup.set_position(-xDest, -yDest);
switchData.inGroup.raise_top();
Tweener.addTween(switchData.outGroup,
{ x: xDest,
y: yDest,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this._switchWorkspaceDone,
onCompleteScope: this,
onCompleteParams: [shellwm]
});
Tweener.addTween(switchData.inGroup,
{ x: 0,
y: 0,
time: WINDOW_ANIMATION_TIME,
transition: 'easeOutQuad'
});
},
_switchWorkspaceDone : function(shellwm) {
let switchData = this._switchData;
if (!switchData)
return;
this._switchData = null;
for (let i = 0; i < switchData.windows.length; i++) {
let w = switchData.windows[i];
if (w.window.is_destroyed()) // Window gone
continue;
if (w.window.get_parent() == switchData.outGroup) {
w.window.reparent(w.parent);
w.window.hide();
} else
w.window.reparent(w.parent);
}
Tweener.removeTweens(switchData.inGroup);
Tweener.removeTweens(switchData.outGroup);
switchData.inGroup.destroy();
switchData.outGroup.destroy();
shellwm.completed_switch_workspace();
},
_startAppSwitcher : function(shellwm, binding, window, backwards) {
/* prevent a corner case where both popups show up at once */
if (this._workspaceSwitcherPopup != null)
this._workspaceSwitcherPopup.actor.hide();
let tabPopup = new AltTab.AltTabPopup();
if (!tabPopup.show(backwards))
tabPopup.destroy();
},
_showWorkspaceSwitcher : function(shellwm, binding, window, backwards) {
if (global.screen.n_workspaces == 1)
return;
if (this._workspaceSwitcherPopup == null)
this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup();
if (binding == 'switch_to_workspace_up')
this.actionMoveWorkspaceUp();
else if (binding == 'switch_to_workspace_down')
this.actionMoveWorkspaceDown();
// left/right would effectively act as synonyms for up/down if we enabled them;
// but that could be considered confusing.
// else if (binding == 'switch_to_workspace_left')
// this.actionMoveWorkspaceLeft();
// else if (binding == 'switch_to_workspace_right')
// this.actionMoveWorkspaceRight();
},
actionMoveWorkspaceLeft: function() {
let rtl = (St.Widget.get_default_direction() == St.TextDirection.RTL);
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (rtl && activeWorkspaceIndex < global.screen.n_workspaces - 1)
indexToActivate++;
else if (!rtl && activeWorkspaceIndex > 0)
indexToActivate--;
if (indexToActivate != activeWorkspaceIndex)
global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.UP, indexToActivate);
},
actionMoveWorkspaceRight: function() {
let rtl = (St.Widget.get_default_direction() == St.TextDirection.RTL);
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (rtl && activeWorkspaceIndex > 0)
indexToActivate--;
else if (!rtl && activeWorkspaceIndex < global.screen.n_workspaces - 1)
indexToActivate++;
if (indexToActivate != activeWorkspaceIndex)
global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.DOWN, indexToActivate);
},
actionMoveWorkspaceUp: function() {
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (activeWorkspaceIndex > 0)
indexToActivate--;
if (indexToActivate != activeWorkspaceIndex)
global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.UP, indexToActivate);
},
actionMoveWorkspaceDown: function() {
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let indexToActivate = activeWorkspaceIndex;
if (activeWorkspaceIndex < global.screen.n_workspaces - 1)
indexToActivate++;
if (indexToActivate != activeWorkspaceIndex)
global.screen.get_workspace_by_index(indexToActivate).activate(global.get_current_time());
if (!Main.overview.visible)
this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.DOWN, indexToActivate);
}
};