1 /**
  2  *
  3  * Copyright (c) 2010-2012 cocos2d-x.org
  4  * Copyright 2011 Yannick Loriot.
  5  * http://yannickloriot.com
  6  *
  7  * Permission is hereby granted, free of charge, to any person obtaining a copy
  8  * of this software and associated documentation files (the "Software"), to deal
  9  * in the Software without restriction, including without limitation the rights
 10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  * copies of the Software, and to permit persons to whom the Software is
 12  * furnished to do so, subject to the following conditions:
 13  *
 14  * The above copyright notice and this permission notice shall be included in
 15  * all copies or substantial portions of the Software.
 16  *
 17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  * THE SOFTWARE.
 24  *
 25  *
 26  * converted to Javascript / cocos2d-x by Angus C
 27  */
 28 
 29 /** Number of kinds of control event. */
 30 cc.CONTROL_EVENT_TOTAL_NUMBER = 9;
 31 
 32 /** Kinds of possible events for the control objects. */
 33 cc.CONTROL_EVENT_TOUCH_DOWN = 1 << 0;    // A touch-down event in the control.
 34 cc.CONTROL_EVENT_TOUCH_DRAG_INSIDE = 1 << 1;    // An event where a finger is dragged inside the bounds of the control.
 35 cc.CONTROL_EVENT_TOUCH_DRAG_OUTSIDE = 1 << 2;    // An event where a finger is dragged just outside the bounds of the control.
 36 cc.CONTROL_EVENT_TOUCH_DRAG_ENTER = 1 << 3;    // An event where a finger is dragged into the bounds of the control.
 37 cc.CONTROL_EVENT_TOUCH_DRAG_EXIT = 1 << 4;    // An event where a finger is dragged from within a control to outside its bounds.
 38 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.
 39 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.
 40 cc.CONTROL_EVENT_TOUCH_CANCEL = 1 << 7;    // A system event canceling the current touches for the control.
 41 cc.CONTROL_EVENT_VALUECHANGED = 1 << 8;    // A touch dragging or otherwise manipulating a control; causing it to emit a series of different values.
 42 
 43 /** The possible state for a control.  */
 44 cc.CONTROL_STATE_NORMAL = 1 << 0; // The normal; or default state of a control梩hat is; enabled but neither selected nor highlighted.
 45 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.
 46 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.
 47 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.
 48 cc.CONTROL_STATE_INITIAL = 1 << 3;
 49 
 50 /**
 51  * CCControl is inspired by the UIControl API class from the UIKit library of
 52  * CocoaTouch. It provides a base class for control CCSprites such as CCButton
 53  * or CCSlider that convey user intent to the application.
 54  * The goal of CCControl is to define an interface and base implementation for
 55  * preparing action messages and initially dispatching them to their targets when
 56  * certain events occur.
 57  * To use the CCControl you have to subclass it.
 58  * @class
 59  * @extends cc.LayerRGBA
 60  *
 61  * @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
 62  * @property {Boolean}  enabled     - Indicate whether the control node is enbaled
 63  * @property {Boolean}  selected    - Indicate whether the control node is selected
 64  * @property {Boolean}  highlighted - Indicate whether the control node is highlighted
 65  */
 66 cc.Control = cc.LayerRGBA.extend(/** @lends cc.Control# */{
 67     _isOpacityModifyRGB:false,
 68     _hasVisibleParents:false,
 69     _touchListener: null,
 70     _className:"Control",
 71 
 72     isOpacityModifyRGB:function () {
 73         return this._isOpacityModifyRGB;
 74     },
 75     setOpacityModifyRGB:function (opacityModifyRGB) {
 76         this._isOpacityModifyRGB = opacityModifyRGB;
 77 
 78         var children = this.getChildren();
 79         for (var i = 0, len = children.length; i < len; i++) {
 80             var selNode = children[i];
 81             if (selNode && selNode.RGBAProtocol)
 82                 selNode.setOpacityModifyRGB(opacityModifyRGB);
 83         }
 84     },
 85 
 86     /** The current control state constant. */
 87     _state:cc.CONTROL_STATE_NORMAL,
 88     getState:function () {
 89         return this._state;
 90     },
 91 
 92     _enabled:false,
 93     _selected:false,
 94     _highlighted:false,
 95 
 96     _dispatchTable:null,
 97 
 98     /**
 99      * Tells whether the control is enabled
100      * @param {Boolean} enabled
101      */
102     setEnabled:function (enabled) {
103         this._enabled = enabled;
104         this._state = enabled ? cc.CONTROL_STATE_NORMAL:cc.CONTROL_STATE_DISABLED;
105 
106         this.needsLayout();
107     },
108     isEnabled:function () {
109         return this._enabled;
110     },
111 
112     /**
113      * A Boolean value that determines the control selected state.
114      * @param {Boolean} selected
115      */
116     setSelected:function (selected) {
117         this._selected = selected;
118         this.needsLayout();
119     },
120     isSelected:function () {
121         return this._selected;
122     },
123 
124     /**
125      *  A Boolean value that determines whether the control is highlighted.
126      * @param {Boolean} highlighted
127      */
128     setHighlighted:function (highlighted) {
129         this._highlighted = highlighted;
130         this.needsLayout();
131     },
132     isHighlighted:function () {
133         return this._highlighted;
134     },
135 
136     hasVisibleParents:function () {
137         var parent = this.getParent();
138         for (var c = parent; c != null; c = c.getParent()) {
139             if (!c.isVisible())
140                 return false;
141         }
142         return true;
143     },
144 
145     ctor:function () {
146         cc.LayerRGBA.prototype.ctor.call(this);
147         this._dispatchTable = {};
148         this._color = cc.color.WHITE;
149     },
150 
151     init:function () {
152         if (cc.LayerRGBA.prototype.init.call(this)) {
153             // Initialise instance variables
154             this._state = cc.CONTROL_STATE_NORMAL;
155             this._enabled = true;
156             this._selected = false;
157             this._highlighted = false;
158 
159             var listener = cc.EventListener.create({
160                 event: cc.EventListener.TOUCH_ONE_BY_ONE
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 window._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 delete window._p;
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