Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / resizer / ResizeTracker.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.resizer.ResizeTracker
17  * @extends Ext.dd.DragTracker
18  * Private utility class for Ext.resizer.Resizer.
19  * @private
20  */
21 Ext.define('Ext.resizer.ResizeTracker', {
22     extend: 'Ext.dd.DragTracker',
23     dynamic: true,
24     preserveRatio: false,
25
26     // Default to no constraint
27     constrainTo: null,
28     
29     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
30
31     constructor: function(config) {
32         var me = this;
33
34         if (!config.el) {
35             if (config.target.isComponent) {
36                 me.el = config.target.getEl();
37             } else {
38                 me.el = config.target;
39             }
40         }
41         this.callParent(arguments);
42
43         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
44         if (me.preserveRatio && me.minWidth && me.minHeight) {
45             var widthRatio = me.minWidth / me.el.getWidth(),
46                 heightRatio = me.minHeight / me.el.getHeight();
47
48             // largest ratio of minimum:size must be preserved.
49             // So if a 400x200 pixel image has
50             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
51             if (heightRatio > widthRatio) {
52                 me.minWidth = me.el.getWidth() * heightRatio;
53             } else {
54                 me.minHeight = me.el.getHeight() * widthRatio;
55             }
56         }
57
58         // If configured as throttled, create an instance version of resize which calls
59         // a throttled function to perform the resize operation.
60         if (me.throttle) {
61             var throttledResizeFn = Ext.Function.createThrottled(function() {
62                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
63                 }, me.throttle);
64
65             me.resize = function(box, direction, atEnd) {
66                 if (atEnd) {
67                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
68                 } else {
69                     throttledResizeFn.apply(null, arguments);
70                 }
71             };
72         }
73     },
74
75     onBeforeStart: function(e) {
76         // record the startBox
77         this.startBox = this.el.getBox();
78     },
79
80     /**
81      * @private
82      * Returns the object that will be resized on every mousemove event.
83      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
84      */
85     getDynamicTarget: function() {
86         var me = this,
87             target = me.target;
88             
89         if (me.dynamic) {
90             return target;
91         } else if (!me.proxy) {
92             me.proxy = me.createProxy(target);
93         }
94         me.proxy.show();
95         return me.proxy;
96     },
97     
98     /**
99      * Create a proxy for this resizer
100      * @param {Ext.Component/Ext.Element} target The target
101      * @return {Ext.Element} A proxy element
102      */
103     createProxy: function(target){
104         var proxy,
105             cls = this.proxyCls,
106             renderTo;
107             
108         if (target.isComponent) {
109             proxy = target.getProxy().addCls(cls);
110         } else {
111             renderTo = Ext.getBody();
112             if (Ext.scopeResetCSS) {
113                 renderTo = Ext.getBody().createChild({
114                     cls: Ext.baseCSSPrefix + 'reset'
115                 });
116             }
117             proxy = target.createProxy({
118                 tag: 'div',
119                 cls: cls,
120                 id: target.id + '-rzproxy'
121             }, renderTo);
122         }
123         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
124         return proxy;
125     },
126
127     onStart: function(e) {
128         // returns the Ext.ResizeHandle that the user started dragging
129         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
130
131         // If we are using a proxy, ensure it is sized.
132         if (!this.dynamic) {
133             this.resize(this.startBox, {
134                 horizontal: 'none',
135                 vertical: 'none'
136             });
137         }
138     },
139
140     onDrag: function(e) {
141         // dynamic resizing, update dimensions during resize
142         if (this.dynamic || this.proxy) {
143             this.updateDimensions(e);
144         }
145     },
146
147     updateDimensions: function(e, atEnd) {
148         var me = this,
149             region = me.activeResizeHandle.region,
150             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
151             box = me.startBox,
152             ratio,
153             widthAdjust = 0,
154             heightAdjust = 0,
155             snappedWidth,
156             snappedHeight,
157             adjustX = 0,
158             adjustY = 0,
159             dragRatio,
160             horizDir = offset[0] < 0 ? 'right' : 'left',
161             vertDir = offset[1] < 0 ? 'down' : 'up',
162             oppositeCorner,
163             axis; // 1 = x, 2 = y, 3 = x and y.
164
165         switch (region) {
166             case 'south':
167                 heightAdjust = offset[1];
168                 axis = 2;
169                 break;
170             case 'north':
171                 heightAdjust = -offset[1];
172                 adjustY = -heightAdjust;
173                 axis = 2;
174                 break;
175             case 'east':
176                 widthAdjust = offset[0];
177                 axis = 1;
178                 break;
179             case 'west':
180                 widthAdjust = -offset[0];
181                 adjustX = -widthAdjust;
182                 axis = 1;
183                 break;
184             case 'northeast':
185                 heightAdjust = -offset[1];
186                 adjustY = -heightAdjust;
187                 widthAdjust = offset[0];
188                 oppositeCorner = [box.x, box.y + box.height];
189                 axis = 3;
190                 break;
191             case 'southeast':
192                 heightAdjust = offset[1];
193                 widthAdjust = offset[0];
194                 oppositeCorner = [box.x, box.y];
195                 axis = 3;
196                 break;
197             case 'southwest':
198                 widthAdjust = -offset[0];
199                 adjustX = -widthAdjust;
200                 heightAdjust = offset[1];
201                 oppositeCorner = [box.x + box.width, box.y];
202                 axis = 3;
203                 break;
204             case 'northwest':
205                 heightAdjust = -offset[1];
206                 adjustY = -heightAdjust;
207                 widthAdjust = -offset[0];
208                 adjustX = -widthAdjust;
209                 oppositeCorner = [box.x + box.width, box.y + box.height];
210                 axis = 3;
211                 break;
212         }
213
214         var newBox = {
215             width: box.width + widthAdjust,
216             height: box.height + heightAdjust,
217             x: box.x + adjustX,
218             y: box.y + adjustY
219         };
220
221         // Snap value between stops according to configured increments
222         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
223         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
224         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
225             switch (region) {
226                 case 'northeast':
227                     newBox.y -= snappedHeight - newBox.height;
228                     break;
229                 case 'north':
230                     newBox.y -= snappedHeight - newBox.height;
231                     break;
232                 case 'southwest':
233                     newBox.x -= snappedWidth - newBox.width;
234                     break;
235                 case 'west':
236                     newBox.x -= snappedWidth - newBox.width;
237                     break;
238                 case 'northwest':
239                     newBox.x -= snappedWidth - newBox.width;
240                     newBox.y -= snappedHeight - newBox.height;
241             }
242             newBox.width = snappedWidth;
243             newBox.height = snappedHeight;
244         }
245
246         // out of bounds
247         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
248             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
249
250             // Re-adjust the X position if we were dragging the west side
251             if (adjustX) {
252                 newBox.x = box.x + (box.width - newBox.width);
253             }
254         } else {
255             me.lastX = newBox.x;
256         }
257         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
258             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
259
260             // Re-adjust the Y position if we were dragging the north side
261             if (adjustY) {
262                 newBox.y = box.y + (box.height - newBox.height);
263             }
264         } else {
265             me.lastY = newBox.y;
266         }
267
268         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
269         if (me.preserveRatio || e.shiftKey) {
270             var newHeight,
271                 newWidth;
272
273             ratio = me.startBox.width / me.startBox.height;
274
275             // Calculate aspect ratio constrained values.
276             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
277             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
278
279             // X axis: width-only change, height must obey
280             if (axis == 1) {
281                 newBox.height = newHeight;
282             }
283
284             // Y axis: height-only change, width must obey
285             else if (axis == 2) {
286                 newBox.width = newWidth;
287             }
288
289             // Corner drag.
290             else {
291                 // Drag ratio is the ratio of the mouse point from the opposite corner.
292                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
293                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
294
295                 // If drag ratio > aspect ratio then width is dominant and height must obey
296                 if (dragRatio > ratio) {
297                     newBox.height = newHeight;
298                 } else {
299                     newBox.width = newWidth;
300                 }
301
302                 // Handle dragging start coordinates
303                 if (region == 'northeast') {
304                     newBox.y = box.y - (newBox.height - box.height);
305                 } else if (region == 'northwest') {
306                     newBox.y = box.y - (newBox.height - box.height);
307                     newBox.x = box.x - (newBox.width - box.width);
308                 } else if (region == 'southwest') {
309                     newBox.x = box.x - (newBox.width - box.width);
310                 }
311             }
312         }
313
314         if (heightAdjust === 0) {
315             vertDir = 'none';
316         }
317         if (widthAdjust === 0) {
318             horizDir = 'none';
319         }
320         me.resize(newBox, {
321             horizontal: horizDir,
322             vertical: vertDir
323         }, atEnd);
324     },
325
326     getResizeTarget: function(atEnd) {
327         return atEnd ? this.target : this.getDynamicTarget();
328     },
329
330     resize: function(box, direction, atEnd) {
331         var target = this.getResizeTarget(atEnd);
332         if (target.isComponent) {
333             if (target.floating) {
334                 target.setPagePosition(box.x, box.y);
335             }
336             target.setSize(box.width, box.height);
337         } else {
338             target.setBox(box);
339             // update the originalTarget if this was wrapped.
340             if (this.originalTarget) {
341                 this.originalTarget.setBox(box);
342             }
343         }
344     },
345
346     onEnd: function(e) {
347         this.updateDimensions(e, true);
348         if (this.proxy) {
349             this.proxy.hide();
350         }
351     }
352 });
353