1 /** 2 * The Render Engine 3 * AbstractUIControl 4 * 5 * @fileoverview Abstract class that provides the foundation for rendering UI 6 * controls to a graphical context. 7 * 8 * @author: Brett Fattori (brettf@renderengine.com) 9 * 10 * @author: $Author: bfattori $ 11 * @version: $Revision: 1555 $ 12 * 13 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 14 * 15 * Permission is hereby granted, free of charge, to any person obtaining a copy 16 * of this software and associated documentation files (the "Software"), to deal 17 * in the Software without restriction, including without limitation the rights 18 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 * copies of the Software, and to permit persons to whom the Software is 20 * furnished to do so, subject to the following conditions: 21 * 22 * The above copyright notice and this permission notice shall be included in 23 * all copies or substantial portions of the Software. 24 * 25 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 * THE SOFTWARE. 32 * 33 */ 34 35 // The class this file defines and its required classes 36 R.Engine.define({ 37 "class":"R.ui.AbstractUIControl", 38 "requires":[ 39 "R.objects.Object2D", 40 "R.text.TextRenderer", 41 "R.text.ContextText", 42 "R.math.Math2D" 43 ] 44 }); 45 46 /** 47 * @class Abstract class that provides the foundation for all UI controls which are 48 * rendered to a graphical context. 49 * 50 * @constructor 51 * @param controlName {String} The name of the control 52 * @param textRenderer {R.text.AbstractTextRenderer} Optional text renderer. Defaults to 53 * {@link R.text.ContextText} renderer. 54 * @extends R.objects.Object2D 55 */ 56 R.ui.AbstractUIControl = function () { 57 return R.objects.Object2D.extend(/** @scope R.ui.AbstractUIControl.prototype */{ 58 59 textRenderer:null, 60 styleClass:null, 61 elem:null, 62 focus:false, 63 inControl:false, 64 overControl:false, 65 buttonDown:false, 66 groupName:null, 67 uiName:null, 68 69 /** @private */ 70 constructor:function (controlName, textRenderer) { 71 this.base(controlName); 72 textRenderer = textRenderer || R.text.ContextText.create(); 73 this.textRenderer = R.text.TextRenderer.create(textRenderer, "", 1); 74 this.styleClass = "ui"; 75 this.elem = $("<span id='" + this.getId() + "'></span>").addClass(this.styleClass).addClass("default"); 76 R.Engine.getDefaultContext().jQ().append(this.elem); 77 this.focus = false; 78 this.inControl = false; 79 this.overControl = false; 80 this.buttonDown = false; 81 this.groupName = ""; 82 this.uiName = this.getId(); 83 }, 84 85 /** 86 * Destroy the UI control. 87 */ 88 destroy:function () { 89 this.textRenderer.destroy(); 90 this.elem.remove(); 91 this.base(); 92 }, 93 94 /** 95 * Releases the object back into the object pool. See {@link R.engine.PooledObject#release} 96 * for more information. 97 */ 98 release:function () { 99 this.base(); 100 this.textRenderer = null; 101 this.styleClass = null; 102 }, 103 104 /** 105 * After the control is added to the render context. 106 * @param renderContext {R.rendercontexts.RenderContext2D} The render context 107 * @private 108 */ 109 afterAdd:function (renderContext) { 110 // We call this method so we can measure the text 111 this.getTextRenderer().setRenderContext(renderContext); 112 113 // We'll need event capturing to know when the input was clicked on 114 renderContext.captureMouse(); 115 }, 116 117 /** 118 * Add a CSS style class to the control. 119 * @param cssClass {String} The name of the style class to add 120 */ 121 addClass:function (cssClass) { 122 this.elem.addClass(cssClass); 123 }, 124 125 /** 126 * Remove a CSS style class from the control. 127 * @param cssClass {String} The name of the style class to remove 128 */ 129 removeClass:function (cssClass) { 130 this.elem.removeClass(cssClass); 131 }, 132 133 /** 134 * Get a reference to the text renderer for the control. 135 * @return {R.text.TextRenderer} 136 */ 137 getTextRenderer:function () { 138 return this.textRenderer; 139 }, 140 141 /** 142 * Set the name of the UI control which uniquely identifies it among other 143 * UI controls. 144 * @param uiName {String} The name of the control 145 */ 146 setControlName:function (uiName) { 147 this.uiName = uiName; 148 }, 149 150 /** 151 * Get the unique name of this UI control. 152 * @return {String} 153 */ 154 getControlName:function () { 155 return this.uiName; 156 }, 157 158 /** 159 * Set the control's group name. 160 * @param groupName {String} The name of the control's group, or an empty string to clear the 161 * group. 162 */ 163 setGroup:function (groupName) { 164 if (this.groupName != null) { 165 // Remove it from its old group 166 R.engine.Support.arrayRemove(R.ui.AbstractUIControl.groups[this.groupName], this); 167 } 168 169 // If group name is null or the empty string, just exit here 170 if (groupName == "") { 171 return; 172 } 173 174 this.groupName = groupName; 175 if (!R.ui.AbstractUIControl.groups[this.groupName]) { 176 // Create the group if it doesn't exist 177 R.ui.AbstractUIControl.groups[this.groupName] = []; 178 } 179 180 // Add this control to the group 181 R.ui.AbstractUIControl.groups[this.groupName].push(this); 182 }, 183 184 /** 185 * Returns the name of the group this UI control belongs to. Returns an 186 * empty string if this control isn't part of a group. 187 * @return {String} 188 */ 189 getGroupName:function () { 190 return this.groupName; 191 }, 192 193 /** 194 * Get the group of controls which are in this control's group. 195 * @return {Array} 196 */ 197 getGroup:function () { 198 if (this.groupName) { 199 return R.ui.AbstractUIControl.groups[this.groupName]; 200 } else { 201 return null; 202 } 203 }, 204 205 /** 206 * Set the focus state of the UI control. 207 * @param state {Boolean} <code>true</code> if the control has focus 208 */ 209 setFocus:function (state) { 210 this.focus = state; 211 if (state) { 212 this.triggerEvent("focus"); 213 } else { 214 this.triggerEvent("blur"); 215 } 216 }, 217 218 /** 219 * Returns a boolean indicating if the control has focus. 220 * @return {Boolean} <code>true</code> if the control has focus 221 */ 222 hasFocus:function () { 223 return this.focus; 224 }, 225 226 /** 227 * [ABSTRACT] Calculate and return the width of the control in pixels. 228 * @return {Number} 229 */ 230 calcWidth:function () { 231 return this.getTextRenderer().getBoundingBox().w; 232 }, 233 234 /** 235 * [ABSTRACT] Calculate and return the height of the control in pixels. 236 * @return {Number} 237 */ 238 calcHeight:function () { 239 return this.getTextRenderer().getBoundingBox().h; 240 }, 241 242 /** 243 * Draw a box for the control. 244 * @param renderContext {R.rendercontexts.RenderContext2D} The render context 245 * @param width {Number} The width of the box 246 * @param height {Number} The height of the box 247 * @param fillColor {String} The fill color for the box, or <code>null</code> 248 */ 249 drawBox:function (renderContext, width, height, fillColor) { 250 var rect = R.math.Rectangle2D.create(0, 0, width, height); 251 if (fillColor) { 252 renderContext.setFillStyle(fillColor); 253 renderContext.drawFilledRectangle(rect); 254 } else { 255 renderContext.setFillStyle("transparent"); 256 } 257 renderContext.drawRectangle(rect); 258 rect.destroy(); 259 }, 260 261 /** 262 * Parses a style class into a set of commands which set up the control for 263 * rendering. 264 * @private 265 */ 266 parseStyleClass:function (renderContext) { 267 // We'll handle a specific set of styles 268 var sNames = "borderTopWidth,borderTopColor,marginLeft,marginTop,fontFamily,fontSize," + 269 "fontWeight,fontStyle,textAlign,width,height,backgroundColor,color"; 270 sNames = sNames.split(","); 271 var self = this, cW = 0, cH = 0, cP, offs; 272 $.each(sNames, function (i, e) { 273 var cS = self.elem.css(e); 274 if (cS != null && cS != "") { 275 var intVal = parseInt(cS); 276 switch (e) { 277 case "width" : 278 cW = (cS == "0px" ? self.calcWidth() : intVal); 279 break; 280 case "height": 281 cH = (cS == "0px" ? self.calcHeight() : intVal); 282 break; 283 case "borderTopWidth": 284 renderContext.setLineWidth(intVal); 285 break; 286 case "borderTopColor": 287 renderContext.setLineStyle(cS); 288 break; 289 case "marginLeft": 290 if (cS != "0px") { 291 cP = R.clone(renderContext.getPosition()); 292 offs = R.math.Point2D.create(intVal, 0); 293 renderContext.setPosition(cP.add(offs)); 294 offs.destroy(); 295 } 296 break; 297 case "marginTop": 298 if (cS != "0px") { 299 cP = R.clone(renderContext.getPosition()); 300 offs = R.math.Point2D.create(0, intVal); 301 renderContext.setPosition(cP.add(offs)); 302 offs.destroy(); 303 } 304 break; 305 case "backgroundColor": 306 cS != "transparent" ? self.drawBox(renderContext, cW, cH, cS) : 307 self.drawBox(renderContext, cW, cH, null); 308 break; 309 case "fontFamily" : 310 self.getTextRenderer().setTextFont(cS); 311 break; 312 case "fontSize" : 313 self.getTextRenderer().setTextSize(intVal); 314 break; 315 case "fontWeight" : 316 self.getTextRenderer().setTextWeight(cS); 317 break; 318 case "fontStyle" : 319 self.getTextRenderer().setTextStyle(cS); 320 break; 321 case "textAlign" : 322 switch (cS) { 323 case "center": 324 self.getTextRenderer().setTextAlignment(R.text.AbstractTextRenderer.ALIGN_CENTER); 325 break; 326 case "right": 327 self.getTextRenderer().setTextAlignment(R.text.AbstractTextRenderer.ALIGN_RIGHT); 328 break; 329 default: 330 self.getTextRenderer().setTextAlignment(R.text.AbstractTextRenderer.ALIGN_LEFT); 331 } 332 break; 333 case "color" : 334 self.getTextRenderer().setColor(cS); 335 break; 336 } 337 } 338 }); 339 this.getBoundingBox().set(0, 0, cW, cH); 340 }, 341 342 /** 343 * Returns <code>true</code> if the mouse is in the control's world bounding box. 344 * @return {Boolean} 345 */ 346 isMouseInControl:function () { 347 return this.inControl; 348 }, 349 350 /** 351 * Called when the mouse is over the control. Triggers the "mouseover" event, passing the 352 * <code>R.struct.MouseInfo</code> structure. 353 * 354 * @param mouseInfo {R.struct.MouseInfo} The mouse info structure 355 */ 356 mouseOver:function (mouseInfo) { 357 this.triggerEvent("mouseover", [mouseInfo]); 358 }, 359 360 /** 361 * Called when the mouse moves out of the control. Triggers the "mouseout" event, passing the 362 * <code>R.struct.MouseInfo</code> structure. 363 * 364 * @param mouseInfo {R.struct.MouseInfo} The mouse info structure 365 */ 366 mouseOut:function (mouseInfo) { 367 this.triggerEvent("mouseout", [mouseInfo]); 368 }, 369 370 /** 371 * Called when the mouse moves over the control. Triggers the "mousemove" event, passing the 372 * <code>R.struct.MouseInfo</code> structure. 373 * 374 * @param mouseInfo {R.struct.MouseInfo} The mouse info structure 375 */ 376 mouseMove:function (mouseInfo) { 377 this.triggerEvent("mousemove", [mouseInfo]); 378 }, 379 380 /** 381 * Called when a mouse button is pressed on the control. Triggers the "mousedown" event, passing the 382 * <code>R.struct.MouseInfo</code> structure. 383 * 384 * @param mouseInfo {R.struct.MouseInfo} The mouse info structure 385 */ 386 mouseDown:function (mouseInfo) { 387 this.triggerEvent("mousedown", [mouseInfo]); 388 }, 389 390 /** 391 * Called when a mouse button is released on the control. Triggers the "mouseup" event, passing the 392 * <code>R.struct.MouseInfo</code> structure. 393 * 394 * @param mouseInfo {R.struct.MouseInfo} The mouse info structure 395 */ 396 mouseUp:function (mouseInfo) { 397 this.triggerEvent("mouseup", [mouseInfo]); 398 }, 399 400 /** 401 * Called when a mouse button is pressed, then released on the control. Triggers the 402 * "click" event, passing the <code>R.struct.MouseInfo</code> structure. 403 * 404 * @param mouseInfo {R.struct.MouseInfo} The mouse info structure 405 */ 406 click:function (mouseInfo) { 407 if (!this.hasFocus()) { 408 this.setFocus(true); 409 } 410 this.triggerEvent("click", [mouseInfo]); 411 }, 412 413 /** 414 * [ABSTRACT] Method which draws the UI control to the render context. 415 * @param renderContext {R.rendercontexts.RenderContext2D} The render context where the control is 416 * drawn. 417 * @param worldTime {Number} The current world time, in milliseconds 418 * @param dt {Number} The time since the last frame was drawn by the engine, in milliseconds 419 */ 420 drawControl:function (renderContext, worldTime, dt) { 421 }, 422 423 /** 424 * Update the input component in the render context. 425 * @param renderContext {R.rendercontexts.RenderContext2D} The render context where the control is 426 * drawn. 427 * @param worldTime {Number} The current world time, in milliseconds 428 * @param dt {Number} The time since the last frame was drawn by the engine, in milliseconds 429 * @private 430 */ 431 update:function (renderContext, worldTime, dt) { 432 renderContext.pushTransform(); 433 this.base(renderContext, worldTime, dt); 434 this.parseStyleClass(renderContext); 435 this.drawControl(renderContext, worldTime, dt); 436 renderContext.popTransform(); 437 438 // Handle events after the control is drawn 439 var mInfo = renderContext.getObjectDataModel("mouseInfo"); 440 if (mInfo) { 441 this.inControl = this.getWorldBox().containsPoint(mInfo.position); 442 if (this.inControl && !this.overControl) { 443 this.overControl = true; 444 this.mouseOver(mInfo); 445 } else if (!this.inControl && this.overControl) { 446 this.overControl = false; 447 this.mouseOut(mInfo); 448 } else if (this.inControl && this.overControl) { 449 this.mouseMove(mInfo); 450 } 451 452 if (this.inControl && mInfo.button != R.engine.Events.MOUSE_NO_BUTTON && !this.buttonDown) { 453 this.buttonDown = true; 454 this.mouseDown(mInfo); 455 } 456 457 if (mInfo.button == R.engine.Events.MOUSE_NO_BUTTON && this.buttonDown) { 458 this.buttonDown = false; 459 this.mouseUp(mInfo); 460 if (this.inControl) { 461 this.click(mInfo); 462 } 463 } 464 465 if (mInfo.button != R.engine.Events.MOUSE_NO_BUTTON && !this.inControl && this.hasFocus()) { 466 this.setFocus(false); 467 } 468 } 469 }, 470 471 /** 472 * Returns a bean which represents the read or read/write properties 473 * of the object. 474 * 475 * @return {Object} The properties object 476 */ 477 getProperties:function () { 478 var self = this; 479 var prop = this.base(self); 480 return $.extend(prop, { 481 "ControlName":[function () { 482 return self.getControlName(); 483 }, function (i) { 484 self.setControlName(i); 485 }, true], 486 "GroupName":[function () { 487 return self.getGroupName(); 488 }, function (i) { 489 self.setGroup(i); 490 }, true] 491 }); 492 } 493 494 }, /** @scope R.ui.AbstractUIControl.prototype */{ 495 496 /** 497 * A list of grouped controls 498 * @private 499 */ 500 groups:{}, 501 502 /** 503 * Get the class name of this object 504 * @return {String} The string "R.ui.AbstractUIControl" 505 */ 506 getClassName:function () { 507 return "R.ui.AbstractUIControl"; 508 }, 509 510 /** 511 * Get a properties object with values for the given object. 512 * @param obj {R.ui.AbstractUIControl} The UI control to query 513 * @param [defaults] {Object} Default values that don't need to be serialized unless 514 * they are different. 515 * @return {Object} 516 */ 517 serialize:function (obj, defaults) { 518 // Defaults for object properties which can be skipped if no different 519 defaults = defaults || []; 520 $.extend(defaults, { 521 "GroupName":"" 522 }); 523 return R.objects.Object2D.serialize(obj, defaults); 524 }, 525 526 /** 527 * Deserialize the object back into a UI control. 528 * @param obj {Object} The object to deserialize 529 * @param [clazz] {Class} The object class to populate 530 * @return {R.ui.AbstractUIControl} The object which was deserialized 531 */ 532 deserialize:function (obj, clazz) { 533 clazz = clazz || R.ui.AbstractUIControl.create("UIControl"); 534 R.objects.Object2D.deserialize(obj, clazz); 535 return clazz; 536 } 537 538 }); 539 540 };