1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  5  *
  6  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7  * of this software and associated documentation files (the "Software"), to deal
  8  * in the Software without restriction, including without limitation the rights
  9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  * copies of the Software, and to permit persons to whom the Software is
 11  * furnished to do so, subject to the following conditions:
 12  *
 13  * The above copyright notice and this permission notice shall be included in
 14  * all copies or substantial portions of the Software.
 15  *
 16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="display_object.js"/>
 27 /// <reference path="graphics.js"/>
 28 /// <reference path="config.js"/>
 29 
 30 /**
 31  * A class that represents a vector shape.
 32  * @param {createjs.Graphics=} opt_graphics
 33  * @extends {createjs.DisplayObject}
 34  * @constructor
 35  */
 36 createjs.Shape = function(opt_graphics) {
 37   createjs.DisplayObject.call(this);
 38 
 39   /**
 40    * The createjs.Graphics object that actually represents this shape.
 41    * @type {createjs.Graphics}
 42    * @private
 43    */
 44   this.graphics_ = opt_graphics ? opt_graphics : new createjs.Graphics();
 45 };
 46 createjs.inherits('Shape', createjs.Shape, createjs.DisplayObject);
 47 
 48 /**
 49  * Createjs.Graphics objects to be set by a tween attached to this shape.
 50  * @type {Array.<createjs.Graphics>}
 51  * @private
 52  */
 53 createjs.Shape.prototype.masks_ = null;
 54 
 55 /**
 56  * The position where this shape was hit-tested last time.
 57  * @type {createjs.Point}
 58  * @private
 59  */
 60 createjs.Shape.prototype.hitTestPoint_ = null;
 61 
 62 /**
 63  * The result of the last hit-testing.
 64  * @type {createjs.DisplayObject}
 65  * @private
 66  */
 67 createjs.Shape.prototype.hitTestResult_ = null;
 68 
 69 /**
 70  * Returns the createjs.Graphics object owned by this object.
 71  * @return {createjs.Graphics}
 72  * @const
 73  */
 74 createjs.Shape.prototype.getGraphics = function() {
 75   /// <returns type="createjs.Graphics"/>
 76   return this.graphics_;
 77 };
 78 
 79 /**
 80  * Sets the createjs.Graphics object.
 81  * @param {createjs.Graphics} graphics
 82  * @const
 83  */
 84 createjs.Shape.prototype.setGraphics = function(graphics) {
 85   /// <param type="createjs.Graphics" name="graphics"/>
 86   var owners = this.getOwners();
 87   if (!owners) {
 88     return;
 89   }
 90   var composition = this.getCompositionId();
 91   if (composition) {
 92     // A mask uses its bounding box in the global coordinate system to render
 93     // its owners. Force its owners to calculate it. (Even though multiple
 94     // owners can share one mask, they create copies of the bounding box of the
 95     // mask to convert them to the global coordinate system. That is, it is safe
 96     // for multiple owners to share one mask.)
 97     this.graphics_ = graphics;
 98     if (graphics) {
 99       var box = graphics.box;
100       this.setBoundingBox(box.minX, box.minY, box.maxX, box.maxY);
101       for (var i = 0; i < owners.length; ++i) {
102         owners[i].setDirty(createjs.DisplayObject.DIRTY_MASK);
103       }
104     }
105   }
106 };
107 
108 /** @override */
109 createjs.Shape.prototype.handleAttach = function(flag) {
110   /// <param type="number" name="flag"/>
111   var graphics = this.getGraphics();
112   if (flag) {
113     // Cache the associated Graphics object when this shape is added to an
114     // object tree. (A CreateJS object generated by Flash CC adds child shapes
115     // to its node list in its constructor, i.e. this code caches all child
116     // shapes of a CreateJS object there.)
117     graphics.cache(flag);
118     if ((flag & 2) && this.masks_) {
119       // Cache masks the first time when a display object has a mask attached,
120       // i.e. "object.mask = shape" is executed.
121       for (var i = 0; i < this.masks_.length; ++i) {
122         this.masks_[i].cache(flag);
123       }
124     }
125   }
126   var box = graphics.box;
127   this.setBoundingBox(box.minX, box.minY, box.maxX, box.maxY);
128 };
129 
130 /** @override */
131 createjs.Shape.prototype.removeAllChildren = function(opt_destroy) {
132   /// <param type="boolean" optional="true" name="opt_destroy"/>
133   this.handleDetach();
134 };
135 
136 /** @override */
137 createjs.Shape.prototype.handleDetach = function() {
138   if (this.graphics_) {
139     this.graphics_.uncache();
140     this.graphics_ = null;
141   }
142   if (this.masks_) {
143     for (var i = 0; i < this.masks_.length; ++i) {
144       this.masks_[i].uncache();
145     }
146     this.masks_ = null;
147   }
148 };
149 
150 if (createjs.USE_PIXEL_TEST) {
151   /** @override */
152   createjs.Shape.prototype.hitTestObject = function(point, types, bubble) {
153     var object = createjs.DisplayObject.prototype.hitTestObject.call(
154         this, point, types, bubble);
155     if (object && this.graphics_) {
156       // Return the cached result if the given point is sufficiently close to
157       // the last one. This method is often called twice with the same position
158       // when a user taps on this shape: one is for a 'touchdown' event, and the
159       // other is for a 'touchup' event. This cache avoids reading the pixels of
160       // its createjs.Graphics object twice.
161       if (this.hitTestPoint_) {
162         var dx = this.hitTestPoint_.x - point.x;
163         var dy = this.hitTestPoint_.y - point.y;
164         if (dx * dx + dy * dy <= 4) {
165           return this.hitTestResult_;
166         }
167         this.hitTestPoint_.x = point.x;
168         this.hitTestPoint_.y = point.y;
169       } else {
170         this.hitTestPoint_ = new createjs.Point(point.x, point.y);
171       }
172       // Read the pixel at the specified position.
173       var local = new createjs.Point(point.x, point.y);
174       this.getInverse().transformPoint(local);
175       if (!this.graphics_.hitTestObject(local)) {
176         object = null;
177       }
178       this.hitTestResult_ = object;
179     }
180     return object;
181   };
182 }
183 
184 /** @override */
185 createjs.Shape.prototype.layout =
186     function(renderer, parent, dirty, time, draw) {
187   /// <param type="createjs.Renderer" name="renderer"/>
188   /// <param type="createjs.DisplayObject" name="parent"/>
189   /// <param type="number" name="dirty"/>
190   /// <param type="number" name="time"/>
191   /// <param type="number" name="draw"/>
192   var graphics = this.graphics_;
193   if (!graphics) {
194     return 0;
195   }
196   if (graphics.isDirty()) {
197     this.setDirty(createjs.DisplayObject.DIRTY_SHAPE);
198     var box = graphics.box;
199     this.setBoundingBox(box.minX, box.minY, box.maxX, box.maxY);
200   }
201   return createjs.Shape.superClass_.layout.call(
202       this, renderer, parent, dirty, time, draw);
203 };
204 
205 /** @override */
206 createjs.Shape.prototype.paintObject = function(renderer) {
207   /// <returns type="createjs.Renderer"/>
208   this.graphics_.paint(renderer);
209 };
210 
211 /** @override */
212 createjs.Shape.prototype.isVisible = function() {
213   /// <returns type="boolean"/>
214   return !!this.graphics_ && !this.graphics_.isEmpty() &&
215       createjs.Shape.superClass_.isVisible.call(this);
216 };
217 
218 /** @override */
219 createjs.Shape.prototype.set = function(properties) {
220   createjs.Shape.superClass_.set.call(this, properties);
221   var value = properties['graphics'];
222   if (value) {
223     this.setGraphics(/** @type {createjs.Graphics} */ (value));
224   }
225   return this;
226 };
227 
228 /** @override */
229 createjs.Shape.prototype.addGraphics = function(graphics) {
230   /// <param type="createjs.Graphics" name="graphics"/>
231   // Add the given graphics object to a list so this shape can create their
232   // cache bitmaps when it is a mask and it has to clip its owners.
233   if (graphics) {
234     if (!this.masks_) {
235       this.masks_ = [];
236     }
237     this.masks_.push(graphics);
238   }
239 };
240 
241 /** @override */
242 createjs.Shape.prototype.getSetters = function() {
243   /// <return type="Object" elementType="createjs.TweenTarget.Setter"/>
244   var setters = createjs.Shape.superClass_.getSetters.call(this);
245   setters['graphics'].setGraphics(this.graphics_);
246   return setters;
247 };
248 
249 // Add a setter to allow tweens to change this object.
250 createjs.TweenTarget.Property.addSetters({
251   'graphics': createjs.Shape.prototype.setGraphics
252 });
253 
254 // Adds a getter and a setter for applications to access internal variables.
255 Object.defineProperties(createjs.Shape.prototype, {
256   'graphics': {
257     get: createjs.Shape.prototype.getGraphics,
258     set: createjs.Shape.prototype.setGraphics
259   }
260 });
261 
262 // Export the createjs.Shape object to the global namespace.
263 createjs.exportObject('createjs.Shape', createjs.Shape, {
264   // createjs.Object methods.
265 });
266