/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; const Mainloop = imports.mainloop; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const AltTab = imports.ui.altTab; const Main = imports.ui.main; const Tweener = imports.ui.tweener; const WINDOW_ANIMATION_TIME = 0.25; const SWITCH_ANIMATION_TIME = 0.5; function WindowManager() { this._init(); } WindowManager.prototype = { _init : function() { let me = this; this._global = Shell.Global.get(); this._shellwm = this._global.window_manager; this._minimizing = []; this._maximizing = []; this._unmaximizing = []; this._mapping = []; this._destroying = []; this._switchData = null; this._shellwm.connect('switch-workspace', function(o, from, to, direction) { let actors = me._shellwm.get_switch_workspace_actors(); me._switchWorkspace(actors, from, to, direction); }); this._shellwm.connect('kill-switch-workspace', function(o) { me._switchWorkspaceDone(); }); this._shellwm.connect('minimize', function(o, actor) { me._minimizeWindow(actor); }); this._shellwm.connect('kill-minimize', function(o, actor) { me._minimizeWindowDone(actor); }); this._shellwm.connect('maximize', function(o, actor, tx, ty, tw, th) { me._maximizeWindow(actor, tx, ty, tw, th); }); this._shellwm.connect('kill-maximize', function(o, actor) { me._maximizeWindowDone(actor); }); this._shellwm.connect('unmaximize', function(o, actor, tx, ty, tw, th) { me._unmaximizeWindow(actor, tx, ty, tw, th); }); this._shellwm.connect('kill-unmaximize', function(o, actor) { me._unmaximizeWindowDone(actor); }); this._shellwm.connect('map', function(o, actor) { me._mapWindow(actor); }); this._shellwm.connect('kill-map', function(o, actor) { me._mapWindowDone(actor); }); this._shellwm.connect('destroy', function(o, actor) { me._destroyWindow(actor); }); this._shellwm.connect('kill-destroy', function(o, actor) { me._destroyWindowDone(actor); }); this._shellwm.connect('begin-alt-tab', function(o, handler) { me._beginAltTab(handler); }); }, _shouldAnimate : function(actor) { if (Main.overlay.visible) return false; if (actor && (actor.get_window_type() != Meta.CompWindowType.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(actor) { if (!this._shouldAnimate(actor)) { this._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); Tweener.addTween(actor, { scale_x: 0.0, scale_y: 0.0, time: WINDOW_ANIMATION_TIME, transition: "easeOutQuad", onComplete: this._minimizeWindowDone, onCompleteScope: this, onCompleteParams: [actor], onOverwrite: this._minimizeWindowOverwritten, onOverwriteScope: this, onOverwriteParams: [actor] }); }, _minimizeWindowDone : function(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); this._shellwm.completed_minimize(actor); } }, _minimizeWindowOverwritten : function(actor) { if (this._removeEffect(this._minimizing, actor)) { this._shellwm.completed_minimize(actor); } }, _maximizeWindow : function(actor, targetX, targetY, targetWidth, targetHeight) { if (!this._shouldAnimate(actor)) { this._shellwm.completed_maximize(actor); return; } /* this doesn't work very well, as simply scaling up the existing * window contents doesn't produce anything like the same results as * actually maximizing the window. */ let scaleX = targetWidth / actor.width; let scaleY = targetHeight / actor.height; let anchorX = (actor.x - targetX) * actor.width/(targetWidth - actor.width); let anchorY = (actor.y - targetY) * actor.height/(targetHeight - actor.height); actor.move_anchor_point(anchorX, anchorY); this._maximizing.push(actor); Tweener.addTween(actor, { scale_x: scaleX, scale_y: scaleY, time: WINDOW_ANIMATION_TIME, transition: "easeOutQuad", onComplete: this._maximizeWindowDone, onCompleteScope: this, onCompleteParams: [actor], onOverwrite: this._maximizeWindowOverwrite, onOverwriteScope: this, onOverwriteParams: [actor] }); }, _maximizeWindowDone : function(actor) { if (this._removeEffect(this._maximizing, actor)) { Tweener.removeTweens(actor); actor.set_scale(1.0, 1.0); actor.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST); this._shellwm.completed_maximize(actor); } }, _maximizeWindowOverwrite : function(actor) { if (this._removeEffect(this._maximizing, actor)) { this._shellwm.completed_maximize(actor); } }, _unmaximizeWindow : function(actor, targetX, targetY, targetWidth, targetHeight) { this._shellwm.completed_unmaximize(actor); }, _unmaximizeWindowDone : function(actor) { }, _mapWindow : function(actor) { if (!this._shouldAnimate(actor)) { this._shellwm.completed_map(actor); return; } actor.move_anchor_point_from_gravity(Clutter.Gravity.CENTER); actor.set_scale(0.0, 0.0); actor.show(); /* scale window up from 0x0 to normal size */ this._mapping.push(actor); Tweener.addTween(actor, { scale_x: 1.0, scale_y: 1.0, time: WINDOW_ANIMATION_TIME, transition: "easeOutQuad", onComplete: this._mapWindowDone, onCompleteScope: this, onCompleteParams: [actor], onOverwrite: this._mapWindowOverwrite, onOverwriteScope: this, onOverwriteParams: [actor] }); }, _mapWindowDone : function(actor) { if (this._removeEffect(this._mapping, actor)) { Tweener.removeTweens(actor); actor.set_scale(1.0, 1.0); actor.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_WEST); this._shellwm.completed_map(actor); } }, _mapWindowOverwrite : function(actor) { if (this._removeEffect(this._mapping, actor)) { this._shellwm.completed_map(actor); } }, _destroyWindow : function(actor) { if (!this._shouldAnimate(actor)) { this._shellwm.completed_destroy(actor); return; } actor.move_anchor_point_from_gravity(Clutter.Gravity.CENTER); /* anachronistic 'tv-like' effect - squash on y axis, leave x alone */ this._destroying.push(actor); Tweener.addTween(actor, { scale_x: 1.0, scale_y: 0.0, time: WINDOW_ANIMATION_TIME, transition: "easeOutQuad", onComplete: this._destroyWindowDone, onCompleteScope: this, onCompleteParams: [actor], onOverwrite: this._destroyWindowOverwrite, onOverwriteScope: this, onOverwriteParams: [actor] }); }, _destroyWindowDone : function(actor) { if (this._removeEffect(this._destroying, actor)) { this._shellwm.completed_destroy(actor); Tweener.removeTweens(actor); actor.set_scale(1.0, 1.0); } }, _destroyWindowOverwrite : function(actor) { if (this._removeEffect(this._destroying, actor)) { this._shellwm.completed_destroy(actor); } }, _switchWorkspace : function(windows, from, to, direction) { if (!this._shouldAnimate()) { this._shellwm.completed_switch_workspace(); return; } /* @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 = this._global.screen_height; else if (direction == Meta.MotionDirection.DOWN || direction == Meta.MotionDirection.DOWN_LEFT || direction == Meta.MotionDirection.DOWN_RIGHT) yDest = -this._global.screen_height; if (direction == Meta.MotionDirection.LEFT || direction == Meta.MotionDirection.UP_LEFT || direction == Meta.MotionDirection.DOWN_LEFT) xDest = this._global.screen_width; else if (direction == Meta.MotionDirection.RIGHT || direction == Meta.MotionDirection.UP_RIGHT || direction == Meta.MotionDirection.DOWN_RIGHT) xDest = -this._global.screen_width; let switchData = {}; this._switchData = switchData; switchData.inGroup = new Clutter.Group(); switchData.outGroup = new Clutter.Group(); switchData.windows = []; let wgroup = this._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: SWITCH_ANIMATION_TIME, transition: "easeOutBack", onComplete: this._switchWorkspaceDone, onCompleteScope: this }); Tweener.addTween(switchData.inGroup, { x: 0, y: 0, time: SWITCH_ANIMATION_TIME, transition: "easeOutBack" }); }, _switchWorkspaceDone : function() { 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.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(); this._shellwm.completed_switch_workspace(); }, _beginAltTab : function(handler) { let popup = new AltTab.AltTabPopup(); handler.connect('window-added', function(handler, window) { popup.addWindow(window); }); handler.connect('show', function(handler, initialSelection) { popup.show(initialSelection); }); handler.connect('destroy', function() { popup.destroy(); }); handler.connect('notify::selected', function() { popup.select(handler.selected); }); } };