Bug 594916 - Allow cancelling DND by hitting Esc
Esc used to close the overview and get the DND actor stuck on the desktop when it was pressed when dragging.
This commit is contained in:
parent
0918bdd612
commit
6cae94edcc
@ -781,7 +781,7 @@ BaseWellItem.prototype = {
|
|||||||
if (!hover) {
|
if (!hover) {
|
||||||
if (this.actor.pressed && this._dragStartX != null) {
|
if (this.actor.pressed && this._dragStartX != null) {
|
||||||
this.actor.fake_release();
|
this.actor.fake_release();
|
||||||
this._draggable.startDrag(this.icon.actor, this._dragStartX, this._dragStartY,
|
this._draggable.startDrag(this._dragStartX, this._dragStartY,
|
||||||
Clutter.get_current_event_time());
|
Clutter.get_current_event_time());
|
||||||
} else {
|
} else {
|
||||||
this._dragStartX = null;
|
this._dragStartX = null;
|
||||||
|
213
js/ui/dnd.js
213
js/ui/dnd.js
@ -8,6 +8,25 @@ const Tweener = imports.ui.tweener;
|
|||||||
|
|
||||||
const SNAP_BACK_ANIMATION_TIME = 0.25;
|
const SNAP_BACK_ANIMATION_TIME = 0.25;
|
||||||
|
|
||||||
|
let eventHandlerActor = null;
|
||||||
|
let currentDraggable = null;
|
||||||
|
|
||||||
|
function _getEventHandlerActor() {
|
||||||
|
if (!eventHandlerActor) {
|
||||||
|
eventHandlerActor = new Clutter.Rectangle();
|
||||||
|
eventHandlerActor.width = 0;
|
||||||
|
eventHandlerActor.height = 0;
|
||||||
|
global.stage.add_actor(eventHandlerActor);
|
||||||
|
// We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
|
||||||
|
// when you've grabbed the pointer.
|
||||||
|
eventHandlerActor.connect('event',
|
||||||
|
function(actor, event) {
|
||||||
|
return currentDraggable._onEvent(actor, event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return eventHandlerActor;
|
||||||
|
}
|
||||||
|
|
||||||
function _Draggable(actor, manualMode) {
|
function _Draggable(actor, manualMode) {
|
||||||
this._init(actor, manualMode);
|
this._init(actor, manualMode);
|
||||||
}
|
}
|
||||||
@ -18,7 +37,11 @@ _Draggable.prototype = {
|
|||||||
if (!manualMode)
|
if (!manualMode)
|
||||||
this.actor.connect('button-press-event',
|
this.actor.connect('button-press-event',
|
||||||
Lang.bind(this, this._onButtonPress));
|
Lang.bind(this, this._onButtonPress));
|
||||||
this._haveSourceGrab = false;
|
this._onEventId = null;
|
||||||
|
|
||||||
|
this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
|
||||||
|
this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet.
|
||||||
|
this._snapBackInProgress = false; // The drag has been cancelled and the item is in the process of snapping back.
|
||||||
},
|
},
|
||||||
|
|
||||||
_onButtonPress : function (actor, event) {
|
_onButtonPress : function (actor, event) {
|
||||||
@ -27,8 +50,8 @@ _Draggable.prototype = {
|
|||||||
if (Tweener.getTweenCount(actor))
|
if (Tweener.getTweenCount(actor))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this._haveSourceGrab = true;
|
this._buttonDown = true;
|
||||||
this._grabActor(actor);
|
this._grabActor();
|
||||||
|
|
||||||
let [stageX, stageY] = event.get_coords();
|
let [stageX, stageY] = event.get_coords();
|
||||||
this._dragStartX = stageX;
|
this._dragStartX = stageX;
|
||||||
@ -37,41 +60,69 @@ _Draggable.prototype = {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_grabActor : function (actor) {
|
_grabActor: function() {
|
||||||
Clutter.grab_pointer(actor);
|
Clutter.grab_pointer(this.actor);
|
||||||
|
this._onEventId = this.actor.connect('event',
|
||||||
// We intercept motion and button-release events so that when
|
Lang.bind(this, this._onEvent));
|
||||||
// you release after dragging, the app doesn't see that and
|
|
||||||
// think you just clicked. We connect to 'event' rather than
|
|
||||||
// 'captured-event' because the capturing phase doesn't happen
|
|
||||||
// when you've grabbed the pointer.
|
|
||||||
this._onEventId = actor.connect('event',
|
|
||||||
Lang.bind(this, this._onEvent));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_ungrabActor : function (actor) {
|
_ungrabActor: function() {
|
||||||
Clutter.ungrab_pointer();
|
Clutter.ungrab_pointer();
|
||||||
actor.disconnect(this._onEventId);
|
this.actor.disconnect(this._onEventId);
|
||||||
|
this._onEventId = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onEvent : function (actor, event) {
|
_grabEvents: function() {
|
||||||
if (this._dragActor) {
|
Clutter.grab_pointer(_getEventHandlerActor());
|
||||||
if (actor != this._dragActor )
|
Clutter.grab_keyboard(_getEventHandlerActor());
|
||||||
return false;
|
},
|
||||||
} else if (actor != this.actor)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (event.type() == Clutter.EventType.BUTTON_RELEASE)
|
_ungrabEvents: function() {
|
||||||
return this._onButtonRelease(actor, event);
|
Clutter.ungrab_pointer();
|
||||||
else if (event.type() == Clutter.EventType.MOTION)
|
Clutter.ungrab_keyboard();
|
||||||
return this._onMotion(actor, event);
|
},
|
||||||
else
|
|
||||||
return false;
|
_onEvent: function(actor, event) {
|
||||||
|
// We intercept BUTTON_RELEASE event to know that the button was released in case we
|
||||||
|
// didn't start the drag, to drop the draggable in case the drag was in progress, and
|
||||||
|
// to complete the drag and ensure that whatever happens to be under the pointer does
|
||||||
|
// not get triggered if the drag was cancelled with Esc.
|
||||||
|
if (event.type() == Clutter.EventType.BUTTON_RELEASE) {
|
||||||
|
this._buttonDown = false;
|
||||||
|
if (this._dragInProgress) {
|
||||||
|
return this._dragActorDropped(event);
|
||||||
|
} else if (this._dragActor != null && !this._snapBackInProgress) {
|
||||||
|
// Drag must have been cancelled with Esc.
|
||||||
|
this._dragComplete();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Drag has never started.
|
||||||
|
this._ungrabActor();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We intercept MOTION event to figure out if the drag has started and to draw
|
||||||
|
// this._dragActor under the pointer when dragging is in progress
|
||||||
|
} else if (event.type() == Clutter.EventType.MOTION) {
|
||||||
|
if (this._dragInProgress) {
|
||||||
|
return this._updateDragPosition(event);
|
||||||
|
} else if (this._dragActor == null) {
|
||||||
|
return this._maybeStartDrag(event);
|
||||||
|
}
|
||||||
|
// We intercept KEY_PRESS event so that we can process Esc key press to cancel
|
||||||
|
// dragging and ignore all other key presses.
|
||||||
|
} else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragInProgress) {
|
||||||
|
let symbol = event.get_key_symbol();
|
||||||
|
if (symbol == Clutter.Escape) {
|
||||||
|
this._cancelDrag(event.get_time());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* startDrag:
|
* startDrag:
|
||||||
* @actor: Origin actor for drag and drop
|
|
||||||
* @stageX: X coordinate of event
|
* @stageX: X coordinate of event
|
||||||
* @stageY: Y coordinate of event
|
* @stageY: Y coordinate of event
|
||||||
* @time: Event timestamp
|
* @time: Event timestamp
|
||||||
@ -80,8 +131,14 @@ _Draggable.prototype = {
|
|||||||
* This function is useful to call if you've specified manualMode
|
* This function is useful to call if you've specified manualMode
|
||||||
* for the draggable.
|
* for the draggable.
|
||||||
*/
|
*/
|
||||||
startDrag: function (actor, stageX, stageY, time) {
|
startDrag: function (stageX, stageY, time) {
|
||||||
|
currentDraggable = this;
|
||||||
|
this._dragInProgress = true;
|
||||||
|
|
||||||
this.emit('drag-begin', time);
|
this.emit('drag-begin', time);
|
||||||
|
if (this._onEventId)
|
||||||
|
this._ungrabActor();
|
||||||
|
this._grabEvents();
|
||||||
|
|
||||||
this._dragStartX = stageX;
|
this._dragStartX = stageX;
|
||||||
this._dragStartY = stageY;
|
this._dragStartY = stageY;
|
||||||
@ -112,59 +169,59 @@ _Draggable.prototype = {
|
|||||||
this._dragActorSource = this.actor;
|
this._dragActorSource = this.actor;
|
||||||
}
|
}
|
||||||
this._dragOrigParent = undefined;
|
this._dragOrigParent = undefined;
|
||||||
if (this._haveSourceGrab) {
|
|
||||||
this._haveSourceGrab = false;
|
|
||||||
this._ungrabActor(actor);
|
|
||||||
}
|
|
||||||
this._grabActor(this._dragActor);
|
|
||||||
|
|
||||||
this._dragOffsetX = this._dragActor.x - this._dragStartX;
|
this._dragOffsetX = this._dragActor.x - this._dragStartX;
|
||||||
this._dragOffsetY = this._dragActor.y - this._dragStartY;
|
this._dragOffsetY = this._dragActor.y - this._dragStartY;
|
||||||
} else {
|
} else {
|
||||||
this._dragActor = actor;
|
this._dragActor = this.actor;
|
||||||
this._dragActorSource = undefined;
|
this._dragActorSource = undefined;
|
||||||
this._dragOrigParent = actor.get_parent();
|
this._dragOrigParent = this.actor.get_parent();
|
||||||
this._dragOrigX = this._dragActor.x;
|
this._dragOrigX = this._dragActor.x;
|
||||||
this._dragOrigY = this._dragActor.y;
|
this._dragOrigY = this._dragActor.y;
|
||||||
this._dragOrigScale = this._dragActor.scale_x;
|
this._dragOrigScale = this._dragActor.scale_x;
|
||||||
|
|
||||||
let [actorStageX, actorStageY] = actor.get_transformed_position();
|
let [actorStageX, actorStageY] = this.actor.get_transformed_position();
|
||||||
this._dragOffsetX = actorStageX - this._dragStartX;
|
this._dragOffsetX = actorStageX - this._dragStartX;
|
||||||
this._dragOffsetY = actorStageY - this._dragStartY;
|
this._dragOffsetY = actorStageY - this._dragStartY;
|
||||||
|
|
||||||
// Set the actor's scale such that it will keep the same
|
// Set the actor's scale such that it will keep the same
|
||||||
// transformed size when it's reparented to the stage
|
// transformed size when it's reparented to the stage
|
||||||
let [scaledWidth, scaledHeight] = actor.get_transformed_size();
|
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
|
||||||
actor.set_scale(scaledWidth / actor.width,
|
this.actor.set_scale(scaledWidth / this.actor.width,
|
||||||
scaledHeight / actor.height);
|
scaledHeight / this.actor.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._dragActor.reparent(actor.get_stage());
|
this._dragActor.reparent(this.actor.get_stage());
|
||||||
this._dragActor.raise_top();
|
this._dragActor.raise_top();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMotion : function (actor, event) {
|
_maybeStartDrag: function(event) {
|
||||||
let [stageX, stageY] = event.get_coords();
|
let [stageX, stageY] = event.get_coords();
|
||||||
|
|
||||||
// If we haven't begun a drag, see if the user has moved the
|
// See if the user has moved the mouse enough to trigger a drag
|
||||||
// mouse enough to trigger a drag
|
|
||||||
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
||||||
if (!this._dragActor &&
|
if ((Math.abs(stageX - this._dragStartX) > threshold ||
|
||||||
(Math.abs(stageX - this._dragStartX) > threshold ||
|
|
||||||
Math.abs(stageY - this._dragStartY) > threshold)) {
|
Math.abs(stageY - this._dragStartY) > threshold)) {
|
||||||
this.startDrag(actor, stageX, stageY, event.get_time());
|
this.startDrag(stageX, stageY, event.get_time());
|
||||||
|
this._updateDragPosition(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateDragPosition : function (event) {
|
||||||
|
let [stageX, stageY] = event.get_coords();
|
||||||
|
|
||||||
// If we are dragging, update the position
|
// If we are dragging, update the position
|
||||||
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,
|
// 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.
|
// we have to temporarily hide this._dragActor.
|
||||||
this._dragActor.hide();
|
this._dragActor.hide();
|
||||||
let target = actor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||||
stageX + this._dragOffsetX,
|
stageX + this._dragOffsetX,
|
||||||
stageY + this._dragOffsetY);
|
stageY + this._dragOffsetY);
|
||||||
this._dragActor.show();
|
this._dragActor.show();
|
||||||
while (target) {
|
while (target) {
|
||||||
if (target._delegate && target._delegate.handleDragOver) {
|
if (target._delegate && target._delegate.handleDragOver) {
|
||||||
@ -172,7 +229,7 @@ _Draggable.prototype = {
|
|||||||
// We currently loop through all parents on drag-over even if one of the children has handled it.
|
// 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
|
// 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.
|
// to continue checking the parents.
|
||||||
target._delegate.handleDragOver(this.actor._delegate, actor,
|
target._delegate.handleDragOver(this.actor._delegate, this._dragActor,
|
||||||
(stageX + this._dragOffsetX - targX) / target.scale_x,
|
(stageX + this._dragOffsetX - targX) / target.scale_x,
|
||||||
(stageY + this._dragOffsetY - targY) / target.scale_y,
|
(stageY + this._dragOffsetY - targY) / target.scale_y,
|
||||||
event.get_time());
|
event.get_time());
|
||||||
@ -184,40 +241,42 @@ _Draggable.prototype = {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onButtonRelease : function (actor, event) {
|
_dragActorDropped: function(event) {
|
||||||
this._ungrabActor(actor);
|
// Find a drop target. 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.
|
||||||
let dragging = (actor == this._dragActor);
|
this._dragActor.hide();
|
||||||
this._dragActor = undefined;
|
|
||||||
|
|
||||||
if (!dragging)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Find a drop target
|
|
||||||
actor.hide();
|
|
||||||
let [dropX, dropY] = event.get_coords();
|
let [dropX, dropY] = event.get_coords();
|
||||||
let target = actor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||||
dropX, dropY);
|
dropX, dropY);
|
||||||
actor.show();
|
this._dragActor.show();
|
||||||
while (target) {
|
while (target) {
|
||||||
if (target._delegate && target._delegate.acceptDrop) {
|
if (target._delegate && target._delegate.acceptDrop) {
|
||||||
let [targX, targY] = target.get_transformed_position();
|
let [targX, targY] = target.get_transformed_position();
|
||||||
if (target._delegate.acceptDrop(this.actor._delegate, actor,
|
if (target._delegate.acceptDrop(this.actor._delegate, this._dragActor,
|
||||||
(dropX - targX) / target.scale_x,
|
(dropX - targX) / target.scale_x,
|
||||||
(dropY - targY) / target.scale_y,
|
(dropY - targY) / target.scale_y,
|
||||||
event.get_time())) {
|
event.get_time())) {
|
||||||
// If it accepted the drop without taking the actor,
|
// If it accepted the drop without taking the actor,
|
||||||
// destroy it.
|
// destroy it.
|
||||||
if (actor.get_parent() == actor.get_stage())
|
if (this._dragActor.get_parent() == this._dragActor.get_stage())
|
||||||
actor.destroy();
|
this._dragActor.destroy();
|
||||||
|
|
||||||
|
this._dragInProgress = false;
|
||||||
this.emit('drag-end', event.get_time(), true);
|
this.emit('drag-end', event.get_time(), true);
|
||||||
|
this._dragComplete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target = target.get_parent();
|
target = target.get_parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._cancelDrag(event.get_time());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_cancelDrag: function(eventTime) {
|
||||||
|
this._dragInProgress = false;
|
||||||
// Snap back to the actor source if the source is still around, snap back
|
// 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
|
// to the original location if the actor itself was being dragged or the
|
||||||
// source is no longer around.
|
// source is no longer around.
|
||||||
@ -227,17 +286,17 @@ _Draggable.prototype = {
|
|||||||
[snapBackX, snapBackY] = this._dragActorSource.get_transformed_position();
|
[snapBackX, snapBackY] = this._dragActorSource.get_transformed_position();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._snapBackInProgress = true;
|
||||||
// No target, so snap back
|
// No target, so snap back
|
||||||
Tweener.addTween(actor,
|
Tweener.addTween(this._dragActor,
|
||||||
{ x: snapBackX,
|
{ x: snapBackX,
|
||||||
y: snapBackY,
|
y: snapBackY,
|
||||||
time: SNAP_BACK_ANIMATION_TIME,
|
time: SNAP_BACK_ANIMATION_TIME,
|
||||||
transition: "easeOutQuad",
|
transition: "easeOutQuad",
|
||||||
onComplete: this._onSnapBackComplete,
|
onComplete: this._onSnapBackComplete,
|
||||||
onCompleteScope: this,
|
onCompleteScope: this,
|
||||||
onCompleteParams: [actor, event.get_time()]
|
onCompleteParams: [this._dragActor, eventTime]
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onSnapBackComplete : function (dragActor, eventTime) {
|
_onSnapBackComplete : function (dragActor, eventTime) {
|
||||||
@ -249,6 +308,16 @@ _Draggable.prototype = {
|
|||||||
dragActor.destroy();
|
dragActor.destroy();
|
||||||
}
|
}
|
||||||
this.emit('drag-end', eventTime, false);
|
this.emit('drag-end', eventTime, false);
|
||||||
|
|
||||||
|
this._snapBackInProgress = false;
|
||||||
|
if (!this._buttonDown)
|
||||||
|
this._dragComplete();
|
||||||
|
},
|
||||||
|
|
||||||
|
_dragComplete: function() {
|
||||||
|
this._dragActor = undefined;
|
||||||
|
currentDraggable = null;
|
||||||
|
this._ungrabEvents();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user