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 cc.Control = cc.LayerRGBA.extend({
 62     _isOpacityModifyRGB:false,
 63     _hasVisibleParents:false,
 64 
 65     isOpacityModifyRGB:function () {
 66         return this._isOpacityModifyRGB;
 67     },
 68     setOpacityModifyRGB:function (opacityModifyRGB) {
 69         this._isOpacityModifyRGB = opacityModifyRGB;
 70 
 71         var children = this.getChildren();
 72         for (var i = 0, len = children.length; i < len; i++) {
 73             var selNode = children[i];
 74             if (selNode && selNode.RGBAProtocol)
 75                 selNode.setOpacityModifyRGB(opacityModifyRGB);
 76         }
 77     },
 78 
 79     /** The current control state constant. */
 80     _state:cc.CONTROL_STATE_NORMAL,
 81     getState:function () {
 82         return this._state;
 83     },
 84 
 85     _enabled:false,
 86     _selected:false,
 87     _highlighted:false,
 88 
 89     _dispatchTable:null,
 90 
 91     /**
 92      * Tells whether the control is enabled
 93      * @param {Boolean} enabled
 94      */
 95     setEnabled:function (enabled) {
 96         this._enabled = enabled;
 97         this._state = enabled ? cc.CONTROL_STATE_NORMAL:cc.CONTROL_STATE_DISABLED;
 98 
 99         this.needsLayout();
100     },
101     isEnabled:function () {
102         return this._enabled;
103     },
104 
105     /**
106      * A Boolean value that determines the control selected state.
107      * @param {Boolean} selected
108      */
109     setSelected:function (selected) {
110         this._selected = selected;
111         this.needsLayout();
112     },
113     isSelected:function () {
114         return this._selected;
115     },
116 
117     /**
118      *  A Boolean value that determines whether the control is highlighted.
119      * @param {Boolean} highlighted
120      */
121     setHighlighted:function (highlighted) {
122         this._highlighted = highlighted;
123         this.needsLayout();
124     },
125     isHighlighted:function () {
126         return this._highlighted;
127     },
128 
129     hasVisibleParents:function () {
130         var parent = this.getParent();
131         for (var c = parent; c != null; c = c.getParent()) {
132             if (!c.isVisible())
133                 return false;
134         }
135         return true;
136     },
137 
138     ctor:function () {
139         cc.LayerRGBA.prototype.ctor.call(this);
140         this._dispatchTable = {};
141         this._color = cc.white();
142     },
143 
144     init:function () {
145         if (cc.LayerRGBA.prototype.init.call(this)) {
146             //this.setTouchEnabled(true);
147             //m_bIsTouchEnabled=true;
148             // Initialise instance variables
149             this._state = cc.CONTROL_STATE_NORMAL;
150             this._enabled = true;
151             this._selected = false;
152             this._highlighted = false;
153 
154             // Set the touch dispatcher priority by default to 1
155             this._defaultTouchPriority = 1;
156             this.setTouchPriority(1);
157             // Initialise the tables
158             //this._dispatchTable = {};
159             //dispatchTable.autorelease();
160             //   dispatchTable_ = [[NSMutableDictionary alloc] initWithCapacity:1];
161             return true;
162         } else
163             return false;
164     },
165 
166     registerWithTouchDispatcher:function () {
167         cc.registerTargetedDelegate(this.getTouchPriority(), true, this);
168     },
169 
170     /**
171      * Sends action messages for the given control events.
172      * which action messages are sent. See "CCControlEvent" for bitmask constants.
173      * @param {Number} controlEvents A bitmask whose set flags specify the control events for
174      */
175     sendActionsForControlEvents:function (controlEvents) {
176         // For each control events
177         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
178             // If the given controlEvents bitmask contains the curent event
179             if ((controlEvents & (1 << i))) {
180                 // Call invocations
181                 // <CCInvocation*>
182                 var invocationList = this._dispatchListforControlEvent(1 << i);
183                 for (var j = 0, inLen = invocationList.length; j < inLen; j++) {
184                     invocationList[j].invoke(this);
185                 }
186             }
187         }
188     },
189 
190     /**
191      * <p>
192      * Adds a target and action for a particular event (or events) to an internal                         <br/>
193      * dispatch table.                                                                                    <br/>
194      * The action message may optionally include the sender and the event as                              <br/>
195      * parameters, in that order.                                                                         <br/>
196      * When you call this method, target is not retained.
197      * </p>
198      * @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.
199      * @param {function} action A selector identifying an action message. It cannot be NULL.
200      * @param {Number} controlEvents A bitmask specifying the control events for which the action message is sent. See "CCControlEvent" for bitmask constants.
201      */
202     addTargetWithActionForControlEvents:function (target, action, controlEvents) {
203         // For each control events
204         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
205             // If the given controlEvents bit mask contains the current event
206             if ((controlEvents & (1 << i)))
207                 this._addTargetWithActionForControlEvent(target, action, 1 << i);
208         }
209     },
210 
211     /**
212      * Removes a target and action for a particular event (or events) from an internal dispatch table.
213      *
214      * @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.
215      * @param {function} action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
216      * @param {Number} controlEvents A bitmask specifying the control events associated with target and action. See "CCControlEvent" for bitmask constants.
217      */
218     removeTargetWithActionForControlEvents:function (target, action, controlEvents) {
219         // For each control events
220         for (var i = 0, len = cc.CONTROL_EVENT_TOTAL_NUMBER; i < len; i++) {
221             // If the given controlEvents bitmask contains the current event
222             if ((controlEvents & (1 << i)))
223                 this._removeTargetWithActionForControlEvent(target, action, 1 << i);
224         }
225     },
226 
227     /**
228      * Returns a point corresponding to the touh location converted into the
229      * control space coordinates.
230      * @param {cc.Touch} touch A CCTouch object that represents a touch.
231      */
232     getTouchLocation:function (touch) {
233         var touchLocation = touch.getLocation();                      // Get the touch position
234         return this.convertToNodeSpace(touchLocation);  // Convert to the node space of this class
235     },
236 
237     /**
238      * 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.
239      *
240      * @param {cc.Touch} touch A cc.Touch object that represents a touch.
241      * @return {Boolean} YES whether a touch is inside the receiver's rect.
242      */
243     isTouchInside:function (touch) {
244         var touchLocation = touch.getLocation(); // Get the touch position
245         touchLocation = this.getParent().convertToNodeSpace(touchLocation);
246         return cc.rectContainsPoint(this.getBoundingBox(), touchLocation);
247     },
248 
249     /**
250      * <p>
251      * Returns an cc.Invocation object able to construct messages using a given                             <br/>
252      * target-action pair. (The invocation may optionally include the sender and                            <br/>
253      * the event as parameters, in that order)
254      * </p>
255      * @param {Object} target The target object.
256      * @param {function} action A selector identifying an action message.
257      * @param {Number} controlEvent A control events for which the action message is sent. See "CCControlEvent" for constants.
258      *
259      * @return {cc.Invocation} an CCInvocation object able to construct messages using a given target-action pair.
260      */
261     _invocationWithTargetAndActionForControlEvent:function (target, action, controlEvent) {
262         return null;
263     },
264 
265     /**
266      * 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.
267      *
268      * @param {Number} controlEvent A control events for which the action message is sent. See "CCControlEvent" for constants.
269      * @return {cc.Invocation} the cc.Invocation list for the given control event.
270      */
271     _dispatchListforControlEvent:function (controlEvent) {
272         controlEvent = controlEvent.toString();
273         // If the invocation list does not exist for the  dispatch table, we create it
274         if (!this._dispatchTable[controlEvent])
275             this._dispatchTable[controlEvent] = [];
276         return this._dispatchTable[controlEvent];
277     },
278 
279     /**
280      * Adds a target and action for a particular event to an internal dispatch
281      * table.
282      * The action message may optionally include the sender and the event as
283      * parameters, in that order.
284      * When you call this method, target is not retained.
285      *
286      * @param target The target object that is, the object to which the action
287      * message is sent. It cannot be nil. The target is not retained.
288      * @param action A selector identifying an action message. It cannot be NULL.
289      * @param controlEvent A control event for which the action message is sent.
290      * See "CCControlEvent" for constants.
291      */
292     _addTargetWithActionForControlEvent:function (target, action, controlEvent) {
293         // Create the invocation object
294         var invocation = new cc.Invocation(target, action, controlEvent);
295 
296         // Add the invocation into the dispatch list for the given control event
297         var eventInvocationList = this._dispatchListforControlEvent(controlEvent);
298         eventInvocationList.push(invocation);
299     },
300 
301     /**
302      * Removes a target and action for a particular event from an internal dispatch table.
303      *
304      * @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.
305      * @param {function} action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
306      * @param {Number} controlEvent A control event for which the action message is sent. See "CCControlEvent" for constants.
307      */
308     _removeTargetWithActionForControlEvent:function (target, action, controlEvent) {
309         // Retrieve all invocations for the given control event
310         //<CCInvocation*>
311         var eventInvocationList = this._dispatchListforControlEvent(controlEvent);
312 
313         //remove all invocations if the target and action are null
314         //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?
315         var bDeleteObjects = true;
316         if (!target && !action) {
317             //remove objects
318             eventInvocationList.length = 0;
319         } else {
320             //normally we would use a predicate, but this won't work here. Have to do it manually
321             for (var i = 0; i < eventInvocationList.length; ) {
322                 var invocation = eventInvocationList[i];
323                 var shouldBeRemoved = true;
324                 if (target)
325                     shouldBeRemoved = (target == invocation.getTarget());
326                 if (action)
327                     shouldBeRemoved = (shouldBeRemoved && (action == invocation.getAction()));
328                 // Remove the corresponding invocation object
329                 if (shouldBeRemoved)
330                     cc.ArrayRemoveObject(eventInvocationList, invocation);
331                 else
332                     i ++;
333             }
334         }
335     },
336 
337     /**
338      * Updates the control layout using its current internal state.
339      */
340     needsLayout:function () {
341     }
342 });
343 
344 cc.Control.create = function () {
345     var retControl = new cc.Control();
346     if (retControl && retControl.init())
347         return retControl;
348     return null;
349 };
350 
351