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.Layer
 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.Layer.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)
 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.Layer.prototype.ctor.call(this);
146         this._dispatchTable = {};
147         this._color = cc.color.WHITE;
148     },
149 
150     init: function () {
151         if (cc.Layer.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                 swallowTouches: true
161             });
162             if (this.onTouchBegan)
163                 listener.onTouchBegan = this.onTouchBegan.bind(this);
164             if (this.onTouchMoved)
165                 listener.onTouchMoved = this.onTouchMoved.bind(this);
166             if (this.onTouchEnded)
167                 listener.onTouchEnded = this.onTouchEnded.bind(this);
168             if (this.onTouchCancelled)
169                 listener.onTouchCancelled = this.onTouchCancelled.bind(this);
170             this._touchListener = listener;
171             return true;
172         } else
173             return false;
174     },
175 
176     onEnter: function () {
177         var locListener = this._touchListener;
178         if (!locListener._isRegistered())
179             cc.eventManager.addListener(locListener, this);
180         cc.Node.prototype.onEnter.call(this);
181     },
182 
183     /**
184      * Sends action messages for the given control events.
185      * which action messages are sent. See "CCControlEvent" for bitmask constants.
186      * @param {Number} controlEvents A bitmask whose set flags specify the control events for
187      */
188     sendActionsForControlEvents: function (controlEvents) {
189         // For each control events
190         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
191             // If the given controlEvents bitmask contains the curent event
192             if ((controlEvents & (1 << i))) {
193                 // Call invocations
194                 // <CCInvocation*>
195                 var invocationList = this._dispatchListforControlEvent(1 << i);
196                 for (var j = 0, inLen = invocationList.length; j < inLen; j++) {
197                     invocationList[j].invoke(this);
198                 }
199             }
200         }
201     },
202 
203     /**
204      * <p>
205      * Adds a target and action for a particular event (or events) to an internal                         <br/>
206      * dispatch table.                                                                                    <br/>
207      * The action message may optionally include the sender and the event as                              <br/>
208      * parameters, in that order.                                                                         <br/>
209      * When you call this method, target is not retained.
210      * </p>
211      * @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.
212      * @param {function} action A selector identifying an action message. It cannot be NULL.
213      * @param {Number} controlEvents A bitmask specifying the control events for which the action message is sent. See "CCControlEvent" for bitmask constants.
214      */
215     addTargetWithActionForControlEvents: function (target, action, controlEvents) {
216         // For each control events
217         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
218             // If the given controlEvents bit mask contains the current event
219             if ((controlEvents & (1 << i)))
220                 this._addTargetWithActionForControlEvent(target, action, 1 << i);
221         }
222     },
223 
224     /**
225      * Removes a target and action for a particular event (or events) from an internal dispatch table.
226      *
227      * @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.
228      * @param {function} action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
229      * @param {Number} controlEvents A bitmask specifying the control events associated with target and action. See "CCControlEvent" for bitmask constants.
230      */
231     removeTargetWithActionForControlEvents: function (target, action, controlEvents) {
232         // For each control events
233         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
234             // If the given controlEvents bitmask contains the current event
235             if ((controlEvents & (1 << i)))
236                 this._removeTargetWithActionForControlEvent(target, action, 1 << i);
237         }
238     },
239 
240     /**
241      * Returns a point corresponding to the touh location converted into the
242      * control space coordinates.
243      * @param {cc.Touch} touch A CCTouch object that represents a touch.
244      */
245     getTouchLocation: function (touch) {
246         var touchLocation = touch.getLocation();                      // Get the touch position
247         return this.convertToNodeSpace(touchLocation);  // Convert to the node space of this class
248     },
249 
250     /**
251      * 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.
252      *
253      * @param {cc.Touch} touch A cc.Touch object that represents a touch.
254      * @return {Boolean} YES whether a touch is inside the receiver's rect.
255      */
256     isTouchInside: function (touch) {
257         var touchLocation = touch.getLocation(); // Get the touch position
258         touchLocation = this.getParent().convertToNodeSpace(touchLocation);
259         return cc.rectContainsPoint(this.getBoundingBox(), touchLocation);
260     },
261 
262     /**
263      * <p>
264      * Returns an cc.Invocation object able to construct messages using a given                             <br/>
265      * target-action pair. (The invocation may optionally include the sender and                            <br/>
266      * the event as parameters, in that order)
267      * </p>
268      * @param {Object} target The target object.
269      * @param {function} action A selector identifying an action message.
270      * @param {Number} controlEvent A control events for which the action message is sent. See "CCControlEvent" for constants.
271      *
272      * @return {cc.Invocation} an CCInvocation object able to construct messages using a given target-action pair.
273      */
274     _invocationWithTargetAndActionForControlEvent: function (target, action, controlEvent) {
275         return null;
276     },
277 
278     /**
279      * 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.
280      *
281      * @param {Number} controlEvent A control events for which the action message is sent. See "CCControlEvent" for constants.
282      * @return {cc.Invocation} the cc.Invocation list for the given control event.
283      */
284     _dispatchListforControlEvent: function (controlEvent) {
285         controlEvent = controlEvent.toString();
286         // If the invocation list does not exist for the  dispatch table, we create it
287         if (!this._dispatchTable[controlEvent])
288             this._dispatchTable[controlEvent] = [];
289         return this._dispatchTable[controlEvent];
290     },
291 
292     /**
293      * Adds a target and action for a particular event to an internal dispatch
294      * table.
295      * The action message may optionally include the sender and the event as
296      * parameters, in that order.
297      * When you call this method, target is not retained.
298      *
299      * @param target The target object that is, the object to which the action
300      * message is sent. It cannot be nil. The target is not retained.
301      * @param action A selector identifying an action message. It cannot be NULL.
302      * @param controlEvent A control event for which the action message is sent.
303      * See "CCControlEvent" for constants.
304      */
305     _addTargetWithActionForControlEvent: function (target, action, controlEvent) {
306         // Create the invocation object
307         var invocation = new cc.Invocation(target, action, controlEvent);
308 
309         // Add the invocation into the dispatch list for the given control event
310         var eventInvocationList = this._dispatchListforControlEvent(controlEvent);
311         eventInvocationList.push(invocation);
312     },
313 
314     /**
315      * Removes a target and action for a particular event from an internal dispatch table.
316      *
317      * @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.
318      * @param {function} action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
319      * @param {Number} controlEvent A control event for which the action message is sent. See "CCControlEvent" for constants.
320      */
321     _removeTargetWithActionForControlEvent: function (target, action, controlEvent) {
322         // Retrieve all invocations for the given control event
323         //<CCInvocation*>
324         var eventInvocationList = this._dispatchListforControlEvent(controlEvent);
325 
326         //remove all invocations if the target and action are null
327         //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?
328         var bDeleteObjects = true;
329         if (!target && !action) {
330             //remove objects
331             eventInvocationList.length = 0;
332         } else {
333             //normally we would use a predicate, but this won't work here. Have to do it manually
334             for (var i = 0; i < eventInvocationList.length;) {
335                 var invocation = eventInvocationList[i];
336                 var shouldBeRemoved = true;
337                 if (target)
338                     shouldBeRemoved = (target === invocation.getTarget());
339                 if (action)
340                     shouldBeRemoved = (shouldBeRemoved && (action === invocation.getAction()));
341                 // Remove the corresponding invocation object
342                 if (shouldBeRemoved)
343                     cc.arrayRemoveObject(eventInvocationList, invocation);
344                 else
345                     i++;
346             }
347         }
348     },
349 
350     /**
351      * Updates the control layout using its current internal state.
352      */
353     needsLayout: function () {
354     }
355 });
356 
357 var _p = cc.Control.prototype;
358 
359 // Extended properties
360 /** @expose */
361 _p.state;
362 cc.defineGetterSetter(_p, "state", _p.getState);
363 /** @expose */
364 _p.enabled;
365 cc.defineGetterSetter(_p, "enabled", _p.isEnabled, _p.setEnabled);
366 /** @expose */
367 _p.selected;
368 cc.defineGetterSetter(_p, "selected", _p.isSelected, _p.setSelected);
369 /** @expose */
370 _p.highlighted;
371 cc.defineGetterSetter(_p, "highlighted", _p.isHighlighted, _p.setHighlighted);
372 
373 _p = null;
374 
375 cc.Control.create = function () {
376     var retControl = new cc.Control();
377     if (retControl && retControl.init())
378         return retControl;
379     return null;
380 };
381 
382