1 /**
  2  * Copyright (c) 2008-2010 Ricardo Quesada
  3  * Copyright (c) 2011-2012 cocos2d-x.org
  4  * Copyright (c) 2013-2014 Chukong Technologies Inc.
  5  * Copyright 2011 Yannick Loriot.
  6  * http://yannickloriot.com
  7  *
  8  * Permission is hereby granted, free of charge, to any person obtaining a copy
  9  * of this software and associated documentation files (the "Software"), to deal
 10  * in the Software without restriction, including without limitation the rights
 11  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  * copies of the Software, and to permit persons to whom the Software is
 13  * furnished to do so, subject to the following conditions:
 14  *
 15  * The above copyright notice and this permission notice shall be included in
 16  * all copies or substantial portions of the Software.
 17  *
 18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  * THE SOFTWARE.
 25  *
 26  */
 27 
 28 /** Number of kinds of control event. */
 29 cc.CONTROL_EVENT_TOTAL_NUMBER = 9;
 30 
 31 /** Kinds of possible events for the control objects. */
 32 cc.CONTROL_EVENT_TOUCH_DOWN = 1 << 0;    // A touch-down event in the control.
 33 cc.CONTROL_EVENT_TOUCH_DRAG_INSIDE = 1 << 1;    // An event where a finger is dragged inside the bounds of the control.
 34 cc.CONTROL_EVENT_TOUCH_DRAG_OUTSIDE = 1 << 2;    // An event where a finger is dragged just outside the bounds of the control.
 35 cc.CONTROL_EVENT_TOUCH_DRAG_ENTER = 1 << 3;    // An event where a finger is dragged into the bounds of the control.
 36 cc.CONTROL_EVENT_TOUCH_DRAG_EXIT = 1 << 4;    // An event where a finger is dragged from within a control to outside its bounds.
 37 cc.CONTROL_EVENT_TOUCH_UP_INSIDE = 1 << 5;    // A touch-up event in the control where the finger is inside the bounds of the control.
 38 cc.CONTROL_EVENT_TOUCH_UP_OUTSIDE = 1 << 6;    // A touch-up event in the control where the finger is outside the bounds of the control.
 39 cc.CONTROL_EVENT_TOUCH_CANCEL = 1 << 7;    // A system event canceling the current touches for the control.
 40 cc.CONTROL_EVENT_VALUECHANGED = 1 << 8;    // A touch dragging or otherwise manipulating a control; causing it to emit a series of different values.
 41 
 42 /** The possible state for a control.  */
 43 cc.CONTROL_STATE_NORMAL = 1 << 0; // The normal; or default state of a controlę¢©hat is; enabled but neither selected nor highlighted.
 44 cc.CONTROL_STATE_HIGHLIGHTED = 1 << 1; // Highlighted state of a control. A control enters this state when a touch down; drag inside or drag enter is performed. You can retrieve and set this value through the highlighted property.
 45 cc.CONTROL_STATE_DISABLED = 1 << 2; // Disabled state of a control. This state indicates that the control is currently disabled. You can retrieve and set this value through the enabled property.
 46 cc.CONTROL_STATE_SELECTED = 1 << 3;  // Selected state of a control. This state indicates that the control is currently selected. You can retrieve and set this value through the selected property.
 47 cc.CONTROL_STATE_INITIAL = 1 << 3;
 48 
 49 /**
 50  * CCControl is inspired by the UIControl API class from the UIKit library of
 51  * CocoaTouch. It provides a base class for control CCSprites such as CCButton
 52  * or CCSlider that convey user intent to the application.
 53  * The goal of CCControl is to define an interface and base implementation for
 54  * preparing action messages and initially dispatching them to their targets when
 55  * certain events occur.
 56  * To use the CCControl you have to subclass it.
 57  * @class
 58  * @extends cc.LayerRGBA
 59  *
 60  * @property {Number}   state       - <@readonly> The current control state: cc.CONTROL_STATE_NORMAL | cc.CONTROL_STATE_HIGHLIGHTED | cc.CONTROL_STATE_DISABLED | cc.CONTROL_STATE_SELECTED | cc.CONTROL_STATE_INITIAL
 61  * @property {Boolean}  enabled     - Indicate whether the control node is enbaled
 62  * @property {Boolean}  selected    - Indicate whether the control node is selected
 63  * @property {Boolean}  highlighted - Indicate whether the control node is highlighted
 64  */
 65 cc.Control = cc.LayerRGBA.extend(/** @lends cc.Control# */{
 66     _isOpacityModifyRGB: false,
 67     _hasVisibleParents: false,
 68     _touchListener: null,
 69     _className: "Control",
 70 
 71     isOpacityModifyRGB: function () {
 72         return this._isOpacityModifyRGB;
 73     },
 74     setOpacityModifyRGB: function (opacityModifyRGB) {
 75         this._isOpacityModifyRGB = opacityModifyRGB;
 76 
 77         var children = this.getChildren();
 78         for (var i = 0, len = children.length; i < len; i++) {
 79             var selNode = children[i];
 80             if (selNode && selNode.RGBAProtocol)
 81                 selNode.setOpacityModifyRGB(opacityModifyRGB);
 82         }
 83     },
 84 
 85     /** The current control state constant. */
 86     _state: cc.CONTROL_STATE_NORMAL,
 87     getState: function () {
 88         return this._state;
 89     },
 90 
 91     _enabled: false,
 92     _selected: false,
 93     _highlighted: false,
 94 
 95     _dispatchTable: null,
 96 
 97     /**
 98      * Tells whether the control is enabled
 99      * @param {Boolean} enabled
100      */
101     setEnabled: function (enabled) {
102         this._enabled = enabled;
103         this._state = enabled ? cc.CONTROL_STATE_NORMAL : cc.CONTROL_STATE_DISABLED;
104 
105         this.needsLayout();
106     },
107     isEnabled: function () {
108         return this._enabled;
109     },
110 
111     /**
112      * A Boolean value that determines the control selected state.
113      * @param {Boolean} selected
114      */
115     setSelected: function (selected) {
116         this._selected = selected;
117         this.needsLayout();
118     },
119     isSelected: function () {
120         return this._selected;
121     },
122 
123     /**
124      *  A Boolean value that determines whether the control is highlighted.
125      * @param {Boolean} highlighted
126      */
127     setHighlighted: function (highlighted) {
128         this._highlighted = highlighted;
129         this.needsLayout();
130     },
131     isHighlighted: function () {
132         return this._highlighted;
133     },
134 
135     hasVisibleParents: function () {
136         var parent = this.getParent();
137         for (var c = parent; c != null; c = c.getParent()) {
138             if (!c.isVisible())
139                 return false;
140         }
141         return true;
142     },
143 
144     ctor: function () {
145         cc.LayerRGBA.prototype.ctor.call(this);
146         this._dispatchTable = {};
147         this._color = cc.color.WHITE;
148     },
149 
150     init: function () {
151         if (cc.LayerRGBA.prototype.init.call(this)) {
152             // Initialise instance variables
153             this._state = cc.CONTROL_STATE_NORMAL;
154             this._enabled = true;
155             this._selected = false;
156             this._highlighted = false;
157 
158             var listener = cc.EventListener.create({
159                 event: cc.EventListener.TOUCH_ONE_BY_ONE
160             });
161             if (this.onTouchBegan)
162                 listener.onTouchBegan = this.onTouchBegan.bind(this);
163             if (this.onTouchMoved)
164                 listener.onTouchMoved = this.onTouchMoved.bind(this);
165             if (this.onTouchEnded)
166                 listener.onTouchEnded = this.onTouchEnded.bind(this);
167             if (this.onTouchCancelled)
168                 listener.onTouchCancelled = this.onTouchCancelled.bind(this);
169             this._touchListener = listener;
170             return true;
171         } else
172             return false;
173     },
174 
175     onEnter: function () {
176         var locListener = this._touchListener;
177         if (!locListener._isRegistered())
178             cc.eventManager.addListener(locListener, this);
179         cc.Node.prototype.onEnter.call(this);
180     },
181 
182     /**
183      * Sends action messages for the given control events.
184      * which action messages are sent. See "CCControlEvent" for bitmask constants.
185      * @param {Number} controlEvents A bitmask whose set flags specify the control events for
186      */
187     sendActionsForControlEvents: function (controlEvents) {
188         // For each control events
189         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
190             // If the given controlEvents bitmask contains the curent event
191             if ((controlEvents & (1 << i))) {
192                 // Call invocations
193                 // <CCInvocation*>
194                 var invocationList = this._dispatchListforControlEvent(1 << i);
195                 for (var j = 0, inLen = invocationList.length; j < inLen; j++) {
196                     invocationList[j].invoke(this);
197                 }
198             }
199         }
200     },
201 
202     /**
203      * <p>
204      * Adds a target and action for a particular event (or events) to an internal                         <br/>
205      * dispatch table.                                                                                    <br/>
206      * The action message may optionally include the sender and the event as                              <br/>
207      * parameters, in that order.                                                                         <br/>
208      * When you call this method, target is not retained.
209      * </p>
210      * @param {Object} target The target object that is, the object to which the action message is sent. It cannot be nil. The target is not retained.
211      * @param {function} action A selector identifying an action message. It cannot be NULL.
212      * @param {Number} controlEvents A bitmask specifying the control events for which the action message is sent. See "CCControlEvent" for bitmask constants.
213      */
214     addTargetWithActionForControlEvents: function (target, action, controlEvents) {
215         // For each control events
216         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
217             // If the given controlEvents bit mask contains the current event
218             if ((controlEvents & (1 << i)))
219                 this._addTargetWithActionForControlEvent(target, action, 1 << i);
220         }
221     },
222 
223     /**
224      * Removes a target and action for a particular event (or events) from an internal dispatch table.
225      *
226      * @param {Object} target The target object that is, the object to which the action message is sent. Pass nil to remove all targets paired with action and the specified control events.
227      * @param {function} action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
228      * @param {Number} controlEvents A bitmask specifying the control events associated with target and action. See "CCControlEvent" for bitmask constants.
229      */
230     removeTargetWithActionForControlEvents: function (target, action, controlEvents) {
231         // For each control events
232         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
233             // If the given controlEvents bitmask contains the current event
234             if ((controlEvents & (1 << i)))
235                 this._removeTargetWithActionForControlEvent(target, action, 1 << i);
236         }
237     },
238 
239     /**
240      * Returns a point corresponding to the touh location converted into the
241      * control space coordinates.
242      * @param {cc.Touch} touch A CCTouch object that represents a touch.
243      */
244     getTouchLocation: function (touch) {
245         var touchLocation = touch.getLocation();                      // Get the touch position
246         return this.convertToNodeSpace(touchLocation);  // Convert to the node space of this class
247     },
248 
249     /**
250      * Returns a boolean value that indicates whether a touch is inside the bounds of the receiver. The given touch must be relative to the world.
251      *
252      * @param {cc.Touch} touch A cc.Touch object that represents a touch.
253      * @return {Boolean} YES whether a touch is inside the receiver's rect.
254      */
255     isTouchInside: function (touch) {
256         var touchLocation = touch.getLocation(); // Get the touch position
257         touchLocation = this.getParent().convertToNodeSpace(touchLocation);
258         return cc.rectContainsPoint(this.getBoundingBox(), touchLocation);
259     },
260 
261     /**
262      * <p>
263      * Returns an cc.Invocation object able to construct messages using a given                             <br/>
264      * target-action pair. (The invocation may optionally include the sender and                            <br/>
265      * the event as parameters, in that order)
266      * </p>
267      * @param {Object} target The target object.
268      * @param {function} action A selector identifying an action message.
269      * @param {Number} controlEvent A control events for which the action message is sent. See "CCControlEvent" for constants.
270      *
271      * @return {cc.Invocation} an CCInvocation object able to construct messages using a given target-action pair.
272      */
273     _invocationWithTargetAndActionForControlEvent: function (target, action, controlEvent) {
274         return null;
275     },
276 
277     /**
278      * Returns the cc.Invocation list for the given control event. If the list does not exist, it'll create an empty array before returning it.
279      *
280      * @param {Number} controlEvent A control events for which the action message is sent. See "CCControlEvent" for constants.
281      * @return {cc.Invocation} the cc.Invocation list for the given control event.
282      */
283     _dispatchListforControlEvent: function (controlEvent) {
284         controlEvent = controlEvent.toString();
285         // If the invocation list does not exist for the  dispatch table, we create it
286         if (!this._dispatchTable[controlEvent])
287             this._dispatchTable[controlEvent] = [];
288         return this._dispatchTable[controlEvent];
289     },
290 
291     /**
292      * Adds a target and action for a particular event to an internal dispatch
293      * table.
294      * The action message may optionally include the sender and the event as
295      * parameters, in that order.
296      * When you call this method, target is not retained.
297      *
298      * @param target The target object that is, the object to which the action
299      * message is sent. It cannot be nil. The target is not retained.
300      * @param action A selector identifying an action message. It cannot be NULL.
301      * @param controlEvent A control event for which the action message is sent.
302      * See "CCControlEvent" for constants.
303      */
304     _addTargetWithActionForControlEvent: function (target, action, controlEvent) {
305         // Create the invocation object
306         var invocation = new cc.Invocation(target, action, controlEvent);
307 
308         // Add the invocation into the dispatch list for the given control event
309         var eventInvocationList = this._dispatchListforControlEvent(controlEvent);
310         eventInvocationList.push(invocation);
311     },
312 
313     /**
314      * Removes a target and action for a particular event from an internal dispatch table.
315      *
316      * @param {Object} target The target object that is, the object to which the action message is sent. Pass nil to remove all targets paired with action and the specified control events.
317      * @param {function} action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
318      * @param {Number} controlEvent A control event for which the action message is sent. See "CCControlEvent" for constants.
319      */
320     _removeTargetWithActionForControlEvent: function (target, action, controlEvent) {
321         // Retrieve all invocations for the given control event
322         //<CCInvocation*>
323         var eventInvocationList = this._dispatchListforControlEvent(controlEvent);
324 
325         //remove all invocations if the target and action are null
326         //TODO: should the invocations be deleted, or just removed from the array? Won't that cause issues if you add a single invocation for multiple events?
327         var bDeleteObjects = true;
328         if (!target && !action) {
329             //remove objects
330             eventInvocationList.length = 0;
331         } else {
332             //normally we would use a predicate, but this won't work here. Have to do it manually
333             for (var i = 0; i < eventInvocationList.length;) {
334                 var invocation = eventInvocationList[i];
335                 var shouldBeRemoved = true;
336                 if (target)
337                     shouldBeRemoved = (target == invocation.getTarget());
338                 if (action)
339                     shouldBeRemoved = (shouldBeRemoved && (action == invocation.getAction()));
340                 // Remove the corresponding invocation object
341                 if (shouldBeRemoved)
342                     cc.arrayRemoveObject(eventInvocationList, invocation);
343                 else
344                     i++;
345             }
346         }
347     },
348 
349     /**
350      * Updates the control layout using its current internal state.
351      */
352     needsLayout: function () {
353     }
354 });
355 
356 var _p = cc.Control.prototype;
357 
358 // Extended properties
359 /** @expose */
360 _p.state;
361 cc.defineGetterSetter(_p, "state", _p.getState);
362 /** @expose */
363 _p.enabled;
364 cc.defineGetterSetter(_p, "enabled", _p.isEnabled, _p.setEnabled);
365 /** @expose */
366 _p.selected;
367 cc.defineGetterSetter(_p, "selected", _p.isSelected, _p.setSelected);
368 /** @expose */
369 _p.highlighted;
370 cc.defineGetterSetter(_p, "highlighted", _p.isHighlighted, _p.setHighlighted);
371 
372 _p = null;
373 
374 cc.Control.create = function () {
375     var retControl = new cc.Control();
376     if (retControl && retControl.init())
377         return retControl;
378     return null;
379 };
380 
381