gnome-shell/js/ui/desktops.js
Dan Winship 79d956b719 show multiple workspaces in the overlay view. #563035
svn path=/trunk/; revision=115
2008-12-04 15:16:16 +00:00

312 lines
12 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
const Tweener = imports.tweener.tweener;
const Clutter = imports.gi.Clutter;
const Main = imports.ui.main;
const Overlay = imports.ui.overlay;
const Panel = imports.ui.panel;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
// Windows are slightly translucent in the overlay mode
const WINDOW_OPACITY = 0.9 * 255;
// Define a layout scheme for small window counts. For larger
// counts we fall back to an algorithm. We need more schemes here
// unless we have a really good algorithm.
// Each triplet is [xCenter, yCenter, scale] where the scale
// is relative to the width of the desktop.
const POSITIONS = {
1: [[0.5, 0.5, 0.8]],
2: [[0.25, 0.5, 0.4], [0.75, 0.5, 0.4]],
3: [[0.25, 0.25, 0.33], [0.75, 0.25, 0.33], [0.5, 0.75, 0.33]],
4: [[0.25, 0.25, 0.33], [0.75, 0.25, 0.33], [0.75, 0.75, 0.33], [0.25, 0.75, 0.33]],
5: [[0.165, 0.25, 0.28], [0.495, 0.25, 0.28], [0.825, 0.25, 0.28], [0.25, 0.75, 0.4], [0.75, 0.75, 0.4]]
};
// spacing between desktops
const GRID_SPACING = 15;
function Desktops(x, y, width, height) {
this._init(x, y, width, height);
}
Desktops.prototype = {
_init : function(x, y, width, height) {
this._group = new Clutter.Group();
this._x = x;
this._y = y;
this._width = width;
this._height = height;
this._workspaces = [];
},
show : function() {
let global = Shell.Global.get();
let windows = global.get_windows();
let activeWorkspace = global.screen.get_active_workspace_index();
// Create a group for each workspace (which lets us raise all of
// its clone windows together when the workspace is activated)
// and add the desktop windows
this._workspaces = [];
for (let w = 0; w < global.screen.n_workspaces; w++) {
this._workspaces[w] = new Clutter.Group();
this._group.add_actor(this._workspaces[w]);
}
this._createDesktopActors(windows);
// The workspaces will go into a grid that is either square,
// or else 1 cell wider than it is tall.
// FIXME: need to make the metacity internal layout agree with this!
let gridWidth = Math.ceil(Math.sqrt(this._workspaces.length));
let gridHeight = Math.ceil(this._workspaces.length / gridWidth);
let wsWidth = (this._width - (gridWidth - 1) * GRID_SPACING) / gridWidth;
let wsHeight = (this._height - (gridHeight - 1) * GRID_SPACING) / gridHeight;
let scale = wsWidth / global.screen_width;
// Position/scale the desktop windows and their children. This
// would be easier if we instead just positioned and scaled
// the entire workspace group, but if we do that then the
// windows of the active workspace will trace out a curved
// path as they move into place, which looks odd. Positioning
// everything independently lets us move them in a straight
// line.
for (let w = 0, x = this._x, y = this._y; w < this._workspaces.length; w++) {
let workspace = this._workspaces[w];
let desktop = workspace.get_nth_child(0);
if (w == activeWorkspace) {
// The currently-active workspace needs to
// slide/shrink into place
workspace.raise_top();
Tweener.addTween(desktop,
{ x: x,
y: y,
scale_x: scale,
scale_y: scale,
time: Overlay.ANIMATION_TIME,
transition: "easeOutQuad"
});
} else {
// Other workspaces can start out in place; they'll be
// revealed as the active workspace shrinks
desktop.set_position(x, y);
desktop.set_scale(scale, scale);
}
// Now handle the rest of the windows in this workspace
let wswindows = windows.filter(function (win) { return win.get_workspace() == w; });
// Do the windows in reverse order so that the active
// actor ends up on top
// for (let i = wswindows.length - 1, windowIndex = 0; i >= 0; i--) {
for (let i = 0, windowIndex = 0; i < wswindows.length; i++) {
let win = wswindows[i];
if (win.get_window_type() == Meta.WindowType.DESKTOP ||
win.is_override_redirect())
continue;
this._createWindowClone(wswindows[i], this._workspaces[w],
x, y, scale,
wswindows.length - windowIndex - 1,
wswindows.length,
w == activeWorkspace);
windowIndex++;
}
x += (wsWidth + GRID_SPACING);
if (x >= this._x + this._width - GRID_SPACING) {
x = this._x;
y += wsHeight + GRID_SPACING;
}
}
},
hide : function() {
let global = Shell.Global.get();
let activeWorkspace = global.screen.get_active_workspace_index();
this._workspaces[activeWorkspace].raise_top();
let windows = this._workspaces[activeWorkspace].get_children();
for (let i = 0; i < windows.length; i++) {
Tweener.addTween(windows[i],
{ x: windows[i].orig_x || 0,
y: windows[i].orig_y || 0,
scale_x: 1.0,
scale_y: 1.0,
time: Overlay.ANIMATION_TIME,
opacity: 255,
transition: "easeOutQuad"
});
}
},
hideDone : function() {
for (let w = 0; w < this._workspaces.length; w++) {
this._workspaces[w].destroy();
}
this._workspaces = [];
},
_createDesktopActors : function(windows) {
let me = this;
let global = Shell.Global.get();
// Find the desktop window or windows
for (let i = 0; i < windows.length; i++) {
if (windows[i].get_window_type() != Meta.WindowType.DESKTOP)
continue;
if (windows[i].get_meta_window().is_on_all_workspaces()) {
for (let w = 0; w < this._workspaces.length; w++)
this._workspaces[w].add_actor(this._cloneWindow(windows[i]));
break;
} else
this._workspaces[windows[i].get_workspace()].add_actor(this._cloneWindow(windows[i]));
}
// Create dummy desktops for workspaces that don't have
// desktop windows, and hook up button events on all desktops
for (let w = 0; w < this._workspaces.length; w++) {
if (this._workspaces[w].get_n_children() == 0)
this._workspaces[w].add_actor(this._createDesktopRectangle());
let workspace = global.screen.get_workspace_by_index(w);
this._workspaces[w].get_nth_child(0).connect(
"button-press-event",
function(clone, event) {
workspace.activate(event.get_time());
me._deactivate();
});
}
},
_cloneWindow : function(window) {
let w = new Clutter.CloneTexture({ parent_texture: window.get_texture(),
reactive: true,
x: window.x,
y: window.y });
w.orig_x = window.x;
w.orig_y = window.y;
return w;
},
_createDesktopRectangle : function() {
let global = Shell.Global.get();
// In the case when we have a desktop window from the file
// manager, its height is full-screen, i.e. it includes the
// height of the panel, so we should not subtract the height
// of the panel from global.screen_height here either to have
// them show up identically. We are also using (0,0)
// coordinates in both cases which makes the background window
// animate out from behind the panel.
return new Clutter.Rectangle({ color: global.stage.color,
reactive: true,
x: 0,
y: 0,
width: global.screen_width,
height: global.screen_height });
},
// windowIndex == 0 => top in stacking order
_computeWindowPosition : function(windowIndex, numberOfWindows) {
if (numberOfWindows in POSITIONS)
return POSITIONS[numberOfWindows][windowIndex];
// If we don't have a predefined scheme for this window count,
// overlap the windows along the diagonal of the desktop
// (improve this!)
let fraction = Math.sqrt(1/numberOfWindows);
// The top window goes at the lower right - this is different from the
// fixed position schemes where the windows are in "reading order"
// and the top window goes at the upper left.
let pos = (numberOfWindows - windowIndex - 1) / (numberOfWindows - 1);
let xCenter = (fraction / 2) + (1 - fraction) * pos;
let yCenter = xCenter;
return [xCenter, yCenter, fraction];
},
_createWindowClone : function(w, workspace, wsX, wsY, wsScale,
windowIndex, numberOfWindows, animate) {
let me = this;
let global = Shell.Global.get();
// We show the window using "clones" of the texture .. separate
// actors that mirror the original actors for the window. For
// animation purposes, it may be better to actually move the
// original actors about instead.
let clone = this._cloneWindow(w);
let [xCenter, yCenter, fraction] = this._computeWindowPosition(windowIndex, numberOfWindows);
let desiredSize = global.screen_width * fraction;
xCenter = wsX + wsScale * (xCenter * global.screen_width);
yCenter = wsY + wsScale * (yCenter * global.screen_height);
let size = clone.width;
if (clone.height > size)
size = clone.height;
// Never scale up
let scale = desiredSize / size;
if (scale > 1)
scale = 1;
scale *= wsScale;
workspace.add_actor(clone);
if (animate) {
Tweener.addTween(clone,
{ x: xCenter - 0.5 * scale * w.width,
y: yCenter - 0.5 * scale * w.height,
scale_x: scale,
scale_y: scale,
time: Overlay.ANIMATION_TIME,
opacity: WINDOW_OPACITY,
transition: "easeOutQuad"
});
} else {
clone.set_position(xCenter - 0.5 * scale * w.width,
yCenter - 0.5 * scale * w.height);
clone.set_scale(scale, scale);
clone.set_opacity(WINDOW_OPACITY);
}
clone.connect("button-press-event",
function(clone, event) {
clone.raise_top();
me._activateWindow(w, event.get_time());
});
},
_activateWindow : function(w, time) {
let global = Shell.Global.get();
let activeWorkspace = global.screen.get_active_workspace_index();
let windowWorkspace = w.get_workspace();
if (windowWorkspace != activeWorkspace) {
let workspace = global.screen.get_workspace_by_index(windowWorkspace);
workspace.activate_with_focus(w.get_meta_window(), time);
} else
w.get_meta_window().activate(time);
this._deactivate();
},
_deactivate : function() {
Main.hide_overlay();
}
};