.settings
data
js
misc
ui
Makefile.am
altTab.js
appDisplay.js
appIcon.js
button.js
chrome.js
dash.js
dnd.js
docDisplay.js
genericDisplay.js
link.js
lookingGlass.js
main.js
overview.js
panel.js
places.js
runDialog.js
sidebar.js
tweener.js
widget.js
widgetBox.js
windowManager.js
workspaces.js
Makefile.am
po
src
tools
.gitignore
.project
AUTHORS
COPYING
MAINTAINERS
Makefile.am
README
autogen.sh
configure.ac

For some use cases we have other behavior on mouse press and want to manually control when a drag starts. Split out the drag initiation code into startDrag.
267 lines
11 KiB
JavaScript
267 lines
11 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Lang = imports.lang;
|
|
const Signals = imports.signals;
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
const SNAP_BACK_ANIMATION_TIME = 0.25;
|
|
|
|
function _Draggable(actor, manualMode) {
|
|
this._init(actor, manualMode);
|
|
}
|
|
|
|
_Draggable.prototype = {
|
|
_init : function(actor, manualMode) {
|
|
this.actor = actor;
|
|
if (!manualMode)
|
|
this.actor.connect('button-press-event',
|
|
Lang.bind(this, this._onButtonPress));
|
|
this._haveSourceGrab = false;
|
|
},
|
|
|
|
_onButtonPress : function (actor, event) {
|
|
// FIXME: we should make sure it's button 1, but we can't currently
|
|
// check that from JavaScript
|
|
if (Tweener.getTweenCount(actor))
|
|
return false;
|
|
|
|
this._haveSourceGrab = true;
|
|
this._grabActor(actor);
|
|
|
|
let [stageX, stageY] = event.get_coords();
|
|
this._dragStartX = stageX;
|
|
this._dragStartY = stageY;
|
|
|
|
return false;
|
|
},
|
|
|
|
_grabActor : function (actor) {
|
|
Clutter.grab_pointer(actor);
|
|
|
|
// We intercept motion and button-release events so that when
|
|
// 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) {
|
|
Clutter.ungrab_pointer();
|
|
actor.disconnect(this._onEventId);
|
|
},
|
|
|
|
_onEvent : function (actor, event) {
|
|
if (this._dragActor) {
|
|
if (actor != this._dragActor )
|
|
return false;
|
|
} else if (actor != this.actor)
|
|
return false;
|
|
|
|
if (event.type() == Clutter.EventType.BUTTON_RELEASE)
|
|
return this._onButtonRelease(actor, event);
|
|
else if (event.type() == Clutter.EventType.MOTION)
|
|
return this._onMotion(actor, event);
|
|
else
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* startDrag:
|
|
* @actor: Origin actor for drag and drop
|
|
* @stageX: X coordinate of event
|
|
* @stageY: Y coordinate of event
|
|
* @time: Event timestamp
|
|
*
|
|
* Directly initiate a drag and drop operation from the given actor.
|
|
* This function is useful to call if you've specified manualMode
|
|
* for the draggable.
|
|
*/
|
|
startDrag: function (actor, stageX, stageY, time) {
|
|
this.emit('drag-begin', time);
|
|
|
|
this._dragStartX = stageX;
|
|
this._dragStartY = stageY;
|
|
|
|
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
|
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();
|
|
// If the user dragged from the source, then position
|
|
// the dragActor over it. Otherwise, center it
|
|
// around the pointer
|
|
let [sourceX, sourceY] = this._dragActorSource.get_transformed_position();
|
|
let [sourceWidth, sourceHeight] = this._dragActorSource.get_transformed_size();
|
|
let x, y;
|
|
if (stageX > sourceX && stageX <= sourceX + sourceWidth &&
|
|
stageY > sourceY && stageY <= sourceY + sourceHeight) {
|
|
x = sourceX;
|
|
y = sourceY;
|
|
} else {
|
|
x = stageX - this._dragActor.width / 2;
|
|
y = stageY - this._dragActor.height / 2;
|
|
}
|
|
this._dragActor.set_position(x, y);
|
|
} else {
|
|
this._dragActorSource = this.actor;
|
|
}
|
|
this._dragOrigParent = undefined;
|
|
if (this._haveSourceGrab) {
|
|
this._haveSourceGrab = false;
|
|
this._ungrabActor(actor);
|
|
}
|
|
this._grabActor(this._dragActor);
|
|
|
|
this._dragOffsetX = this._dragActor.x - this._dragStartX;
|
|
this._dragOffsetY = this._dragActor.y - this._dragStartY;
|
|
} else {
|
|
this._dragActor = actor;
|
|
this._dragActorSource = undefined;
|
|
this._dragOrigParent = actor.get_parent();
|
|
this._dragOrigX = this._dragActor.x;
|
|
this._dragOrigY = this._dragActor.y;
|
|
this._dragOrigScale = this._dragActor.scale_x;
|
|
|
|
let [actorStageX, actorStageY] = actor.get_transformed_position();
|
|
this._dragOffsetX = actorStageX - this._dragStartX;
|
|
this._dragOffsetY = actorStageY - this._dragStartY;
|
|
|
|
// Set the actor's scale such that it will keep the same
|
|
// transformed size when it's reparented to the stage
|
|
let [scaledWidth, scaledHeight] = actor.get_transformed_size();
|
|
actor.set_scale(scaledWidth / actor.width,
|
|
scaledHeight / actor.height);
|
|
}
|
|
|
|
this._dragActor.reparent(actor.get_stage());
|
|
this._dragActor.raise_top();
|
|
},
|
|
|
|
_onMotion : function (actor, event) {
|
|
let [stageX, stageY] = event.get_coords();
|
|
|
|
// If we haven't begun a drag, see if the user has moved the
|
|
// mouse enough to trigger a drag
|
|
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
|
if (!this._dragActor &&
|
|
(Math.abs(stageX - this._dragStartX) > threshold ||
|
|
Math.abs(stageY - this._dragStartY) > threshold)) {
|
|
this.startDrag(actor, stageX, stageY, event.get_time());
|
|
}
|
|
|
|
// If we are dragging, update the position
|
|
if (this._dragActor) {
|
|
this._dragActor.set_position(stageX + this._dragOffsetX,
|
|
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(Clutter.PickMode.ALL,
|
|
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 - targX) / target.scale_x,
|
|
(stageY + this._dragOffsetY - targY) / target.scale_y,
|
|
event.get_time());
|
|
}
|
|
target = target.get_parent();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
_onButtonRelease : function (actor, event) {
|
|
this._ungrabActor(actor);
|
|
|
|
let dragging = (actor == this._dragActor);
|
|
this._dragActor = undefined;
|
|
|
|
if (!dragging)
|
|
return false;
|
|
|
|
// Find a drop target
|
|
actor.hide();
|
|
let [dropX, dropY] = event.get_coords();
|
|
let target = actor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
|
dropX, dropY);
|
|
actor.show();
|
|
while (target) {
|
|
if (target._delegate && target._delegate.acceptDrop) {
|
|
let [targX, targY] = target.get_transformed_position();
|
|
if (target._delegate.acceptDrop(this.actor._delegate, actor,
|
|
(dropX - targX) / target.scale_x,
|
|
(dropY - targY) / target.scale_y,
|
|
event.get_time())) {
|
|
// If it accepted the drop without taking the actor,
|
|
// destroy it.
|
|
if (actor.get_parent() == actor.get_stage())
|
|
actor.destroy();
|
|
|
|
this.emit('drag-end', event.get_time(), true);
|
|
return true;
|
|
}
|
|
}
|
|
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
|
|
Tweener.addTween(actor,
|
|
{ x: snapBackX,
|
|
y: snapBackY,
|
|
time: SNAP_BACK_ANIMATION_TIME,
|
|
transition: "easeOutQuad",
|
|
onComplete: this._onSnapBackComplete,
|
|
onCompleteScope: this,
|
|
onCompleteParams: [actor, event.get_time()]
|
|
});
|
|
return true;
|
|
},
|
|
|
|
_onSnapBackComplete : function (dragActor, eventTime) {
|
|
if (this._dragOrigParent) {
|
|
dragActor.reparent(this._dragOrigParent);
|
|
dragActor.set_scale(this._dragOrigScale, this._dragOrigScale);
|
|
dragActor.set_position(this._dragOrigX, this._dragOrigY);
|
|
} else {
|
|
dragActor.destroy();
|
|
}
|
|
this.emit('drag-end', eventTime, false);
|
|
}
|
|
};
|
|
|
|
Signals.addSignalMethods(_Draggable.prototype);
|
|
|
|
/**
|
|
* makeDraggable:
|
|
* @actor: Source actor
|
|
* @manualMode: If given, do not automatically start drag and drop on click
|
|
*
|
|
* Create an object which controls drag and drop for the given actor.
|
|
*/
|
|
function makeDraggable(actor, manualMode) {
|
|
return new _Draggable(actor, manualMode);
|
|
}
|