gnome-shell/js/ui/workspacesView.js
Owen W. Taylor 649ed3332f Fix background to animate with thumbnails
When we animating the scale for the thumbnails, the border and
background should wrap around the current size of the thumbails.
The technique that we are using to animate the scale breaks that
since we don't animate the overall size of the thumbnails box -
we just animate our child actors within the allocation.

To fix this, switch from drawing the background by packing in another
container to drawing the background with a separate actor that
is under the other actors and allocated by our custom logic.

https://bugzilla.gnome.org/show_bug.cgi?id=641881
2011-02-18 11:36:33 -05:00

931 lines
34 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const Tweener = imports.ui.tweener;
const Workspace = imports.ui.workspace;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
const WORKSPACE_SWITCH_TIME = 0.25;
// Note that mutter has a compile-time limit of 36
const MAX_WORKSPACES = 16;
const CONTROLS_POP_IN_TIME = 0.1;
function WorkspacesView(width, height, x, y, workspaces) {
this._init(width, height, x, y, workspaces);
}
WorkspacesView.prototype = {
_init: function(width, height, x, y, workspaces) {
this.actor = new St.Group({ style_class: 'workspaces-view' });
this.actor.set_clip(x, y, width, height);
// The actor itself isn't a drop target, so we don't want to pick on its area
this.actor.set_size(0, 0);
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('style-changed', Lang.bind(this,
function() {
let node = this.actor.get_theme_node();
this._spacing = node.get_length('spacing');
this._computeWorkspacePositions();
}));
this.actor.connect('notify::mapped',
Lang.bind(this, this._onMappedChanged));
this._width = width;
this._height = height;
this._x = x;
this._y = y;
this._zoomScale = 1.0;
this._spacing = 0;
this._activeWorkspaceX = 0; // x offset of active ws while dragging
this._activeWorkspaceY = 0; // y offset of active ws while dragging
this._lostWorkspaces = [];
this._animating = false; // tweening
this._scrolling = false; // swipe-scrolling
this._animatingScroll = false; // programatically updating the adjustment
this._zoomOut = false; // zoom to a larger area
this._inDrag = false; // dragging a window
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
this._workspaces = workspaces;
// Add workspace actors
for (let w = 0; w < global.screen.n_workspaces; w++)
this._workspaces[w].actor.reparent(this.actor);
this._workspaces[activeWorkspaceIndex].actor.raise_top();
// Position/scale the desktop windows and their children after the
// workspaces have been created. This cannot be done first because
// window movement depends on the Workspaces object being accessible
// as an Overview member.
this._overviewShowingId =
Main.overview.connect('showing',
Lang.bind(this, function() {
for (let w = 0; w < this._workspaces.length; w++)
this._workspaces[w].zoomToOverview();
}));
this._scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
lower: 0,
page_increment: 1,
page_size: 1,
step_increment: 0,
upper: this._workspaces.length });
this._scrollAdjustment.connect('notify::value',
Lang.bind(this, this._onScroll));
this._timeoutId = 0;
this._switchWorkspaceNotifyId =
global.window_manager.connect('switch-workspace',
Lang.bind(this, this._activeWorkspaceChanged));
this._itemDragBeginId = Main.overview.connect('item-drag-begin',
Lang.bind(this, this._dragBegin));
this._itemDragEndId = Main.overview.connect('item-drag-end',
Lang.bind(this, this._dragEnd));
this._windowDragBeginId = Main.overview.connect('window-drag-begin',
Lang.bind(this, this._dragBegin));
this._windowDragEndId = Main.overview.connect('window-drag-end',
Lang.bind(this, this._dragEnd));
this._swipeScrollBeginId = 0;
this._swipeScrollEndId = 0;
},
setZoomScale: function(zoomScale) {
if (zoomScale == this._zoomScale)
return;
this._zoomScale = zoomScale;
if (this._zoomOut) {
// If we are already zoomed out, then we have to reposition.
// Note that when shown initially zoomOut is false, so we
// won't trigger this.
// setZoomScale can be invoked when the workspaces view is
// reallocated. Since we just want to animate things to the
// new position it seems OK to call updateWorkspaceActors
// immediately - adding a tween doesn't immediately cause
// a new allocation. But hide/show of the window overlays we
// do around animation does, so we need to do it later.
// This can be removed when we fix things to not hide/show
// the window overlay.
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
Lang.bind(this, function() {
this._computeWorkspacePositions();
this._updateWorkspaceActors(true);
}));
}
},
_lookupWorkspaceForMetaWindow: function (metaWindow) {
for (let i = 0; i < this._workspaces.length; i++) {
if (this._workspaces[i].containsMetaWindow(metaWindow))
return this._workspaces[i];
}
return null;
},
getActiveWorkspace: function() {
let active = global.screen.get_active_workspace_index();
return this._workspaces[active];
},
hide: function() {
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let activeWorkspace = this._workspaces[activeWorkspaceIndex];
activeWorkspace.actor.raise_top();
for (let w = 0; w < this._workspaces.length; w++)
this._workspaces[w].zoomFromOverview();
},
destroy: function() {
this.actor.destroy();
},
getScale: function() {
return this._workspaces[0].scale;
},
syncStacking: function(stackIndices) {
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].syncStacking(stackIndices);
},
// Get the grid position of the active workspace.
getActiveWorkspacePosition: function() {
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let activeWorkspace = this._workspaces[activeWorkspaceIndex];
return [activeWorkspace.x, activeWorkspace.y];
},
zoomOut: function() {
if (this._zoomOut)
return;
this._zoomOut = true;
this._computeWorkspacePositions();
this._updateWorkspaceActors(true);
},
zoomIn: function() {
if (!this._zoomOut)
return;
this._zoomOut = false;
this._computeWorkspacePositions();
this._updateWorkspaceActors(true);
},
// Compute the position, scale and opacity of the workspaces, but don't
// actually change the actors to match
_computeWorkspacePositions: function() {
let active = global.screen.get_active_workspace_index();
let zoomScale = this._zoomOut ? this._zoomScale : 1;
let scale = zoomScale * this._width / global.screen_width;
let _width = this._workspaces[0].actor.width * scale;
let _height = this._workspaces[0].actor.height * scale;
this._activeWorkspaceX = (this._width - _width) / 2;
this._activeWorkspaceY = (this._height - _height) / 2;
for (let w = 0; w < this._workspaces.length; w++) {
let workspace = this._workspaces[w];
workspace.opacity = (this._inDrag && w != active) ? 200 : 255;
workspace.scale = scale;
workspace.x = this._x + this._activeWorkspaceX;
// We adjust the center because the zoomScale is to leave space for
// the expanded workspace control so we want to zoom to either the
// left part of the area or the right part of the area
let offset = 0.5 * (1 - this._zoomScale) * this._width;
let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
if (this._zoomOut)
workspace.x += rtl ? offset : - offset;
// We divide by zoomScale so that adjacent workspaces are always offscreen
// except when we are switching between workspaces
workspace.y = this._y + this._activeWorkspaceY
+ (w - active) * (_height + this._spacing) / zoomScale;
}
},
_scrollToActive: function(showAnimation) {
let active = global.screen.get_active_workspace_index();
this._computeWorkspacePositions();
this._updateWorkspaceActors(showAnimation);
this._updateScrollAdjustment(active, showAnimation);
},
// Update workspace actors parameters to the values calculated in
// _computeWorkspacePositions()
// @showAnimation: iff %true, transition between states
_updateWorkspaceActors: function(showAnimation) {
let active = global.screen.get_active_workspace_index();
let targetWorkspaceNewY = this._y + this._activeWorkspaceY;
let targetWorkspaceCurrentY = this._workspaces[active].y;
let dy = targetWorkspaceNewY - targetWorkspaceCurrentY;
this._animating = showAnimation;
for (let w = 0; w < this._workspaces.length; w++) {
let workspace = this._workspaces[w];
Tweener.removeTweens(workspace.actor);
workspace.y += dy;
if (showAnimation) {
let params = { x: workspace.x,
y: workspace.y,
scale_x: workspace.scale,
scale_y: workspace.scale,
opacity: workspace.opacity,
time: WORKSPACE_SWITCH_TIME,
transition: 'easeOutQuad'
};
// we have to call _updateVisibility() once before the
// animation and once afterwards - it does not really
// matter which tween we use, so we pick the first one ...
if (w == 0) {
this._updateVisibility();
params.onComplete = Lang.bind(this,
function() {
this._animating = false;
this._updateVisibility();
});
}
Tweener.addTween(workspace.actor, params);
} else {
workspace.actor.set_scale(workspace.scale, workspace.scale);
workspace.actor.set_position(workspace.x, workspace.y);
workspace.actor.opacity = workspace.opacity;
if (w == 0)
this._updateVisibility();
}
}
for (let l = 0; l < this._lostWorkspaces.length; l++) {
let workspace = this._lostWorkspaces[l];
Tweener.removeTweens(workspace.actor);
workspace.y += dy;
workspace.actor.show();
workspace.hideWindowsOverlays();
if (showAnimation) {
Tweener.addTween(workspace.actor,
{ y: workspace.x,
time: WORKSPACE_SWITCH_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
this._cleanWorkspaces)
});
} else {
this._cleanWorkspaces();
}
}
},
_updateVisibility: function() {
let active = global.screen.get_active_workspace_index();
for (let w = 0; w < this._workspaces.length; w++) {
let workspace = this._workspaces[w];
if (this._animating || this._scrolling) {
workspace.hideWindowsOverlays();
workspace.actor.show();
} else {
workspace.showWindowsOverlays();
if (this._inDrag)
workspace.actor.visible = (Math.abs(w - active) <= 1);
else
workspace.actor.visible = (w == active);
}
}
},
_cleanWorkspaces: function() {
if (this._lostWorkspaces.length == 0)
return;
for (let l = 0; l < this._lostWorkspaces.length; l++)
this._lostWorkspaces[l].destroy();
this._lostWorkspaces = [];
this._computeWorkspacePositions();
this._updateWorkspaceActors(false);
},
_updateScrollAdjustment: function(index, showAnimation) {
if (this._scrolling)
return;
this._animatingScroll = true;
if (showAnimation) {
Tweener.addTween(this._scrollAdjustment, {
value: index,
time: WORKSPACE_SWITCH_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
function() {
this._animatingScroll = false;
})
});
} else {
this._scrollAdjustment.value = index;
this._animatingScroll = false;
}
},
updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces, lostWorkspaces) {
let active = global.screen.get_active_workspace_index();
for (let l = 0; l < lostWorkspaces.length; l++)
lostWorkspaces[l].disconnectAll();
Tweener.addTween(this._scrollAdjustment,
{ upper: newNumWorkspaces,
time: WORKSPACE_SWITCH_TIME,
transition: 'easeOutQuad'
});
if (newNumWorkspaces > oldNumWorkspaces) {
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++)
this.actor.add_actor(this._workspaces[w].actor);
this._computeWorkspacePositions();
this._updateWorkspaceActors(false);
} else {
this._lostWorkspaces = lostWorkspaces;
}
this._scrollToActive(true);
},
_activeWorkspaceChanged: function(wm, from, to, direction) {
if (this._scrolling)
return;
this._scrollToActive(true);
},
_onDestroy: function() {
this._scrollAdjustment.run_dispose();
Main.overview.disconnect(this._overviewShowingId);
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
if (this._timeoutId) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
if (this._itemDragBeginId > 0) {
Main.overview.disconnect(this._itemDragBeginId);
this._itemDragBeginId = 0;
}
if (this._itemDragEndId > 0) {
Main.overview.disconnect(this._itemDragEndId);
this._itemDragEndId = 0;
}
if (this._windowDragBeginId > 0) {
Main.overview.disconnect(this._windowDragBeginId);
this._windowDragBeginId = 0;
}
if (this._windowDragEndId > 0) {
Main.overview.disconnect(this._windowDragEndId);
this._windowDragEndId = 0;
}
},
_onMappedChanged: function() {
if (this.actor.mapped) {
let direction = Overview.SwipeScrollDirection.VERTICAL;
Main.overview.setScrollAdjustment(this._scrollAdjustment,
direction);
this._swipeScrollBeginId = Main.overview.connect('swipe-scroll-begin',
Lang.bind(this, this._swipeScrollBegin));
this._swipeScrollEndId = Main.overview.connect('swipe-scroll-end',
Lang.bind(this, this._swipeScrollEnd));
} else {
Main.overview.disconnect(this._swipeScrollBeginId);
Main.overview.disconnect(this._swipeScrollEndId);
}
},
_dragBegin: function() {
if (this._scrolling)
return;
this._inDrag = true;
this._dragMonitor = {
dragMotion: Lang.bind(this, this._onDragMotion)
};
DND.addDragMonitor(this._dragMonitor);
},
_onDragMotion: function(dragEvent) {
if (Main.overview.animationInProgress)
return DND.DragMotionResult.CONTINUE;
let primary = global.get_primary_monitor();
let activeWorkspaceIndex = global.screen.get_active_workspace_index();
let topWorkspace, bottomWorkspace;
topWorkspace = this._workspaces[activeWorkspaceIndex - 1];
bottomWorkspace = this._workspaces[activeWorkspaceIndex + 1];
let hoverWorkspace = null;
// reactive monitor edges
let topEdge = primary.y;
let switchTop = (dragEvent.y <= topEdge && topWorkspace);
if (switchTop && this._dragOverLastY != topEdge) {
topWorkspace.metaWorkspace.activate(global.get_current_time());
topWorkspace.setReservedSlot(dragEvent.dragActor._delegate);
this._dragOverLastY = topEdge;
return DND.DragMotionResult.CONTINUE;
}
let bottomEdge = primary.y + primary.height - 1;
let switchBottom = (dragEvent.y >= bottomEdge && bottomWorkspace);
if (switchBottom && this._dragOverLastY != bottomEdge) {
bottomWorkspace.metaWorkspace.activate(global.get_current_time());
bottomWorkspace.setReservedSlot(dragEvent.dragActor._delegate);
this._dragOverLastY = bottomEdge;
return DND.DragMotionResult.CONTINUE;
}
this._dragOverLastY = dragEvent.y;
let result = DND.DragMotionResult.CONTINUE;
// check hover state of new workspace area / inactive workspaces
if (topWorkspace) {
if (topWorkspace.actor.contains(dragEvent.targetActor)) {
hoverWorkspace = topWorkspace;
topWorkspace.opacity = topWorkspace.actor.opacity = 255;
result = topWorkspace.handleDragOver(dragEvent.source, dragEvent.dragActor);
} else {
topWorkspace.opacity = topWorkspace.actor.opacity = 200;
}
}
if (bottomWorkspace) {
if (bottomWorkspace.actor.contains(dragEvent.targetActor)) {
hoverWorkspace = bottomWorkspace;
bottomWorkspace.opacity = bottomWorkspace.actor.opacity = 255;
result = bottomWorkspace.handleDragOver(dragEvent.source, dragEvent.dragActor);
} else {
bottomWorkspace.opacity = bottomWorkspace.actor.opacity = 200;
}
}
// handle delayed workspace switches
if (hoverWorkspace) {
if (!this._timeoutId)
this._timeoutId = Mainloop.timeout_add_seconds(1,
Lang.bind(this, function() {
hoverWorkspace.metaWorkspace.activate(global.get_current_time());
hoverWorkspace.setReservedSlot(dragEvent.dragActor._delegate);
return false;
}));
} else {
if (this._timeoutId) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
}
return result;
},
_dragEnd: function() {
if (this._timeoutId) {
Mainloop.source_remove(this._timeoutId);
this._timeoutId = 0;
}
DND.removeMonitor(this._dragMonitor);
this._inDrag = false;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].setReservedSlot(null);
},
_swipeScrollBegin: function() {
this._scrolling = true;
},
_swipeScrollEnd: function(overview, result) {
this._scrolling = false;
if (result == Overview.SwipeScrollResult.CLICK) {
let [x, y, mod] = global.get_pointer();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL,
x, y);
// Only switch to the workspace when there's no application
// windows open. The problem is that it's too easy to miss
// an app window and get the wrong one focused.
let active = global.screen.get_active_workspace_index();
if (this._workspaces[active].isEmpty() &&
this.actor.contains(actor))
Main.overview.hide();
}
if (result == Overview.SwipeScrollResult.SWIPE)
// The active workspace has changed; while swipe-scrolling
// has already taken care of the positioning, the cached
// positions need to be updated
this._computeWorkspacePositions();
// Make sure title captions etc are shown as necessary
this._updateVisibility();
},
// sync the workspaces' positions to the value of the scroll adjustment
// and change the active workspace if appropriate
_onScroll: function(adj) {
if (this._animatingScroll)
return;
let active = global.screen.get_active_workspace_index();
let current = Math.round(adj.value);
if (active != current) {
let metaWorkspace = this._workspaces[current].metaWorkspace;
metaWorkspace.activate(global.get_current_time());
}
let last = this._workspaces.length - 1;
let firstWorkspaceY = this._workspaces[0].actor.y;
let lastWorkspaceY = this._workspaces[last].actor.y;
let workspacesHeight = lastWorkspaceY - firstWorkspaceY;
if (adj.upper == 1)
return;
let currentY = firstWorkspaceY;
let newY = this._y - adj.value / (adj.upper - 1) * workspacesHeight;
let dy = newY - currentY;
for (let i = 0; i < this._workspaces.length; i++) {
this._workspaces[i].hideWindowsOverlays();
this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
this._workspaces[i].actor.y += dy;
}
},
_getWorkspaceIndexToRemove: function() {
return global.screen.get_active_workspace_index();
}
};
Signals.addSignalMethods(WorkspacesView.prototype);
function WorkspacesDisplay() {
this._init();
}
WorkspacesDisplay.prototype = {
_init: function() {
this.actor = new Shell.GenericContainer();
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));
let controls = new St.Bin({ style_class: 'workspace-controls',
request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT,
y_align: St.Align.START,
y_fill: true });
this._controls = controls;
this.actor.add_actor(controls);
controls.reactive = true;
controls.track_hover = true;
controls.connect('notify::hover',
Lang.bind(this, this._onControlsHoverChanged));
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
controls.add_actor(this._thumbnailsBox.actor);
this.workspacesView = null;
this._inDrag = false;
this._zoomOut = false;
this._zoomFraction = 0;
this._nWorkspacesNotifyId = 0;
this._switchWorkspaceNotifyId = 0;
this._itemDragBeginId = 0;
this._itemDragEndId = 0;
this._windowDragBeginId = 0;
this._windowDragEndId = 0;
},
show: function() {
this._controls.show();
this._thumbnailsBox.show();
this._workspaces = [];
for (let i = 0; i < global.screen.n_workspaces; i++) {
let metaWorkspace = global.screen.get_workspace_by_index(i);
this._workspaces[i] = new Workspace.Workspace(metaWorkspace);
}
let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
let totalAllocation = this.actor.allocation;
let totalWidth = totalAllocation.x2 - totalAllocation.x1;
let totalHeight = totalAllocation.y2 - totalAllocation.y1;
let controlsVisible = this._controls.get_theme_node().get_length('visible-width');
totalWidth -= controlsVisible;
// Workspaces expect to have the same ratio as the screen, so take
// this into account when fitting the workspace into the available space
let width, height;
let totalRatio = totalWidth / totalHeight;
let wsRatio = global.screen_width / global.screen_height;
if (wsRatio > totalRatio) {
width = totalWidth;
height = Math.floor(totalWidth / wsRatio);
} else {
width = Math.floor(totalHeight * wsRatio);
height = totalHeight;
}
// Position workspaces in the available space
let [x, y] = this.actor.get_transformed_position();
x = Math.floor(x + Math.abs(totalWidth - width) / 2);
y = Math.floor(y + Math.abs(totalHeight - height) / 2);
if (rtl)
x += controlsVisible;
let newView = new WorkspacesView(width, height, x, y, this._workspaces);
this._updateZoomScale();
if (this.workspacesView)
this.workspacesView.destroy();
this.workspacesView = newView;
this._nWorkspacesNotifyId =
global.screen.connect('notify::n-workspaces',
Lang.bind(this, this._workspacesChanged));
this._restackedNotifyId =
global.screen.connect('restacked',
Lang.bind(this, this._onRestacked));
if (this._itemDragBeginId == 0)
this._itemDragBeginId = Main.overview.connect('item-drag-begin',
Lang.bind(this, this._dragBegin));
if (this._itemDragEndId == 0)
this._itemDragEndId = Main.overview.connect('item-drag-end',
Lang.bind(this, this._dragEnd));
if (this._windowDragBeginId == 0)
this._windowDragBeginId = Main.overview.connect('window-drag-begin',
Lang.bind(this, this._dragBegin));
if (this._windowDragEndId == 0)
this._windowDragEndId = Main.overview.connect('window-drag-end',
Lang.bind(this, this._dragEnd));
this._onRestacked();
this._zoomOut = false;
this._zoomFraction = 0;
this._updateZoom();
},
hide: function() {
this._controls.hide();
this._thumbnailsBox.hide();
if (this._nWorkspacesNotifyId > 0) {
global.screen.disconnect(this._nWorkspacesNotifyId);
this._nWorkspacesNotifyId = 0;
}
if (this._restackedNotifyId > 0){
global.screen.disconnect(this._restackedNotifyId);
this._restackedNotifyId = 0;
}
if (this._itemDragBeginId > 0) {
Main.overview.disconnect(this._itemDragBeginId);
this._itemDragBeginId = 0;
}
if (this._itemEndBeginId > 0) {
Main.overview.disconnect(this._itemDragEndId);
this._itemDragEndId = 0;
}
if (this._windowDragBeginId > 0) {
Main.overview.disconnect(this._windowDragBeginId);
this._windowDragBeginId = 0;
}
if (this._windowDragEndId > 0) {
Main.overview.disconnect(this._windowDragEndId);
this._windowDragEndId = 0;
}
this.workspacesView.destroy();
this.workspacesView = null;
for (let w = 0; w < this._workspaces.length; w++) {
this._workspaces[w].disconnectAll();
this._workspaces[w].destroy();
}
},
// zoomFraction property allows us to tween the controls sliding in and out
set zoomFraction(fraction) {
this._zoomFraction = fraction;
this.actor.queue_relayout();
},
get zoomFraction() {
return this._zoomFraction;
},
_getPreferredWidth: function (actor, forHeight, alloc) {
// pass through the call in case the child needs it, but report 0x0
this._controls.get_preferred_width(forHeight);
},
_getPreferredHeight: function (actor, forWidth, alloc) {
// pass through the call in case the child needs it, but report 0x0
this._controls.get_preferred_height(forWidth);
},
_allocate: function (actor, box, flags) {
let childBox = new Clutter.ActorBox();
let totalWidth = box.x2 - box.x1;
// width of the controls
let [controlsMin, controlsNatural] = this._controls.get_preferred_width(box.y2 - box.y1);
// Amount of space on the screen we reserve for the visible control
let controlsVisible = this._controls.get_theme_node().get_length('visible-width');
let controlsReserved = controlsVisible * (1 - this._zoomFraction) + controlsNatural * this._zoomFraction;
let rtl = (St.Widget.get_default_direction () == St.TextDirection.RTL);
if (rtl) {
childBox.x2 = controlsReserved;
childBox.x1 = childBox.x2 - controlsNatural;
} else {
childBox.x1 = totalWidth - controlsReserved;
childBox.x2 = childBox.x1 + controlsNatural;
}
childBox.y1 = 0;
childBox.y2 = box.y2- box.y1;
this._controls.allocate(childBox, flags);
this._updateZoomScale();
},
_updateZoomScale: function() {
if (!this.workspacesView)
return;
let totalAllocation = this.actor.allocation;
let totalWidth = totalAllocation.x2 - totalAllocation.x1;
let totalHeight = totalAllocation.y2 - totalAllocation.y1;
let [controlsMin, controlsNatural] = this._controls.get_preferred_width(totalHeight);
let controlsVisible = this._controls.get_theme_node().get_length('visible-width');
let zoomScale = (totalWidth - controlsNatural) / (totalWidth - controlsVisible);
this.workspacesView.setZoomScale(zoomScale);
},
_onRestacked: function() {
let stack = global.get_window_actors();
let stackIndices = {};
for (let i = 0; i < stack.length; i++) {
// Use the stable sequence for an integer to use as a hash key
stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
}
this.workspacesView.syncStacking(stackIndices);
this._thumbnailsBox.syncStacking(stackIndices);
},
_workspacesChanged: function() {
let oldNumWorkspaces = this._workspaces.length;
let newNumWorkspaces = global.screen.n_workspaces;
let active = global.screen.get_active_workspace_index();
if (oldNumWorkspaces == newNumWorkspaces)
return;
let lostWorkspaces = [];
if (newNumWorkspaces > oldNumWorkspaces) {
// Assume workspaces are only added at the end
for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
let metaWorkspace = global.screen.get_workspace_by_index(w);
this._workspaces[w] = new Workspace.Workspace(metaWorkspace);
}
this._thumbnailsBox.addThumbnails(oldNumWorkspaces, newNumWorkspaces - oldNumWorkspaces);
} else {
// Assume workspaces are only removed sequentially
// (e.g. 2,3,4 - not 2,4,7)
let removedIndex;
let removedNum = oldNumWorkspaces - newNumWorkspaces;
for (let w = 0; w < oldNumWorkspaces; w++) {
let metaWorkspace = global.screen.get_workspace_by_index(w);
if (this._workspaces[w].metaWorkspace != metaWorkspace) {
removedIndex = w;
break;
}
}
lostWorkspaces = this._workspaces.splice(removedIndex,
removedNum);
// Don't let the user try to select this workspace as it's
// making its exit.
for (let l = 0; l < lostWorkspaces.length; l++)
lostWorkspaces[l].setReactive(false);
this._thumbnailsBox.removeThumbmails(removedIndex, removedNum);
}
this.workspacesView.updateWorkspaces(oldNumWorkspaces,
newNumWorkspaces,
lostWorkspaces);
},
_updateZoom : function() {
if (Main.overview.animationInProgress)
return;
let shouldZoom = this._controls.hover || this._inDrag;
if (shouldZoom != this._zoomOut) {
this._zoomOut = shouldZoom;
if (!this.workspacesView)
return;
Tweener.addTween(this,
{ zoomFraction: this._zoomOut ? 1 : 0,
time: WORKSPACE_SWITCH_TIME,
transition: 'easeOutQuad' });
if (shouldZoom)
this.workspacesView.zoomOut();
else
this.workspacesView.zoomIn();
}
},
_onControlsHoverChanged: function() {
this._updateZoom();
},
_dragBegin: function() {
this._inDrag = true;
this._updateZoom();
},
_dragEnd: function() {
this._inDrag = false;
// We do this deferred because drag-end is emitted before dnd.js emits
// event/leave events that were suppressed during the drag. If we didn't
// defer this, we'd zoom out then immediately zoom in because of the
// enter event we received. That would normally be invisible but we
// might as well avoid it.
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
Lang.bind(this, this._updateZoom));
}
};
Signals.addSignalMethods(WorkspacesDisplay.prototype);