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 };