9
9
const SNAP_BACK_ANIMATION_TIME = 0.25;
11
let eventHandlerActor = null;
12
let currentDraggable = null;
14
function _getEventHandlerActor() {
15
if (!eventHandlerActor) {
16
eventHandlerActor = new Clutter.Rectangle();
17
eventHandlerActor.width = 0;
18
eventHandlerActor.height = 0;
19
global.stage.add_actor(eventHandlerActor);
20
// We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
21
// when you've grabbed the pointer.
22
eventHandlerActor.connect('event',
23
function(actor, event) {
24
return currentDraggable._onEvent(actor, event);
27
return eventHandlerActor;
11
30
function _Draggable(actor, manualMode) {
12
31
this._init(actor, manualMode);
40
_grabActor : function (actor) {
41
Clutter.grab_pointer(actor);
43
// We intercept motion and button-release events so that when
44
// you release after dragging, the app doesn't see that and
45
// think you just clicked. We connect to 'event' rather than
46
// 'captured-event' because the capturing phase doesn't happen
47
// when you've grabbed the pointer.
48
this._onEventId = actor.connect('event',
49
Lang.bind(this, this._onEvent));
52
_ungrabActor : function (actor) {
53
Clutter.ungrab_pointer();
54
actor.disconnect(this._onEventId);
57
_onEvent : function (actor, event) {
58
if (this._dragActor) {
59
if (actor != this._dragActor )
63
_grabActor: function() {
64
Clutter.grab_pointer(this.actor);
65
this._onEventId = this.actor.connect('event',
66
Lang.bind(this, this._onEvent));
69
_ungrabActor: function() {
70
Clutter.ungrab_pointer();
71
this.actor.disconnect(this._onEventId);
72
this._onEventId = null;
75
_grabEvents: function() {
76
Clutter.grab_pointer(_getEventHandlerActor());
77
Clutter.grab_keyboard(_getEventHandlerActor());
80
_ungrabEvents: function() {
81
Clutter.ungrab_pointer();
82
Clutter.ungrab_keyboard();
85
_onEvent: function(actor, event) {
86
// We intercept BUTTON_RELEASE event to know that the button was released in case we
87
// didn't start the drag, to drop the draggable in case the drag was in progress, and
88
// to complete the drag and ensure that whatever happens to be under the pointer does
89
// not get triggered if the drag was cancelled with Esc.
90
if (event.type() == Clutter.EventType.BUTTON_RELEASE) {
91
this._buttonDown = false;
92
if (this._dragInProgress) {
93
return this._dragActorDropped(event);
94
} else if (this._dragActor != null && !this._snapBackInProgress) {
95
// Drag must have been cancelled with Esc.
99
// Drag has never started.
61
} else if (actor != this.actor)
103
// We intercept MOTION event to figure out if the drag has started and to draw
104
// this._dragActor under the pointer when dragging is in progress
105
} else if (event.type() == Clutter.EventType.MOTION) {
106
if (this._dragInProgress) {
107
return this._updateDragPosition(event);
108
} else if (this._dragActor == null) {
109
return this._maybeStartDrag(event);
111
// We intercept KEY_PRESS event so that we can process Esc key press to cancel
112
// dragging and ignore all other key presses.
113
} else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragInProgress) {
114
let symbol = event.get_key_symbol();
115
if (symbol == Clutter.Escape) {
116
this._cancelDrag(event.get_time());
64
if (event.type() == Clutter.EventType.BUTTON_RELEASE)
65
return this._onButtonRelease(actor, event);
66
else if (event.type() == Clutter.EventType.MOTION)
67
return this._onMotion(actor, event);
74
* @actor: Origin actor for drag and drop
75
126
* @stageX: X coordinate of event
76
127
* @stageY: Y coordinate of event
77
128
* @time: Event timestamp
112
169
this._dragActorSource = this.actor;
114
171
this._dragOrigParent = undefined;
115
if (this._haveSourceGrab) {
116
this._haveSourceGrab = false;
117
this._ungrabActor(actor);
119
this._grabActor(this._dragActor);
121
173
this._dragOffsetX = this._dragActor.x - this._dragStartX;
122
174
this._dragOffsetY = this._dragActor.y - this._dragStartY;
124
this._dragActor = actor;
176
this._dragActor = this.actor;
125
177
this._dragActorSource = undefined;
126
this._dragOrigParent = actor.get_parent();
178
this._dragOrigParent = this.actor.get_parent();
127
179
this._dragOrigX = this._dragActor.x;
128
180
this._dragOrigY = this._dragActor.y;
129
181
this._dragOrigScale = this._dragActor.scale_x;
131
let [actorStageX, actorStageY] = actor.get_transformed_position();
183
let [actorStageX, actorStageY] = this.actor.get_transformed_position();
132
184
this._dragOffsetX = actorStageX - this._dragStartX;
133
185
this._dragOffsetY = actorStageY - this._dragStartY;
135
187
// Set the actor's scale such that it will keep the same
136
188
// transformed size when it's reparented to the stage
137
let [scaledWidth, scaledHeight] = actor.get_transformed_size();
138
actor.set_scale(scaledWidth / actor.width,
139
scaledHeight / actor.height);
189
let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
190
this.actor.set_scale(scaledWidth / this.actor.width,
191
scaledHeight / this.actor.height);
142
this._dragActor.reparent(actor.get_stage());
194
this._dragActor.reparent(this.actor.get_stage());
143
195
this._dragActor.raise_top();
146
_onMotion : function (actor, event) {
198
_maybeStartDrag: function(event) {
147
199
let [stageX, stageY] = event.get_coords();
149
// If we haven't begun a drag, see if the user has moved the
150
// mouse enough to trigger a drag
201
// See if the user has moved the mouse enough to trigger a drag
151
202
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
152
if (!this._dragActor &&
153
(Math.abs(stageX - this._dragStartX) > threshold ||
203
if ((Math.abs(stageX - this._dragStartX) > threshold ||
154
204
Math.abs(stageY - this._dragStartY) > threshold)) {
155
this.startDrag(actor, stageX, stageY, event.get_time());
205
this.startDrag(stageX, stageY, event.get_time());
206
this._updateDragPosition(event);
212
_updateDragPosition : function (event) {
213
let [stageX, stageY] = event.get_coords();
158
215
// If we are dragging, update the position
159
216
if (this._dragActor) {
160
217
this._dragActor.set_position(stageX + this._dragOffsetX,
161
218
stageY + this._dragOffsetY);
162
219
// Because we want to find out what other actor is located at the current position of this._dragActor,
163
220
// we have to temporarily hide this._dragActor.
164
this._dragActor.hide();
165
let target = actor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
166
stageX + this._dragOffsetX,
167
stageY + this._dragOffsetY);
221
this._dragActor.hide();
222
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
223
stageX + this._dragOffsetX,
224
stageY + this._dragOffsetY);
168
225
this._dragActor.show();
170
227
if (target._delegate && target._delegate.handleDragOver) {
172
229
// We currently loop through all parents on drag-over even if one of the children has handled it.
173
230
// We can check the return value of the function and break the loop if it's true if we don't want
174
231
// to continue checking the parents.
175
target._delegate.handleDragOver(this.actor._delegate, actor,
232
target._delegate.handleDragOver(this.actor._delegate, this._dragActor,
176
233
(stageX + this._dragOffsetX - targX) / target.scale_x,
177
234
(stageY + this._dragOffsetY - targY) / target.scale_y,
178
235
event.get_time());
187
_onButtonRelease : function (actor, event) {
188
this._ungrabActor(actor);
190
let dragging = (actor == this._dragActor);
191
this._dragActor = undefined;
196
// Find a drop target
244
_dragActorDropped: function(event) {
245
// Find a drop target. Because we want to find out what other actor is located at
246
// the current position of this._dragActor, we have to temporarily hide this._dragActor.
247
this._dragActor.hide();
198
248
let [dropX, dropY] = event.get_coords();
199
let target = actor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
249
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
251
this._dragActor.show();
203
253
if (target._delegate && target._delegate.acceptDrop) {
204
254
let [targX, targY] = target.get_transformed_position();
205
if (target._delegate.acceptDrop(this.actor._delegate, actor,
255
if (target._delegate.acceptDrop(this.actor._delegate, this._dragActor,
206
256
(dropX - targX) / target.scale_x,
207
257
(dropY - targY) / target.scale_y,
208
258
event.get_time())) {
209
259
// If it accepted the drop without taking the actor,
211
if (actor.get_parent() == actor.get_stage())
261
if (this._dragActor.get_parent() == this._dragActor.get_stage())
262
this._dragActor.destroy();
264
this._dragInProgress = false;
214
265
this.emit('drag-end', event.get_time(), true);
266
this._dragComplete();
218
270
target = target.get_parent();
273
this._cancelDrag(event.get_time());
278
_cancelDrag: function(eventTime) {
279
this._dragInProgress = false;
221
280
// Snap back to the actor source if the source is still around, snap back
222
281
// to the original location if the actor itself was being dragged or the
223
282
// source is no longer around.
227
286
[snapBackX, snapBackY] = this._dragActorSource.get_transformed_position();
289
this._snapBackInProgress = true;
230
290
// No target, so snap back
231
Tweener.addTween(actor,
291
Tweener.addTween(this._dragActor,
234
294
time: SNAP_BACK_ANIMATION_TIME,
235
295
transition: "easeOutQuad",
236
296
onComplete: this._onSnapBackComplete,
237
297
onCompleteScope: this,
238
onCompleteParams: [actor, event.get_time()]
298
onCompleteParams: [this._dragActor, eventTime]
243
302
_onSnapBackComplete : function (dragActor, eventTime) {