1 /*
  2  *Copyright (c) 2009, TellurianRing.com
  3  *All rights reserved.
  4  *
  5  *Redistribution and use in source and binary forms, with or without modification,
  6  *are permitted provided that the following conditions are met:
  7  *
  8  *   Redistributions of source code must retain the above copyright notice, this
  9  *   list of conditions and the following disclaimer.
 10  *   Redistributions in binary form must reproduce the above copyright notice,
 11  *   this list of conditions and the following disclaimer in the documentation
 12  *   and/or other materials provided with the distribution.
 13  *   Neither the name of the Organization (TellurianRing.com) nor the names of
 14  *   its contributors may be used to endorse or promote products derived from
 15  *   this software without specific prior written permission.
 16  *
 17  *THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 18  *ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 19  *WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 20  *DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 21  *ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 22  *(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 23  *LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 24  *ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 25  *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 26  *SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 27  */
 28 /**                                                  
 29  * @class A Path builder which can be used to draw pretty much any shape. Due to
 30  * current Canvas path restrictions, a Path's width and height are always equal
 31  * to the width and height passed into the Path factory function. This is unlike
 32  * other Groups in where the Groups bounds is calculated based on it's sub content.
 33  * @extends Group
 34  * @example // A Paths bounds is equal to the bounds passed in.
 35  * var path = Path({ x: 10, y: 10, w: 25, h: 30 });
 36  * alert(path.getBounds().x == 10 && path.getBounds().w == 25); // alerts true
 37  * path.moveBy({ x: 10 });
 38  * alert(path.getBounds().x == 20); // alerts true
 39  * <script type="text/javascript">
 40  *    function path_example_1() {
 41  *       var path = Path({ x: 10, y: 10, w: 25, h: 30 });
 42  *       alert(path.getBounds().x == 10 && path.getBounds().w == 25); // alerts true
 43  *       path.moveBy({ x: 10 });
 44  *       alert(path.getBounds().x == 20); // alerts true
 45  *    }
 46  * </script>
 47  * <button onclick="path_example_1()">Prove It!</button>
 48  * @example
 49  * // Creating a circle.
 50  * Path({ fill: "#CCCCCC" }).jumpTo(75, 50)
 51  *    .arc(50, 50, 25, 0, 360 * (Math.PI / 180), true)
 52  * <div id="path_example_2"></div>
 53  * <script type="text/javascript">
 54  *    Stage({
 55  *       container_id: "path_example_2",
 56  *       width: 100, height: 100,
 57  *       update_time: 10000,
 58  *       scene: Scene({
 59  *          content: [
 60  *             Path({ fill: "#CCCCCC" }).jumpTo(75, 50)
 61  *                .arc(50, 50, 25, 0, 360 * (Math.PI / 180), true)
 62  *          ]
 63  *       })
 64  *    });
 65  * </script>
 66  * @example
 67  * // Creating a box.
 68  * Path({x: 10, y: 10, stroke: "#333333"})
 69  *    .lineTo(50, 50)
 70  *    .lineTo(50, 90)
 71  *    .lineTo(10, 90)
 72  *    .lineTo(10, 10)
 73  * <div id="path_example_3"></div>
 74  * <script type="text/javascript">
 75  *    Stage({
 76  *       container_id: "path_example_3",
 77  *       width: 100, height: 100,
 78  *       update_time: 10000,
 79  *       scene: Scene({
 80  *          content: [
 81  *             Path({x: 25, y: 25, stroke: "#333333"})
 82  *                .lineTo(25, 75)
 83  *                .lineTo(75, 75)
 84  *                .lineTo(75, 25)
 85  *                .lineTo(25, 25)
 86  *          ]
 87  *       })
 88  *    });
 89  * </script>
 90  *  @example
 91  * // Splitting the builder across multiple lines.
 92  * var path = Path({x: 10, y: 10, stroke: "#333333"});
 93  * path.lineTo(50, 50);
 94  * path.lineTo(50, 90);
 95  * path.lineTo(10, 90);
 96  * path.lineTo(10, 10);
 97  * <div id="path_example_4"></div>
 98  * <script type="text/javascript">
 99  *    var path = Path({x: 10, y: 10, stroke: "#333333"});
100  *    path.lineTo(50, 50);
101  *    path.lineTo(50, 90);
102  *    path.lineTo(10, 90);
103  *    path.lineTo(10, 10);
104  *    
105  *    Stage({
106  *       container_id: "path_example_4",
107  *       width: 100, height: 100,
108  *       update_time: 10000,
109  *       scene: Scene({
110  *          content: [ path ]
111  *       })
112  *    });
113  * </script>
114  * @extends Group
115  * @param {Object} _details A JSON Object with these properties.
116  * <table cellpadding="0" cellspacing="1" border="0" class="constructor_details">
117  *    <tr><th>Property</th>     <th>Required</th> <th>Default</th></tr>
118  *    <tr><td>fill</td>         <td>no</td>       <td>transparent</td></tr>
119  *    <tr><td>stroke</td>       <td>no</td>       <td>#000000</td></tr>
120  *    <tr><td>stroke_width</td> <td>no</td>       <td>1</td></tr>
121  * </table>
122  */
123 function Path(_details) { // {{{
124    /** @constructs */
125    function _Path(_details) { // {{{
126       // Private members {{{
127       var fill = check(_details.fill, "transparent");
128       var stroke = check(_details.stroke, "#000000");
129       var stroke_width = check(_details.stroke_width, 1);
130       // }}}
131       // Public Members {{{
132       /**
133        * Dreates an arc (based on a circle) in this path.
134        * @function
135        * @param _x The center x coord. of the circle.
136        * @param _y The center y coord. of the circle.
137        * @param _start The starting angle of this arc in radians.
138        * @param _end The ending angle of this arc in radians.
139        * @param _clockwise Should this arc go clockwise.
140        */
141       this.arc = function(_x, _y, _radius, _start, _end, _clockwise) {
142          this.getContent().push(Arc({x: _x, y: _y, radius: _radius, start: _start, end: _end, clockwise: _clockwise}));
143          return this;
144       }
145       /**
146        * Draws a bezier curve from the current position of the path to the
147        * given coordinates.
148        * @function
149        * @param _cpx1 The control point x1.
150        * @param _cpy1 The control point y1.
151        * @param _cpx2 The control point x2.
152        * @param _cpy2 The control point y2.
153        * @param _x The x coord. to draw to.
154        * @param _y The y coord. to draw to.
155        */
156       this.beziTo = function(_cpx1, _cpy1, _cpx2, _cpy2, _x, _y) { // {{{
157          this.getContent().push(BezierTo({cpx1: _cpx1, cpy1: _cpy1, cpx2: _cpx2, cpy2: _cpy2, x: _x, y: _y}));
158          return this;
159       } // }}} lineTo
160 
161       // Overridden to draw this path.
162       this.draw = function(_context) {
163          var bounds = this.getBounds();
164          var content = this.getContent();
165          _context.save();
166          _context.beginPath();
167          _context.moveTo(bounds.x, bounds.y);
168          for(var index in content) {
169             var component = content[index];
170             component.draw(_context);
171          }
172          if(exists(fill)) {
173             _context.fillStyle = fill;
174             _context.fill();
175          }
176          if(exists(stroke)) {
177             _context.strokeStyle = stroke;
178             _context.lineWidth = stroke_width;
179             _context.stroke();
180          }
181          _context.closePath();
182          _context.restore();
183       }
184       /**
185        * Moves the next part of this path to the given coordinates.
186        * <b>This function does not draw anything.</b>
187        * @function
188        * @param _x The x coord. to jump to.
189        * @param _y The y coord. to jump to.
190        */
191       this.jumpTo = function(_x, _y) { // {{{
192          this.getContent().push(JumpTo({x: _x, y: _y}));
193          return this;
194          } // }}} jumpTo
195       /**
196        * Draws a line from the current position of the path to the given coordinates.
197        * @function
198        * @param _x The x coord. to draw to.
199        * @param _y The y coord. to draw to.
200        */
201       this.lineTo = function(_x, _y) { // {{{
202          this.getContent().push(LineTo({x: _x, y: _y}));
203          return this;
204       } // }}} lineTo
205       /** 
206        * Draws a quadratic curve from the current position of the path to the
207        * given coordinates.
208        * @function
209        * @param _cpx The control point x.
210        * @param _cpy The control point x.
211        * @param _x The x coord. to draw to.
212        * @param _y The y coord. to draw to.
213        */
214       this.quadTo = function(_cpx, _cpy, _x, _y) { // {{{
215          this.getContent().push(QuadraticTo({cpx: _cpx, cpy: _cpy, x: _x, y: _y}));
216          return this;
217       } // }}} quadTo
218       
219       // Overriden because Group's update method calculates bounds based on
220       // sub content.
221       this.update = function(_runtime) {
222          this.updateBase(_runtime);
223       }
224       // }}}
225    } // }}} _Path
226    
227    
228    /**
229     * Draws an arc from the current location to a new one.
230     * @private
231     */
232    function Arc(_details) { // {{{
233       /** @constructs */
234       function _Arc(_details) { // {{{
235          // Private Members {{{
236          var radius = check(_details.radius, 0);
237          var start = check(_details.start, 0);
238          var end = check(_details.end, 0);
239          var clockwise = check(_details.clockwise, true);
240          // }}} Private Members
241          // Public Members {{{
242          this.draw = function(_context) { // {{{
243             var bounds = this.getBounds();
244             // arc function asks for anti-clockwise parameters. So we just
245             // negate the clockwise value.
246             _context.arc(bounds.x, bounds.y, radius, start, end, !clockwise);
247          } // }}} draw
248          // }}} Public Members
249       } // }}} _Arc
250       SceneContent(_details).extend(_Arc);
251       var theArc = new _Arc(_details);
252       return theArc;
253    } // }}} Arc
254    
255    /**
256     * Draws a Bezier curve from the current location to a new one.
257     * @private
258     */
259    function BezierTo(_details) { // {{{
260       /** @constructs */
261       function _BezierTo(_details) { // {{{
262          // Private Members {{{
263          var cpx1 = check(_details.cpx1, 0);
264          var cpy1 = check(_details.cpy1, 0);
265          var cpx2 = check(_details.cpx2, 0);
266          var cpy2 = check(_details.cpy2, 0);
267          // }}} Private Members
268          // Public Members {{{
269          this.draw = function(_context) { // {{{
270             var bounds = this.getBounds();
271             _context.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, bounds.x, bounds.y);
272          } // }}} draw
273          // }}} Public Members
274       } // }}} _BezierTo
275       SceneContent(_details).extend(_BezierTo);
276       var theBezierTo = new _BezierTo(_details);
277       return theBezierTo;
278    } // }}} BezierTo
279    
280    /**
281     * Moves the context to a new location.
282     * @private
283     */
284    function JumpTo(_details) { // {{{
285       /** @constructs */
286       function _JumpTo(_details) { // {{{
287          // Public Members {{{
288          this.draw = function(_context) { // {{{
289             var bounds = this.getBounds();
290             _context.moveTo(bounds.x, bounds.y);
291          } // }}} draw
292          // }}} Public Members
293       } // }}} _JumpTo
294       SceneContent(_details).extend(_JumpTo);
295       var theJumpTo = new _JumpTo(_details);
296       return theJumpTo;
297    } // }}} JumpTo
298    
299    /**
300     * Draws a line from the current position to a new one.
301     * @private
302     */
303    function LineTo(_details) { // {{{
304       /** @constructs */
305       function _LineTo(_details) { // {{{
306          // Public Members {{{
307          this.draw = function(_context) { // {{{
308             var bounds = this.getBounds();
309             _context.lineTo(bounds.x, bounds.y);
310          } // }}} draw
311          // }}} Public Members
312       } // }}} _LineTo
313       SceneContent(_details).extend(_LineTo);
314       var theLineTo = new _LineTo(_details);
315       return theLineTo;
316    } // }}} LineTo
317    
318    /**
319     * Draws a Quadratic curve from the current location to a new one.
320     * @private
321     */
322    function QuadraticTo(_details) { // {{{
323       /** @constructs */
324       function _QuadraticTo(_details) { // {{{
325          // Private Members {{{
326          var cpx = check(_details.cpx, 0);
327          var cpy = check(_details.cpy, 0);
328          // }}} Private Members
329          // Public Members {{{
330          this.draw = function(_context) { // {{{
331             var bounds = this.getBounds();
332             _context.quadraticCurveTo(cpx, cpy, bounds.x, bounds.y);
333          } // }}} draw
334          // }}} Public Members
335       } // }}} _QuadTo
336       SceneContent(_details).extend(_QuadraticTo);
337       var theQuadTo = new _QuadraticTo(_details);
338       return theQuadTo;
339    } // }}} QuadTo
340    
341    Group(_details).extend(_Path);
342    var thePath = new _Path(_details);
343    return thePath;
344 } // }}} Path
345 // These properties are for jEdit - Programmer's Text Editor.
346 // Load this file in jEdit to see what they do.
347 // ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
348