Bug 574697 - Slide the workspaces back in if the user starts dragging a document or an application item in the expanded display mode
Sliding the workspaces back in when the user starts dragging an item in the expanded display mode allows the user to select a workspace in which the item should be launched and stay in the overlay mode. This patch adds code to dnd.js that handles notifying actors when a drag item is being dragged over them. Overlay code uses such notification to unset expanded display modes and trigger sliding in of the workspaces. If the drag is cancelled the drag item snaps back to its source or disappears at the original position of its source if the source is no longer displayed.
This commit is contained in:
parent
c570795ac7
commit
39eb563687
46
js/ui/dnd.js
46
js/ui/dnd.js
@ -1,10 +1,9 @@
|
|||||||
/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
|
/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
const Lang = imports.lang;
|
|
||||||
const Signals = imports.signals;
|
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Signals = imports.signals;
|
||||||
const Tweener = imports.ui.tweener;
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
const SNAP_BACK_ANIMATION_TIME = 0.25;
|
const SNAP_BACK_ANIMATION_TIME = 0.25;
|
||||||
@ -80,6 +79,13 @@ _Draggable.prototype = {
|
|||||||
|
|
||||||
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
||||||
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
|
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
|
||||||
|
// Drag actor does not always have to be the same as actor. For example drag actor
|
||||||
|
// can be an image that's part of the actor. So to perform "snap back" correctly we need
|
||||||
|
// to know what was the drag actor source.
|
||||||
|
if (this.actor._delegate.getDragActorSource)
|
||||||
|
this._dragActorSource = this.actor._delegate.getDragActorSource();
|
||||||
|
else
|
||||||
|
this._dragActorSource = this.actor;
|
||||||
this._dragOrigParent = undefined;
|
this._dragOrigParent = undefined;
|
||||||
this._ungrabActor(actor);
|
this._ungrabActor(actor);
|
||||||
this._grabActor(this._dragActor);
|
this._grabActor(this._dragActor);
|
||||||
@ -88,6 +94,7 @@ _Draggable.prototype = {
|
|||||||
this._dragOffsetY = this._dragActor.y - this._dragStartY;
|
this._dragOffsetY = this._dragActor.y - this._dragStartY;
|
||||||
} else {
|
} else {
|
||||||
this._dragActor = actor;
|
this._dragActor = actor;
|
||||||
|
this._dragActorSource = undefined;
|
||||||
this._dragOrigParent = actor.get_parent();
|
this._dragOrigParent = actor.get_parent();
|
||||||
this._dragOrigX = this._dragActor.x;
|
this._dragOrigX = this._dragActor.x;
|
||||||
this._dragOrigY = this._dragActor.y;
|
this._dragOrigY = this._dragActor.y;
|
||||||
@ -112,6 +119,24 @@ _Draggable.prototype = {
|
|||||||
if (this._dragActor) {
|
if (this._dragActor) {
|
||||||
this._dragActor.set_position(stageX + this._dragOffsetX,
|
this._dragActor.set_position(stageX + this._dragOffsetX,
|
||||||
stageY + this._dragOffsetY);
|
stageY + this._dragOffsetY);
|
||||||
|
// Because we want to find out what other actor is located at the current position of this._dragActor,
|
||||||
|
// we have to temporarily hide this._dragActor.
|
||||||
|
this._dragActor.hide();
|
||||||
|
let target = actor.get_stage().get_actor_at_pos(stageX + this._dragOffsetX, stageY + this._dragOffsetY);
|
||||||
|
this._dragActor.show();
|
||||||
|
while (target) {
|
||||||
|
if (target._delegate && target._delegate.handleDragOver) {
|
||||||
|
let [targX, targY] = target.get_transformed_position();
|
||||||
|
// We currently loop through all parents on drag-over even if one of the children has handled it.
|
||||||
|
// We can check the return value of the function and break the loop if it's true if we don't want
|
||||||
|
// to continue checking the parents.
|
||||||
|
target._delegate.handleDragOver(this.actor._delegate, actor,
|
||||||
|
(stageX + this._dragOffsetX + this._xOffset - targX) / target.scale_x,
|
||||||
|
(stageY + this._dragOffsetY + this._yOffset - targY) / target.scale_y,
|
||||||
|
event.get_time());
|
||||||
|
}
|
||||||
|
target = target.get_parent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -151,10 +176,19 @@ _Draggable.prototype = {
|
|||||||
target = target.get_parent();
|
target = target.get_parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snap back to the actor source if the source is still around, snap back
|
||||||
|
// to the original location if the actor itself was being dragged or the
|
||||||
|
// source is no longer around.
|
||||||
|
let snapBackX = this._dragStartX + this._dragOffsetX;
|
||||||
|
let snapBackY = this._dragStartY + this._dragOffsetY;
|
||||||
|
if (this._dragActorSource && this._dragActorSource.visible) {
|
||||||
|
[snapBackX, snapBackY] = this._dragActorSource.get_transformed_position();
|
||||||
|
}
|
||||||
|
|
||||||
// No target, so snap back
|
// No target, so snap back
|
||||||
Tweener.addTween(actor,
|
Tweener.addTween(actor,
|
||||||
{ x: this._dragStartX + this._dragOffsetX,
|
{ x: snapBackX,
|
||||||
y: this._dragStartY + this._dragOffsetY,
|
y: snapBackY,
|
||||||
time: SNAP_BACK_ANIMATION_TIME,
|
time: SNAP_BACK_ANIMATION_TIME,
|
||||||
transition: "easeOutQuad",
|
transition: "easeOutQuad",
|
||||||
onComplete: this._onSnapBackComplete,
|
onComplete: this._onSnapBackComplete,
|
||||||
@ -178,4 +212,4 @@ Signals.addSignalMethods(_Draggable.prototype);
|
|||||||
|
|
||||||
function makeDraggable(actor) {
|
function makeDraggable(actor) {
|
||||||
return new _Draggable(actor);
|
return new _Draggable(actor);
|
||||||
}
|
}
|
||||||
|
@ -64,13 +64,17 @@ GenericDisplayItem.prototype = {
|
|||||||
this._name = null;
|
this._name = null;
|
||||||
this._description = null;
|
this._description = null;
|
||||||
this._icon = null;
|
this._icon = null;
|
||||||
|
|
||||||
|
this.dragActor = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
//// Draggable interface ////
|
//// Draggable object interface ////
|
||||||
|
|
||||||
|
// Returns a cloned texture of the item's icon to represent the item as it
|
||||||
|
// is being dragged.
|
||||||
getDragActor: function(stageX, stageY) {
|
getDragActor: function(stageX, stageY) {
|
||||||
// FIXME: assumes this._icon is a Clutter.Texture
|
this.dragActor = new Clutter.Clone({ source: this._icon });
|
||||||
let icon = new Clutter.Clone({ source: this._icon });
|
[this.dragActor.width, this.dragActor.height] = this._icon.get_transformed_size();
|
||||||
[icon.width, icon.height] = this._icon.get_transformed_size();
|
|
||||||
|
|
||||||
// If the user dragged from the icon itself, then position
|
// If the user dragged from the icon itself, then position
|
||||||
// the dragActor over the original icon. Otherwise center it
|
// the dragActor over the original icon. Otherwise center it
|
||||||
@ -79,12 +83,18 @@ GenericDisplayItem.prototype = {
|
|||||||
let [iconWidth, iconHeight] = this._icon.get_transformed_size();
|
let [iconWidth, iconHeight] = this._icon.get_transformed_size();
|
||||||
if (stageX > iconX && stageX <= iconX + iconWidth &&
|
if (stageX > iconX && stageX <= iconX + iconWidth &&
|
||||||
stageY > iconY && stageY <= iconY + iconHeight)
|
stageY > iconY && stageY <= iconY + iconHeight)
|
||||||
icon.set_position(iconX, iconY);
|
this.dragActor.set_position(iconX, iconY);
|
||||||
else
|
else
|
||||||
icon.set_position(stageX - icon.width / 2, stageY - icon.height / 2);
|
this.dragActor.set_position(stageX - this.dragActor.width / 2, stageY - this.dragActor.height / 2);
|
||||||
return icon;
|
return this.dragActor;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Returns the original icon that is being used as a source for the cloned texture
|
||||||
|
// that represents the item as it is being dragged.
|
||||||
|
getDragActorSource: function() {
|
||||||
|
return this._icon;
|
||||||
|
},
|
||||||
|
|
||||||
//// Public methods ////
|
//// Public methods ////
|
||||||
|
|
||||||
// Highlights the item by setting a different background color than the default
|
// Highlights the item by setting a different background color than the default
|
||||||
@ -388,7 +398,32 @@ GenericDisplay.prototype = {
|
|||||||
this.selectUp();
|
this.selectUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
displayItem.actor.destroy();
|
if (displayItem.dragActor) {
|
||||||
|
// The user might be handling a dragActor when the list of items
|
||||||
|
// changes (for example, if the dragging caused us to transition
|
||||||
|
// from an expanded overlay view to the regular view). So we need
|
||||||
|
// to keep the item around so that the drag and drop action initiated
|
||||||
|
// by the user can be completed. However, we remove the item from the list.
|
||||||
|
//
|
||||||
|
// For some reason, just removing the displayItem.actor
|
||||||
|
// is not enough to get displayItem._icon.visible
|
||||||
|
// to return false, so we hide the display item and
|
||||||
|
// all its children first. (We check displayItem._icon.visible
|
||||||
|
// when deciding if a dragActor has a place to snap back to
|
||||||
|
// in case the drop was not accepted by any actor.)
|
||||||
|
displayItem.actor.hide_all();
|
||||||
|
this._grid.remove_actor(displayItem.actor);
|
||||||
|
// We should not destroy the actor up-front, because that would also
|
||||||
|
// destroy the icon that was used to clone the image for the drag actor.
|
||||||
|
// We destroy it once the dragActor is destroyed instead.
|
||||||
|
displayItem.dragActor.connect('destroy',
|
||||||
|
function(item) {
|
||||||
|
displayItem.actor.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
displayItem.actor.destroy();
|
||||||
|
}
|
||||||
delete this._displayedItems[itemId];
|
delete this._displayedItems[itemId];
|
||||||
this._displayedItemsCount--;
|
this._displayedItemsCount--;
|
||||||
},
|
},
|
||||||
|
@ -568,6 +568,8 @@ Overlay.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._group = new Clutter.Group();
|
this._group = new Clutter.Group();
|
||||||
|
this._group._delegate = this;
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
|
||||||
let background = new Clutter.Rectangle({ color: OVERLAY_BACKGROUND_COLOR,
|
let background = new Clutter.Rectangle({ color: OVERLAY_BACKGROUND_COLOR,
|
||||||
@ -604,7 +606,7 @@ Overlay.prototype = {
|
|||||||
{ x: displayGridColumnWidth * asideXFactor,
|
{ x: displayGridColumnWidth * asideXFactor,
|
||||||
time: ANIMATION_TIME,
|
time: ANIMATION_TIME,
|
||||||
transition: "easeOutQuad"
|
transition: "easeOutQuad"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._sideshow.connect('less-activated', function(sideshow) {
|
this._sideshow.connect('less-activated', function(sideshow) {
|
||||||
@ -621,6 +623,24 @@ Overlay.prototype = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//// Draggable target interface ////
|
||||||
|
|
||||||
|
// Unsets the expanded display mode if a GenericDisplayItem is being
|
||||||
|
// dragged over the overlay, i.e. as soon as it starts being dragged.
|
||||||
|
// This slides the workspaces back in and allows the user to place
|
||||||
|
// the item on any workspace.
|
||||||
|
handleDragOver : function(source, actor, x, y, time) {
|
||||||
|
if (source instanceof GenericDisplay.GenericDisplayItem) {
|
||||||
|
this._sideshow._unsetMoreAppsMode();
|
||||||
|
this._sideshow._unsetMoreDocsMode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
//// Public methods ////
|
||||||
|
|
||||||
show : function() {
|
show : function() {
|
||||||
if (this.visible)
|
if (this.visible)
|
||||||
return;
|
return;
|
||||||
@ -690,6 +710,8 @@ Overlay.prototype = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//// Private methods ////
|
||||||
|
|
||||||
_hideDone: function() {
|
_hideDone: function() {
|
||||||
let global = Shell.Global.get();
|
let global = Shell.Global.get();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user