1 // ==========================================================================
  2 // Papercube.ZoomView
  3 //
  4 // License:  PaperCube is open source software released under 
  5 //           the MIT License (see license.js)
  6 // ==========================================================================
  7 
  8 require('core');
  9 require('controllers/canvas');
 10 
 11 /** @class
 12 
 13   ZoomView is the preview view and control view for the portal/canvas paradigm 
 14   that allows for resolution independence.
 15 
 16   @extends SC.View
 17   @author Peter Bergstrom
 18   @version 1.0
 19   @copyright 2008-2009 Peter Bergström.
 20 */
 21 Papercube.ZoomView = SC.View.extend(
 22 /** @scope Papercube.ZoomView.prototype */ {
 23 
 24   /**
 25     The portal height, set by the canvasController.
 26   
 27     @property {Integer}
 28     @binding "Papercube.canvasController.portalHeight"
 29   */
 30   heightBinding: "Papercube.canvasController.portalHeight",
 31 
 32   /**
 33     The portal width, set by the canvasController.
 34   
 35     @property {Integer}
 36     @binding "Papercube.canvasController.portalWidth"
 37   */
 38   widthBinding: "Papercube.canvasController.portalWidth",
 39 
 40   /**
 41     The zoom value, set by the canvasController.
 42   
 43     @property {Integer}
 44     @binding "Papercube.canvasController.zoomValue"
 45   */
 46   zoomValueBinding: "Papercube.canvasController.zoomValue",
 47 
 48   /**
 49     Zooming or not, set by the canvasController.
 50   
 51     @property {Boolean}
 52     @binding "Papercube.canvasController.isZooming"
 53   */
 54   isZoomingBinding: "Papercube.canvasController.isZooming",
 55 
 56   /**
 57     The x percentage, set by the canvasController.
 58   
 59     @property {Float}
 60     @binding "Papercube.canvasController.percentageX"
 61   */
 62   percentageXBinding: "Papercube.canvasController.percentageX",
 63 
 64   /**
 65     The y percentage, set by the canvasController.
 66   
 67     @property {Float}
 68     @binding "Papercube.canvasController.percentageY"
 69   */
 70   percentageYBinding: "Papercube.canvasController.percentageY",
 71   
 72   /**
 73     Is the view visible?
 74     
 75     @property {Boolean}
 76     @default NO
 77   */
 78   isVisible: NO,
 79   
 80   /**
 81     Internal boolean that specifies if the zoom has been touched or not. 
 82     
 83     @property {Boolean}
 84     @default NO
 85   */
 86   _zoomDirty: NO,
 87   
 88   /**
 89     The bounds that are used for rendering the zoomView.
 90     
 91     @property {Array}
 92   */
 93   _bounds: {
 94     x: 0,
 95     y: 0,
 96     height: 0,
 97     width: 0
 98   },
 99 
100   /**
101     Action type is either drag or click. The positioning behavior changes depending on what this parameter is.
102     
103     @property {String}
104     @default ''
105   */
106   _actionType: '', 
107   
108   /**
109     Used to offset your mouse pointer's x inside the preview portal view so that it doesn't jump.
110     
111     @property {Integer}
112     @default 0
113   */
114   _ptrXOffset: 0, 
115 
116   /**
117     Used to offset your mouse pointer's y inside the preview portal view so that it doesn't jump.
118     
119     @property {Integer}
120     @default 0
121   */
122   _ptrYOffset: 0, 
123   
124   /**
125     The x-axis percentage.
126     
127     @property {Float}
128     @default 0.5
129   */
130   percentageX: 0.5,
131   
132   /**
133     The y-axis percentage.
134     
135     @property {Float}
136     @default 0.5
137   */
138   percentageY: 0.5,
139 
140   /**
141     The zoom view has two outlets, the canvas and portal, the portal is the visible window.
142 
143     ["canvasPreview", "portalPreview"]
144   */
145   outlets: ["canvasPreview", "portalPreview"],
146 
147   /**
148     This is the DIV element for the preview canvas.
149 
150     @outlet {DOM Element} '.canvas-preview?'
151   */
152   canvasPreview: ".canvas-preview?",
153 
154   /**
155     This is the DIV element for the preview portal.
156 
157     @outlet {DOM Element} '.portal-preview?'
158   */
159   portalPreview: ".portal-preview?",
160   
161   /**
162     Observes isZooming and shows the view it is needed.
163 
164     @observes isZooming
165   */
166   isZoomingDidChange: function()
167   {
168     this.set('isVisible', this.get('isZooming'));
169     setTimeout(this.applyZoomTransition.bind(this), 5);
170     
171     // If you're not zooming, reset the dirty parameter to NO.
172     if(!this.get("isZooming"))
173     {
174       this._zoomDirty = NO;
175     }
176   }.observes('isZooming'),
177 
178   /**
179     Redraw the zoomView as needed based on 'height', 'width', 'zoomValue'.
180 
181     @observes height
182     @observes width
183     @observes zoomValue
184   
185     @returns {Boolean} Returns NO if there is no guid of if the view is not visible.
186   */
187   redrawParamsDidChange: function()
188   {
189     // Only redraw if it is visible.
190     if(!this.get('isVisible')) return NO;
191 
192     // Get height and width parameters.
193     var height = this.get('height');
194     var width = this.get('width');
195     
196     // These are the max values for the view.
197     var lheight = 140;
198     var lwidth = 140;
199     
200     // Grab the zoom value.
201     var zoomValue = this.get('zoomValue');
202 
203     // Set the min length depending on the aspect ratio.
204     if(height > width)
205     {
206       lwidth = parseInt(lheight*(width/height),0);
207     }
208     else
209     {
210       lheight = parseInt(lwidth*(height/width),0);
211     }
212     
213     // Calculate the frame of the zoomView.
214     var h = (lheight+10);
215     var w = (lwidth+20);
216     this.setStyle({height: h+"px", width: w+"px"});
217     this._frame = null;
218     this.get("frame");
219     this._frame.height = h;
220     this._frame.width = w;
221 
222     // Set the position of the canvasPreview.
223     this.canvasPreview.style.right = "10px";
224     this.canvasPreview.style.width = lwidth + "px";
225     this.canvasPreview.style.height = lheight+ "px";
226     
227     // Set the portal bounds.
228     var pWidth = (lwidth/zoomValue);
229     var pHeight = (lheight/zoomValue);
230     var bounds =  {
231       x: (this._frame.x+8),
232       y: 30,
233       height: lheight,
234       width: lwidth,
235       previewWidth: pWidth,
236       previewHeight: pHeight
237     };
238     
239     this._bounds = bounds;
240     
241     // Calculate the preview x and y coordinates.
242     var recalcPercent = NO;
243     if(this._zoomDirty && (this._lastHeight != height || this._lastWidth != width))
244     {
245       var prevX = bounds.x+this.get('percentageX')*lwidth+pWidth/2;
246       var prevY = bounds.y+this.get('percentageY')*lheight+pHeight/2;
247     }
248     else if(this._zoomDirty && this._lastZoomValue != zoomValue)
249     {
250       var prevX = this._lastPrevX;
251       var prevY = this._lastPrevY;
252       recalcPercent = YES;
253     }
254     else
255     {
256       var prevX = bounds.x+lwidth/2;
257       var prevY = bounds.y+lheight/2;
258       recalcPercent = YES;
259     }
260 
261     // Based on the calculated preview values, set the position.
262     this._setPreviewPosition(prevX, prevY);
263     
264     // If you need to recalculate the percentage, then set it back to the controller.
265     if(recalcPercent)
266     {
267       this._setPercentage();
268     }
269     // this.positionPreviewBox((this.get('percentageX')*lwidth-w/2), (this.get('percentageY')*lheight-h/2), lheight, lwidth, zoomValue);
270     
271     // Set last height, width, and zoom value so that you can see if it needs be changed next time.
272     this._lastHeight = height;
273     this._lastWidth = width;
274     this._lastZoomValue = zoomValue;
275     
276   }.observes('height', 'width', 'zoomValue'),
277 
278   /**
279     Add the zoom class so it animates.
280   */
281   applyZoomTransition: function()
282   {
283     this.setClassName('zoom-view-visible', this.get('isZooming'));
284   },
285 
286   /**
287     Set the preview box position and percentages.
288     
289     @param pctX {Integer} The x percentage.
290     @param pctY {Integer} The y percentage.
291   */
292   setParams: function(pctX, pctY)
293   {
294     var bounds = this._bounds;
295     this._setPreviewPosition((bounds.x+bounds.width*pctX), (bounds.y+bounds.height*pctY));
296     this._setPercentage();
297   },
298 
299   /**
300     Update preview box postion.
301     
302     @param pctX {Integer} The x percentage.
303     @param pctY {Integer} The y percentage.
304   */
305   updatePreviewBox: function(pctX, pctY)
306   {
307     var bounds = this._bounds;
308     this._setPreviewPosition(parseInt((bounds.x+bounds.width*pctX+bounds.previewWidth/2),0), parseInt((bounds.y+bounds.height*pctY+bounds.previewHeight/2),0));
309   },
310 
311   /**
312     Set the position and dimensions of the portalPreview.
313     
314     @param x {Integer} The x-position.
315     @param y {Integer} The y-position.
316     @param height {Integer} The box height
317     @param height {Integer} The box width
318   */
319   positionPreviewBox: function(x, y, height, width)
320   {
321     this.portalPreview.style.top = y + "px";
322     this.portalPreview.style.left = x + "px";
323     this.portalPreview.style.width = width + "px";
324     this.portalPreview.style.height = height + "px";
325     this._bounds.previewX = x;
326     this._bounds.previewY = y;
327   },
328   
329   /**
330     Set the preview box position as you're dragging. Make sure it does not leave the bounds of the canvas.
331     
332     
333     @param x {Integer} The x-position.
334     @param y {Integer} The y-position.
335   */
336   _setPreviewPosition: function(x,y)
337   {
338     this._lastPrevX = x;
339     this._lastPrevY = y;
340     var bounds = this._bounds;
341     x = x-bounds.previewWidth/2;
342     y = y-bounds.previewHeight/2;
343     if(y < bounds.y) y = bounds.y;
344     if(y > ((bounds.y+bounds.height)-bounds.previewHeight)) y = ((bounds.y+bounds.height)-bounds.previewHeight);
345 
346     if(x < bounds.x) x = bounds.x;
347     if(x > ((bounds.x+bounds.width)-bounds.previewWidth)) x = ((bounds.x+bounds.width)-bounds.previewWidth)+1;
348 
349     var ly = y-bounds.y;
350     var lx = x-bounds.x;
351 
352     this._bounds.previewX = Math.min(lx+1, bounds.width-bounds.previewWidth+1);
353     this._bounds.previewY = ly+1;
354     this._bounds.ly = ly;
355     this._bounds.lx = lx;
356     
357     // Set the review box position.
358     this.positionPreviewBox(this._bounds.previewX, this._bounds.previewY, bounds.previewHeight, bounds.previewWidth);
359   },
360   
361   /**
362     Set the percentage back to the controller.
363     
364   */
365   _setPercentage: function()
366   {
367     var bounds = this._bounds;
368     this.set('percentageX', 1- (bounds.width-bounds.lx)/bounds.width);
369     this.set('percentageY', 1- (bounds.height-bounds.ly)/bounds.height);
370   },
371 
372   /** 
373     Handle the mouseDown action.
374 
375     @param {DOM Event} evt The mouseDown event.
376   */
377   mouseDown: function(evt)
378   {
379     // If you clicked the canvas, the action is click.
380     if(Element.hasClassName(evt.target, 'canvas-preview')) 
381     {
382       this._actionType = 'click';
383     }
384 
385     // If you clicked the portal, then the action is drag.
386     else if(Element.hasClassName(evt.target, 'portal-preview'))
387     {
388 
389       // In order to prevent annoying jumping set the offset of the pointer x and y so that you can click and drag anywhere in the box.
390       this._ptrXOffset = parseInt(Event.pointerX(evt)-(this._bounds.x+this._bounds.previewX+this._bounds.previewWidth/2),0);
391       this._ptrYOffset = parseInt(Event.pointerY(evt)-(this._bounds.y+this._bounds.previewY+this._bounds.previewHeight/2),0);
392       this._actionType = 'drag';
393       this.portalPreview.className = "portal-preview grabbing";
394     }
395     else 
396     {
397       this._actionType = '';
398       return NO;
399     }
400 
401     return YES;
402   },
403 
404   /** 
405     When it is dragged, handle it.
406 
407     @param {DOM Event} evt The mouseDragged event.
408   */
409   mouseDragged: function(evt)
410   {
411     // We don't do a drag if it is a noop or a click.
412     if(this._actionType == '' || this._actionType == 'click') return NO; 
413 
414     // First position box, then translate into correct coordinates for the actual view.
415     this._setPreviewPosition(Event.pointerX(evt)-this._ptrXOffset, Event.pointerY(evt)-this._ptrYOffset);
416 
417     return YES;
418   },
419 
420   /** 
421     This will end a drag or position after a click.
422     
423     @param {DOM Event} evt The mouseUp event.
424   */
425   mouseUp: function(evt)
426   {
427     this._zoomDirty = YES;
428     this.portalPreview.className = "portal-preview";
429 
430     // If it is a drag, set the final position with the proper click offset.
431     if(this._actionType == 'drag')
432     {
433       this._setPreviewPosition(Event.pointerX(evt)-this._ptrXOffset, Event.pointerY(evt)-this._ptrYOffset);
434     }
435     
436     // If it is a click, then just set it right away.
437     else
438     {
439       this._setPreviewPosition(Event.pointerX(evt), Event.pointerY(evt));
440     }
441     
442     // Since we're done, set the percentage back to the controller.
443     this._setPercentage();
444     
445     this._actionType = '';
446     
447     return YES;
448   }
449 
450 }) ;
451