1 /**
  2  * The Render Engine
  3  * MouseInputComponent
  4  *
  5  * @fileoverview An extension of the input component which handles
  6  *               mouse input.
  7  *
  8  * @author: Brett Fattori (brettf@renderengine.com)
  9  * @author: $Author: bfattori $
 10  * @version: $Revision: 1555 $
 11  *
 12  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 13  *
 14  * Permission is hereby granted, free of charge, to any person obtaining a copy
 15  * of this software and associated documentation files (the "Software"), to deal
 16  * in the Software without restriction, including without limitation the rights
 17  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 18  * copies of the Software, and to permit persons to whom the Software is
 19  * furnished to do so, subject to the following conditions:
 20  *
 21  * The above copyright notice and this permission notice shall be included in
 22  * all copies or substantial portions of the Software.
 23  *
 24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 27  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 29  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 30  * THE SOFTWARE.
 31  *
 32  */
 33 
 34 // The class this file defines and its required classes
 35 R.Engine.define({
 36     "class":"R.components.input.Mouse",
 37     "requires":[
 38         "R.components.Input",
 39         "R.engine.Events",
 40         "R.lang.Timeout",
 41         "R.math.Point2D",
 42         "R.math.Vector2D",
 43         "R.math.Math2D"
 44     ]
 45 });
 46 
 47 /**
 48  * @class A component which responds to mouse events and notifies
 49  * the host object when one of the events occurs.  The <tt>R.engine.GameObject</tt> should
 50  * add event handlers for any of the following:
 51  * <ul>
 52  * <li><tt>mouseover</tt> - The mouse moved over the host object, or the object moved under the mouse</li>
 53  * <li><tt>mouseout</tt> - The mouse moved out of the host object (after being over it)</li>
 54  * <li><tt>mousedown</tt> - A mouse button was depressed, while over the object</li>
 55  * <li><tt>mouseup</tt> - A mouse button was released</li>
 56  * <li><tt>click</tt> - A mouse button was depressed, and released, while over the object</li>
 57  * <li><tt>mousemove</tt> - The mouse was moved</li>
 58  * </ul>
 59  * Each event is passed the event object and a {@link R.struct.MouseInfo MouseInfo} structure which
 60  * contains information about the mouse event in the context of a game.
 61  * <p/>
 62  * <i>Note: The rendering context that the object is contained within needs to enable mouse event
 63  * capturing with the {@link R.rendercontexts.AbstractRenderContext#captureMouse} method.</i>
 64  * Objects which wish to be notified via the <tt>mouseover</tt> event handler will need to define
 65  * a bounding box.
 66  *
 67  * @param name {String} The unique name of the component.
 68  * @param priority {Number} The priority of the component among other input components.
 69  * @extends R.components.Input
 70  * @constructor
 71  * @description Create a mouse input component.
 72  */
 73 R.components.input.Mouse = function () {
 74     "use strict";
 75     return R.components.Input.extend(/** @scope R.components.input.Mouse.prototype */{
 76 
 77         /**
 78          * @private
 79          */
 80         constructor:function (name, priority) {
 81             this.base(name, priority);
 82         },
 83 
 84         /**
 85          * Destroy the component.
 86          */
 87         destroy:function () {
 88             if (this.getGameObject()) {
 89                 delete this.getGameObject().getObjectDataModel()[R.components.input.Mouse.MOUSE_DATA_MODEL];
 90             }
 91             this.base();
 92         },
 93 
 94         /**
 95          * Deprecated in favor of {@link #setGameObject}
 96          * @deprecated
 97          */
 98         setHostObject:function (hostobj) {
 99             this.setGameObject(hostobj);
100         },
101 
102         /**
103          * Set the game object this component exists within.  Additionally, this component
104          * sets some readable flags on the game object and establishes (if not already set)
105          * a mouse listener on the render context.
106          *
107          * @param gameObject {R.engine.GameObject} The object which hosts the component
108          * @private
109          */
110         setGameObject:function (gameObject) {
111             this.base(gameObject);
112 
113             // Set some flags we can check
114             var dataModel = gameObject.setObjectDataModel(R.components.input.Mouse.MOUSE_DATA_MODEL, {
115                 mouseOver:false,
116                 mouseDown:false
117             });
118 
119             // Add event pass-thru for DOM objects
120             var el = gameObject.jQ();
121             if (el) {
122                 var mI = R.struct.MouseInfo.create();
123 
124                 // Wire up event handlers for the DOM element to mimic what is done for
125                 // canvas objects
126                 el.bind("mousemove", function (evt) {
127                     mI.lastPosition.set(mI.position);
128                     mI.position.set(evt.pageX, evt.pageY);
129                     gameObject.triggerEvent("mousemove", [mI]);
130                 });
131 
132                 el.bind("mouseover", function (evt) {
133                     mI.lastPosition.set(mI.position);
134                     mI.position.set(evt.pageX, evt.pageY);
135                     gameObject.triggerEvent("mousover", [mI]);
136                 });
137 
138                 el.bind("mouseout", function (evt) {
139                     mI.lastPosition.set(mI.position);
140                     mI.position.set(evt.pageX, evt.pageY);
141                     mI.lastOver = gameObject;
142                     gameObject.triggerEvent("mouseout", [mI]);
143                 });
144 
145                 el.bind("mousedown", function (evt) {
146                     mI.button = evt.which;
147                     mI.downPosition.set(evt.pageX, evt.pageY);
148                     gameObject.triggerEvent("mousedown", [mI]);
149                 });
150 
151                 el.bind("mouseup", function (evt) {
152                     mI.button = R.engine.Events.MOUSE_NO_BUTTON;
153                     mI.dragVec.set(0, 0);
154                     gameObject.triggerEvent("mouseup", [mI]);
155                 });
156 
157                 el.bind("click", function (evt) {
158                     mI.button = evt.which;
159                     mI.downPosition.set(evt.pageX, evt.pageY);
160                     gameObject.triggerEvent("click", [mI]);
161                 });
162             }
163         },
164 
165         /**
166          * Perform the checks on the mouse info object, and also perform
167          * intersection tests to be able to call mouse events.
168          * @param renderContext {R.rendercontexts.AbstractRenderContext} The render context
169          * @param time {Number} The current world time
170          * @param dt {Number} The delta between the world time and the last time the world was updated
171          *          in milliseconds.
172          */
173         execute:function (renderContext, time, dt) {
174             // Objects may be in motion.  If so, we need to call the mouse
175             // methods for just such a case.
176             var gameObject = this.getGameObject();
177 
178             // Only objects without an element will use this.  For object WITH an element,
179             // this component will have intervened and wired up special handlers to fake
180             // the mouseInfo object.
181             if (!gameObject.getElement()) {
182                 var mouseInfo = renderContext.getMouseInfo(),
183                     bBox = gameObject.getWorldBox(),
184                     mouseOver = false,
185                     dataModel = gameObject.getObjectDataModel(R.components.input.Mouse.MOUSE_DATA_MODEL);
186 
187                 if (mouseInfo && bBox) {
188                     mouseOver = R.math.Math2D.boxPointCollision(bBox, mouseInfo.position);
189                 }
190 
191                 // Mouse position changed
192                 if (!mouseInfo.position.equals(mouseInfo.lastPosition) && mouseOver) {
193                     gameObject.triggerEvent("mousemove", [mouseInfo]);
194                 }
195 
196                 // Mouse is over object
197                 if (mouseOver && !dataModel.mouseOver) {
198                     dataModel.mouseOver = true;
199                     gameObject.triggerEvent("mouseover", [mouseInfo]);
200                 }
201 
202                 // Mouse was over object
203                 if (!mouseOver && dataModel.mouseOver === true) {
204                     dataModel.mouseOver = false;
205                     mouseInfo.lastOver = this;
206                     gameObject.triggerEvent("mouseout", [mouseInfo]);
207                 }
208 
209                 // Whether the mouse is over the object or not, we'll still record that the
210                 // mouse button was pressed.
211                 if (!dataModel.mouseDown && (mouseInfo.button != R.engine.Events.MOUSE_NO_BUTTON)) {
212 
213                     // BAF: 06/17/2011 - https://github.com/bfattori/TheRenderEngine/issues/8
214                     // Mouse down can only be triggered if the mouse went down while over the object
215                     if (mouseOver) {
216                         dataModel.mouseDown = true;
217                         gameObject.triggerEvent("mousedown", [mouseInfo]);
218                     }
219                 }
220 
221                 // Mouse button released (and mouse was down)
222                 if (dataModel.mouseDown && (mouseInfo.button == R.engine.Events.MOUSE_NO_BUTTON)) {
223                     dataModel.mouseDown = false;
224                     gameObject.triggerEvent("mouseup", [mouseInfo]);
225 
226                     // Trigger the "click" event if the mouse was pressed and released
227                     // over an object
228                     if (mouseOver) {
229                         gameObject.triggerEvent("click", [mouseInfo]);
230                     }
231                 }
232             }
233         }
234 
235     }, /** @scope R.components.input.Mouse.prototype */{
236         /**
237          * Get the class name of this object
238          *
239          * @return {String} "R.components.input.Mouse"
240          */
241         getClassName:function () {
242             return "R.components.input.Mouse";
243         },
244 
245         /**
246          * @private
247          */
248         MOUSE_DATA_MODEL:"MouseInputComponent"
249     });
250 };