Upgrade to ExtJS 4.0.2 - Released 06/09/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     constructor: function(config) {
30         var me = this;
31
32         if (!config.el) {
33             if (config.target.isComponent) {
34                 me.el = config.target.getEl();
35             } else {
36                 me.el = config.target;
37             }
38         }
39         this.callParent(arguments);
40
41         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
42         if (me.preserveRatio && me.minWidth && me.minHeight) {
43             var widthRatio = me.minWidth / me.el.getWidth(),
44                 heightRatio = me.minHeight / me.el.getHeight();
45
46             // largest ratio of minimum:size must be preserved.
47             // So if a 400x200 pixel image has
48             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
49             if (heightRatio > widthRatio) {
50                 me.minWidth = me.el.getWidth() * heightRatio;
51             } else {
52                 me.minHeight = me.el.getHeight() * widthRatio;
53             }
54         }
55
56         // If configured as throttled, create an instance version of resize which calls
57         // a throttled function to perform the resize operation.
58         if (me.throttle) {
59             var throttledResizeFn = Ext.Function.createThrottled(function() {
60                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
61                 }, me.throttle);
62
63             me.resize = function(box, direction, atEnd) {
64                 if (atEnd) {
65                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
66                 } else {
67                     throttledResizeFn.apply(null, arguments);
68                 }
69             };
70         }
71     },
72
73     onBeforeStart: function(e) {
74         // record the startBox
75         this.startBox = this.el.getBox();
76     },
77
78     /**
79      * @private
80      * Returns the object that will be resized on every mousemove event.
81      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
82      */
83     getDynamicTarget: function() {
84         var d = this.target;
85         if (this.dynamic) {
86             return d;
87         } else if (!this.proxy) {
88             this.proxy = d.isComponent ? d.getProxy().addCls(Ext.baseCSSPrefix + 'resizable-proxy') : d.createProxy({tag: 'div', cls: Ext.baseCSSPrefix + 'resizable-proxy', id: d.id + '-rzproxy'}, Ext.getBody());
89             this.proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
90         }
91         this.proxy.show();
92         return this.proxy;
93     },
94
95     onStart: function(e) {
96         // returns the Ext.ResizeHandle that the user started dragging
97         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
98
99         // If we are using a proxy, ensure it is sized.
100         if (!this.dynamic) {
101             this.resize(this.startBox, {
102                 horizontal: 'none',
103                 vertical: 'none'
104             });
105         }
106     },
107
108     onDrag: function(e) {
109         // dynamic resizing, update dimensions during resize
110         if (this.dynamic || this.proxy) {
111             this.updateDimensions(e);
112         }
113     },
114
115     updateDimensions: function(e, atEnd) {
116         var me = this,
117             region = me.activeResizeHandle.region,
118             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
119             box = me.startBox,
120             ratio,
121             widthAdjust = 0,
122             heightAdjust = 0,
123             snappedWidth,
124             snappedHeight,
125             adjustX = 0,
126             adjustY = 0,
127             dragRatio,
128             horizDir = offset[0] < 0 ? 'right' : 'left',
129             vertDir = offset[1] < 0 ? 'down' : 'up',
130             oppositeCorner,
131             axis; // 1 = x, 2 = y, 3 = x and y.
132
133         switch (region) {
134             case 'south':
135                 heightAdjust = offset[1];
136                 axis = 2;
137                 break;
138             case 'north':
139                 heightAdjust = -offset[1];
140                 adjustY = -heightAdjust;
141                 axis = 2;
142                 break;
143             case 'east':
144                 widthAdjust = offset[0];
145                 axis = 1;
146                 break;
147             case 'west':
148                 widthAdjust = -offset[0];
149                 adjustX = -widthAdjust;
150                 axis = 1;
151                 break;
152             case 'northeast':
153                 heightAdjust = -offset[1];
154                 adjustY = -heightAdjust;
155                 widthAdjust = offset[0];
156                 oppositeCorner = [box.x, box.y + box.height];
157                 axis = 3;
158                 break;
159             case 'southeast':
160                 heightAdjust = offset[1];
161                 widthAdjust = offset[0];
162                 oppositeCorner = [box.x, box.y];
163                 axis = 3;
164                 break;
165             case 'southwest':
166                 widthAdjust = -offset[0];
167                 adjustX = -widthAdjust;
168                 heightAdjust = offset[1];
169                 oppositeCorner = [box.x + box.width, box.y];
170                 axis = 3;
171                 break;
172             case 'northwest':
173                 heightAdjust = -offset[1];
174                 adjustY = -heightAdjust;
175                 widthAdjust = -offset[0];
176                 adjustX = -widthAdjust;
177                 oppositeCorner = [box.x + box.width, box.y + box.height];
178                 axis = 3;
179                 break;
180         }
181
182         var newBox = {
183             width: box.width + widthAdjust,
184             height: box.height + heightAdjust,
185             x: box.x + adjustX,
186             y: box.y + adjustY
187         };
188
189         // Snap value between stops according to configured increments
190         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
191         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
192         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
193             switch (region) {
194                 case 'northeast':
195                     newBox.y -= snappedHeight - newBox.height;
196                     break;
197                 case 'north':
198                     newBox.y -= snappedHeight - newBox.height;
199                     break;
200                 case 'southwest':
201                     newBox.x -= snappedWidth - newBox.width;
202                     break;
203                 case 'west':
204                     newBox.x -= snappedWidth - newBox.width;
205                     break;
206                 case 'northwest':
207                     newBox.x -= snappedWidth - newBox.width;
208                     newBox.y -= snappedHeight - newBox.height;
209             }
210             newBox.width = snappedWidth;
211             newBox.height = snappedHeight;
212         }
213
214         // out of bounds
215         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
216             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
217
218             // Re-adjust the X position if we were dragging the west side
219             if (adjustX) {
220                 newBox.x = box.x + (box.width - newBox.width);
221             }
222         } else {
223             me.lastX = newBox.x;
224         }
225         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
226             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
227
228             // Re-adjust the Y position if we were dragging the north side
229             if (adjustY) {
230                 newBox.y = box.y + (box.height - newBox.height);
231             }
232         } else {
233             me.lastY = newBox.y;
234         }
235
236         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
237         if (me.preserveRatio || e.shiftKey) {
238             var newHeight,
239                 newWidth;
240
241             ratio = me.startBox.width / me.startBox.height;
242
243             // Calculate aspect ratio constrained values.
244             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
245             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
246
247             // X axis: width-only change, height must obey
248             if (axis == 1) {
249                 newBox.height = newHeight;
250             }
251
252             // Y axis: height-only change, width must obey
253             else if (axis == 2) {
254                 newBox.width = newWidth;
255             }
256
257             // Corner drag.
258             else {
259                 // Drag ratio is the ratio of the mouse point from the opposite corner.
260                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
261                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
262
263                 // If drag ratio > aspect ratio then width is dominant and height must obey
264                 if (dragRatio > ratio) {
265                     newBox.height = newHeight;
266                 } else {
267                     newBox.width = newWidth;
268                 }
269
270                 // Handle dragging start coordinates
271                 if (region == 'northeast') {
272                     newBox.y = box.y - (newBox.height - box.height);
273                 } else if (region == 'northwest') {
274                     newBox.y = box.y - (newBox.height - box.height);
275                     newBox.x = box.x - (newBox.width - box.width);
276                 } else if (region == 'southwest') {
277                     newBox.x = box.x - (newBox.width - box.width);
278                 }
279             }
280         }
281
282         if (heightAdjust === 0) {
283             vertDir = 'none';
284         }
285         if (widthAdjust === 0) {
286             horizDir = 'none';
287         }
288         me.resize(newBox, {
289             horizontal: horizDir,
290             vertical: vertDir
291         }, atEnd);
292     },
293
294     getResizeTarget: function(atEnd) {
295         return atEnd ? this.target : this.getDynamicTarget();
296     },
297
298     resize: function(box, direction, atEnd) {
299         var target = this.getResizeTarget(atEnd);
300         if (target.isComponent) {
301             if (target.floating) {
302                 target.setPagePosition(box.x, box.y);
303             }
304             target.setSize(box.width, box.height);
305         } else {
306             target.setBox(box);
307             // update the originalTarget if this was wrapped.
308             if (this.originalTarget) {
309                 this.originalTarget.setBox(box);
310             }
311         }
312     },
313
314     onEnd: function(e) {
315         this.updateDimensions(e, true);
316         if (this.proxy) {
317             this.proxy.hide();
318         }
319     }
320 });
321