1 var jaws = (function(jaws) {
  2 
  3 /**
  4  *
  5  * @class A window (Rect) into a bigger canvas/image. Viewport is always contained within that given image (called the game world). "Field Summary" contains options for the Viewport()-constructor.
  6  *
  7  * @property {int} width  Width of viewport, defaults to canvas width
  8  * @property {int} height Height of viewport, defaults to canvas height
  9  * @property {int} max_x  Maximum x-position for viewport, defaults to canvas width
 10  * @property {int} max_y  Maximum y-position for viewport, defaults to canvas height 
 11  * @property {int} x      X-position for the upper left corner of the viewport
 12  * @property {int} y      Y-position for the upper left corner of the viewport
 13  *
 14  * @example
 15  * // Center viewport around players position (player needs to have x/y attributes)
 16  * // Usefull for sidescrollers
 17  * viewport.centerAround(player)
 18  *
 19  * // Common viewport usage. max_x/max_y could be said to set the "game world size"
 20  * viewport = viewport = new jaws.Viewport({max_x: 400, max_y: 3000})
 21  * player = new jaws.Sprite({x:100, y:400})
 22  * viewport.centerAround(player)
 23  *
 24  * // Draw player relative to the viewport. If viewport is way off, player won't even show up.
 25  * viewport.apply( function() {
 26  *  player.draw()
 27  * });
 28  *
 29  */
 30 
 31 
 32 jaws.Viewport = function ViewPort(options) {
 33   if( !(this instanceof arguments.callee) ) return new arguments.callee( options );
 34 
 35   jaws.parseOptions(this, options, this.default_options)
 36  
 37   /* This is needed cause default_options is set loadtime, we need to get width etc runtime */
 38   if(!this.context) this.context = jaws.context;
 39   if(!this.width)   this.width = jaws.width;
 40   if(!this.height)  this.height = jaws.height;
 41   if(!this.max_x)   this.max_x = jaws.width;
 42   if(!this.max_y)   this.max_y = jaws.height;
 43 
 44   var that = this
 45 
 46   /** Move viewport x pixels horizontally and y pixels vertically */
 47   this.move = function(x, y) {
 48     x && (this.x += x)
 49     y && (this.y += y)
 50     this.verifyPosition()
 51   };
 52   
 53   /** Move viewport to given x/y */
 54   this.moveTo = function(x, y) {
 55     if(!(x==undefined)) { this.x = x }
 56     if(!(y==undefined)) { this.y = y }
 57     this.verifyPosition()
 58   };
 59 
 60   /** 
 61    * Returns true if item is outside viewport 
 62    * @example
 63    *
 64    * if( viewport.isOutside(player)) player.die();
 65    *
 66    * // ... or the more advanced:
 67    * bullets = new SpriteList()
 68    * bullets.push( bullet )
 69    * bullets.removeIf( viewport.isOutside )
 70    *
 71    */
 72   this.isOutside = function(item) {
 73     return(!that.isInside(item))
 74   };
 75 
 76   /** Returns true if item is inside viewport  */
 77   this.isInside = function(item) {
 78     return( item.x >= that.x && item.x <= (that.x + that.width) && item.y >= that.y && item.y <= (that.y + that.height) )
 79   };
 80 
 81   /** Returns true if item is partly (down to 1 pixel) inside viewport */
 82   this.isPartlyInside = function(item) {
 83     var rect = item.rect()
 84     return( rect.right >= that.x && rect.x <= (that.x + that.width) && rect.bottom >= that.y && item.y <= (that.y + that.height) )
 85   };
 86   
 87   /** Returns true of item is left of viewport */
 88   this.isLeftOf = function(item) { return(item.x < that.x)  }
 89  
 90   /** Returns true of item is right of viewport */
 91   this.isRightOf = function(item) { return(item.x > (that.x + that.width) )  }
 92 
 93   /** Returns true of item is above viewport */
 94   this.isAbove = function(item) { return(item.y < that.y)  }
 95 
 96   /** Returns true of item is above viewport */
 97   this.isBelow = function(item) { return(item.y > (that.y + that.height) )  }
 98 
 99 
100   /** 
101    * center the viewport around item. item must respond to x and y for this to work. 
102    * Usefull for sidescrollers when you wan't to keep the player in the center of the screen no matter how he moves.
103    */
104   this.centerAround = function(item) {
105     this.x = Math.floor(item.x - this.width / 2);
106     this.y = Math.floor(item.y - this.height / 2);
107     this.verifyPosition();
108   };
109 
110   /**
111    * force 'item' inside current viewports visible area
112    * using 'buffer' as indicator how close to the 'item' is allowed to go
113    *
114    * @example
115    *
116    * viewport.move(10,0)                          // scroll forward
117    * viewport.forceInsideVisibleArea(player, 20)  // make sure player doesn't get left behind
118    */
119   this.forceInsideVisibleArea = function(item, buffer) {
120     if(item.x < this.x+buffer)               { item.x = this.x+buffer }
121     if(item.x > this.x+jaws.width-buffer)    { item.x = this.x+jaws.width-buffer }
122     if(item.y < this.y+buffer)               { item.y = this.y+buffer }
123     if(item.y > this.y+jaws.height-buffer)   { item.y = this.y+jaws.height-buffer }
124   }
125   
126   /**
127    * force 'item' inside the limits of the viewport
128    * using 'buffer' as indicator how close to the 'item' is allowed to go
129    *
130    * @example
131    * viewport.forceInside(player, 10) 
132    */
133   this.forceInside = function(item, buffer) {
134     if(item.x < buffer)               { item.x = buffer }
135     if(item.x > this.max_x-buffer)    { item.x = this.max_x-buffer }
136     if(item.y < buffer)               { item.y = buffer }
137     if(item.y > this.max_y-buffer)    { item.y = this.max_y-buffer }
138   }
139 
140 
141   /** 
142   * executes given draw-callback with a translated canvas which will draw items relative to the viewport
143   * 
144   * @example
145   *
146   * viewport.apply( function() {
147   *   player.draw();
148   *   foo.draw();
149   * });
150   * 
151   */
152   this.apply = function(func) {
153     this.context.save()
154     this.context.translate(-this.x, -this.y)
155     func()
156     this.context.restore()
157   };
158 
159   /** 
160    * if obj is an array-like object, iterate through it and call draw() on each item if it's partly inside the viewport 
161    */
162   this.draw = function( obj ) {
163     this.apply( function() {
164       if(obj.forEach) obj.forEach( that.drawIfPartlyInside );
165       else if(obj.draw) that.drawIfPartlyInside(obj);
166       // else if(jaws.isFunction(obj) {};  // add apply()-functionally here?
167     });
168   }
169 
170   /** 
171    * draws all items of 'tile_map' that's lies inside the viewport 
172    * this is simular to viewport.draw( tile_map.all() ) but optmized for Huge game worlds (tile maps)
173    */
174   this.drawTileMap = function( tile_map ) {
175     var sprites = tile_map.atRect({ x: this.x, y: this.y, right: this.x + this.width, bottom: this.y + this.height })
176     this.apply( function() {
177       for(var i=0; i < sprites.length; i++) sprites[i].draw();
178     });
179   }
180 
181   /** draws 'item' if it's partly inside the viewport */
182   this.drawIfPartlyInside = function(item) { 
183     if(that.isPartlyInside(item)) item.draw(); 
184   }
185 
186   /** @private */
187   this.verifyPosition = function() {
188     var max = this.max_x - this.width
189     if(this.x < 0)      { this.x = 0 }
190     if(this.x > max)    { this.x = max }
191 
192     var max = this.max_y - this.height
193     if(this.y < 0)      { this.y = 0 }
194     if(this.y > max)    { this.y = max }
195   };
196  
197   this.moveTo(options.x||0, options.y||0)
198 }
199 
200 jaws.Viewport.prototype.default_options = {
201   context: null,
202   width: null,
203   height: null,
204   max_x: null,
205   max_y: null,
206   x: 0,
207   y: 0
208 };
209 
210 jaws.Viewport.prototype.toString = function() { return "[Viewport " + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ", " + this.width + ", " + this.height + "]" }
211 
212 return jaws;
213 })(jaws || {});
214 
215