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