|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 // ********** |
|
6 // Title: drag.js |
|
7 |
|
8 // ---------- |
|
9 // Variable: drag |
|
10 // The Drag that's currently in process. |
|
11 var drag = { |
|
12 info: null, |
|
13 zIndex: 100, |
|
14 lastMoveTime: 0 |
|
15 }; |
|
16 |
|
17 //---------- |
|
18 //Variable: resize |
|
19 //The resize (actually a Drag) that is currently in process |
|
20 var resize = { |
|
21 info: null, |
|
22 lastMoveTime: 0 |
|
23 }; |
|
24 |
|
25 // ########## |
|
26 // Class: Drag (formerly DragInfo) |
|
27 // Helper class for dragging <Item>s |
|
28 // |
|
29 // ---------- |
|
30 // Constructor: Drag |
|
31 // Called to create a Drag in response to an <Item> draggable "start" event. |
|
32 // Note that it is also used partially during <Item>'s resizable method as well. |
|
33 // |
|
34 // Parameters: |
|
35 // item - The <Item> being dragged |
|
36 // event - The DOM event that kicks off the drag |
|
37 function Drag(item, event) { |
|
38 Utils.assert(item && (item.isAnItem || item.isAFauxItem), |
|
39 'must be an item, or at least a faux item'); |
|
40 |
|
41 this.item = item; |
|
42 this.el = item.container; |
|
43 this.$el = iQ(this.el); |
|
44 this.parent = this.item.parent; |
|
45 this.startPosition = new Point(event.clientX, event.clientY); |
|
46 this.startTime = Date.now(); |
|
47 |
|
48 this.item.isDragging = true; |
|
49 this.item.setZ(999999); |
|
50 |
|
51 this.safeWindowBounds = Items.getSafeWindowBounds(); |
|
52 |
|
53 Trenches.activateOthersTrenches(this.el); |
|
54 }; |
|
55 |
|
56 Drag.prototype = { |
|
57 // ---------- |
|
58 // Function: toString |
|
59 // Prints [Drag (item)] for debug use |
|
60 toString: function Drag_toString() { |
|
61 return "[Drag (" + this.item + ")]"; |
|
62 }, |
|
63 |
|
64 // ---------- |
|
65 // Function: snapBounds |
|
66 // Adjusts the given bounds according to the currently active trenches. Used by <Drag.snap> |
|
67 // |
|
68 // Parameters: |
|
69 // bounds - (<Rect>) bounds |
|
70 // stationaryCorner - which corner is stationary? by default, the top left in LTR mode, |
|
71 // and top right in RTL mode. |
|
72 // "topleft", "bottomleft", "topright", "bottomright" |
|
73 // assumeConstantSize - (boolean) whether the bounds' dimensions are sacred or not. |
|
74 // keepProportional - (boolean) if assumeConstantSize is false, whether we should resize |
|
75 // proportionally or not |
|
76 // checkItemStatus - (boolean) make sure this is a valid item which should be snapped |
|
77 snapBounds: function Drag_snapBounds(bounds, stationaryCorner, assumeConstantSize, keepProportional, checkItemStatus) { |
|
78 if (!stationaryCorner) |
|
79 stationaryCorner = UI.rtl ? 'topright' : 'topleft'; |
|
80 var update = false; // need to update |
|
81 var updateX = false; |
|
82 var updateY = false; |
|
83 var newRect; |
|
84 var snappedTrenches = {}; |
|
85 |
|
86 // OH SNAP! |
|
87 |
|
88 // if we aren't holding down the meta key or have trenches disabled... |
|
89 if (!Keys.meta && !Trenches.disabled) { |
|
90 // snappable = true if we aren't a tab on top of something else, and |
|
91 // there's no active drop site... |
|
92 let snappable = !(this.item.isATabItem && |
|
93 this.item.overlapsWithOtherItems()) && |
|
94 !iQ(".acceptsDrop").length; |
|
95 if (!checkItemStatus || snappable) { |
|
96 newRect = Trenches.snap(bounds, stationaryCorner, assumeConstantSize, |
|
97 keepProportional); |
|
98 if (newRect) { // might be false if no changes were made |
|
99 update = true; |
|
100 snappedTrenches = newRect.snappedTrenches || {}; |
|
101 bounds = newRect; |
|
102 } |
|
103 } |
|
104 } |
|
105 |
|
106 // make sure the bounds are in the window. |
|
107 newRect = this.snapToEdge(bounds, stationaryCorner, assumeConstantSize, |
|
108 keepProportional); |
|
109 if (newRect) { |
|
110 update = true; |
|
111 bounds = newRect; |
|
112 Utils.extend(snappedTrenches, newRect.snappedTrenches); |
|
113 } |
|
114 |
|
115 Trenches.hideGuides(); |
|
116 for (var edge in snappedTrenches) { |
|
117 var trench = snappedTrenches[edge]; |
|
118 if (typeof trench == 'object') { |
|
119 trench.showGuide = true; |
|
120 trench.show(); |
|
121 } |
|
122 } |
|
123 |
|
124 return update ? bounds : false; |
|
125 }, |
|
126 |
|
127 // ---------- |
|
128 // Function: snap |
|
129 // Called when a drag or mousemove occurs. Set the bounds based on the mouse move first, then |
|
130 // call snap and it will adjust the item's bounds if appropriate. Also triggers the display of |
|
131 // trenches that it snapped to. |
|
132 // |
|
133 // Parameters: |
|
134 // stationaryCorner - which corner is stationary? by default, the top left in LTR mode, |
|
135 // and top right in RTL mode. |
|
136 // "topleft", "bottomleft", "topright", "bottomright" |
|
137 // assumeConstantSize - (boolean) whether the bounds' dimensions are sacred or not. |
|
138 // keepProportional - (boolean) if assumeConstantSize is false, whether we should resize |
|
139 // proportionally or not |
|
140 snap: function Drag_snap(stationaryCorner, assumeConstantSize, keepProportional) { |
|
141 var bounds = this.item.getBounds(); |
|
142 bounds = this.snapBounds(bounds, stationaryCorner, assumeConstantSize, keepProportional, true); |
|
143 if (bounds) { |
|
144 this.item.setBounds(bounds, true); |
|
145 return true; |
|
146 } |
|
147 return false; |
|
148 }, |
|
149 |
|
150 // -------- |
|
151 // Function: snapToEdge |
|
152 // Returns a version of the bounds snapped to the edge if it is close enough. If not, |
|
153 // returns false. If <Keys.meta> is true, this function will simply enforce the |
|
154 // window edges. |
|
155 // |
|
156 // Parameters: |
|
157 // rect - (<Rect>) current bounds of the object |
|
158 // stationaryCorner - which corner is stationary? by default, the top left in LTR mode, |
|
159 // and top right in RTL mode. |
|
160 // "topleft", "bottomleft", "topright", "bottomright" |
|
161 // assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not |
|
162 // keepProportional - (boolean) if we are allowed to change the rect's size, whether the |
|
163 // dimensions should scaled proportionally or not. |
|
164 snapToEdge: function Drag_snapToEdge(rect, stationaryCorner, assumeConstantSize, keepProportional) { |
|
165 |
|
166 var swb = this.safeWindowBounds; |
|
167 var update = false; |
|
168 var updateX = false; |
|
169 var updateY = false; |
|
170 var snappedTrenches = {}; |
|
171 |
|
172 var snapRadius = (Keys.meta ? 0 : Trenches.defaultRadius); |
|
173 if (rect.left < swb.left + snapRadius ) { |
|
174 if (stationaryCorner.indexOf('right') > -1 && !assumeConstantSize) |
|
175 rect.width = rect.right - swb.left; |
|
176 rect.left = swb.left; |
|
177 update = true; |
|
178 updateX = true; |
|
179 snappedTrenches.left = 'edge'; |
|
180 } |
|
181 |
|
182 if (rect.right > swb.right - snapRadius) { |
|
183 if (updateX || !assumeConstantSize) { |
|
184 var newWidth = swb.right - rect.left; |
|
185 if (keepProportional) |
|
186 rect.height = rect.height * newWidth / rect.width; |
|
187 rect.width = newWidth; |
|
188 update = true; |
|
189 } else if (!updateX || !Trenches.preferLeft) { |
|
190 rect.left = swb.right - rect.width; |
|
191 update = true; |
|
192 } |
|
193 snappedTrenches.right = 'edge'; |
|
194 delete snappedTrenches.left; |
|
195 } |
|
196 if (rect.top < swb.top + snapRadius) { |
|
197 if (stationaryCorner.indexOf('bottom') > -1 && !assumeConstantSize) |
|
198 rect.height = rect.bottom - swb.top; |
|
199 rect.top = swb.top; |
|
200 update = true; |
|
201 updateY = true; |
|
202 snappedTrenches.top = 'edge'; |
|
203 } |
|
204 if (rect.bottom > swb.bottom - snapRadius) { |
|
205 if (updateY || !assumeConstantSize) { |
|
206 var newHeight = swb.bottom - rect.top; |
|
207 if (keepProportional) |
|
208 rect.width = rect.width * newHeight / rect.height; |
|
209 rect.height = newHeight; |
|
210 update = true; |
|
211 } else if (!updateY || !Trenches.preferTop) { |
|
212 rect.top = swb.bottom - rect.height; |
|
213 update = true; |
|
214 } |
|
215 snappedTrenches.top = 'edge'; |
|
216 delete snappedTrenches.bottom; |
|
217 } |
|
218 |
|
219 if (update) { |
|
220 rect.snappedTrenches = snappedTrenches; |
|
221 return rect; |
|
222 } |
|
223 return false; |
|
224 }, |
|
225 |
|
226 // ---------- |
|
227 // Function: drag |
|
228 // Called in response to an <Item> draggable "drag" event. |
|
229 drag: function Drag_drag(event) { |
|
230 this.snap(UI.rtl ? 'topright' : 'topleft', true); |
|
231 |
|
232 if (this.parent && this.parent.expanded) { |
|
233 var distance = this.startPosition.distance(new Point(event.clientX, event.clientY)); |
|
234 if (distance > 100) { |
|
235 this.parent.remove(this.item); |
|
236 this.parent.collapse(); |
|
237 } |
|
238 } |
|
239 }, |
|
240 |
|
241 // ---------- |
|
242 // Function: stop |
|
243 // Called in response to an <Item> draggable "stop" event. |
|
244 // |
|
245 // Parameters: |
|
246 // immediately - bool for doing the pushAway immediately, without animation |
|
247 stop: function Drag_stop(immediately) { |
|
248 Trenches.hideGuides(); |
|
249 this.item.isDragging = false; |
|
250 |
|
251 if (this.parent && this.parent != this.item.parent) |
|
252 this.parent.closeIfEmpty(); |
|
253 |
|
254 if (this.parent && this.parent.expanded) |
|
255 this.parent.arrange(); |
|
256 |
|
257 if (this.item.parent) |
|
258 this.item.parent.arrange(); |
|
259 |
|
260 if (this.item.isAGroupItem) { |
|
261 this.item.setZ(drag.zIndex); |
|
262 drag.zIndex++; |
|
263 |
|
264 this.item.pushAway(immediately); |
|
265 } |
|
266 |
|
267 Trenches.disactivate(); |
|
268 } |
|
269 }; |