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