1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.input.awt; 34 35 import com.jme3.input.MouseInput; 36 import com.jme3.input.RawInputListener; 37 import com.jme3.input.event.MouseButtonEvent; 38 import com.jme3.input.event.MouseMotionEvent; 39 import java.awt.*; 40 import java.awt.event.*; 41 import java.awt.image.BufferedImage; 42 import java.util.ArrayList; 43 import java.util.logging.Level; 44 import java.util.logging.Logger; 45 import javax.swing.SwingUtilities; 46 47 /** 48 * <code>AwtMouseInput</code> 49 * 50 * @author Joshua Slack 51 * @author MHenze (cylab) 52 * 53 * @version $Revision$ 54 */ 55 public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListener, MouseMotionListener { 56 57 public static int WHEEL_AMP = 40; // arbitrary... Java's mouse wheel seems to report something a lot lower than lwjgl's 58 59 private static final Logger logger = Logger.getLogger(AwtMouseInput.class.getName()); 60 61 private boolean visible = true; 62 63 private RawInputListener listener; 64 65 private Component component; 66 67 private final ArrayList<MouseButtonEvent> eventQueue = new ArrayList<MouseButtonEvent>(); 68 private final ArrayList<MouseButtonEvent> eventQueueCopy = new ArrayList<MouseButtonEvent>(); 69 70 private int lastEventX; 71 private int lastEventY; 72 private int lastEventWheel; 73 74 private Cursor transparentCursor; 75 76 private Robot robot; 77 private int wheelPos; 78 private Point location; 79 private Point centerLocation; 80 private Point centerLocationOnScreen; 81 private Point lastKnownLocation; 82 private boolean isRecentering; 83 private boolean cursorMoved; 84 private int eventsSinceRecenter; 85 86 public AwtMouseInput() { 87 location = new Point(); 88 centerLocation = new Point(); 89 centerLocationOnScreen = new Point(); 90 lastKnownLocation = new Point(); 91 92 try{ 93 robot = new Robot(); 94 }catch (java.awt.AWTException e){ 95 logger.log(Level.SEVERE, "Could not create a robot, so the mouse cannot be grabbed! ", e); 96 } 97 } 98 99 public void setInputSource(Component comp){ 100 if (component != null){ 101 component.removeMouseListener(this); 102 component.removeMouseMotionListener(this); 103 component.removeMouseWheelListener(this); 104 105 eventQueue.clear(); 106 107 wheelPos = 0; 108 isRecentering = false; 109 eventsSinceRecenter = 0; 110 lastEventX = 0; 111 lastEventY = 0; 112 lastEventWheel = 0; 113 location = new Point(); 114 centerLocation = new Point(); 115 centerLocationOnScreen = new Point(); 116 lastKnownLocation = new Point(); 117 } 118 119 component = comp; 120 component.addMouseListener(this); 121 component.addMouseMotionListener(this); 122 component.addMouseWheelListener(this); 123 } 124 125 public void initialize() { 126 } 127 128 public void destroy() { 129 } 130 131 public boolean isInitialized() { 132 return true; 133 } 134 135 public void setInputListener(RawInputListener listener){ 136 this.listener = listener; 137 } 138 139 public long getInputTimeNanos() { 140 return System.nanoTime(); 141 } 142 143 public void setCursorVisible(boolean visible){ 144 if (this.visible != visible){ 145 146 lastKnownLocation.x = lastKnownLocation.y = 0; 147 148 this.visible = visible; 149 final boolean newVisible = visible; 150 SwingUtilities.invokeLater(new Runnable() { 151 public void run() { 152 component.setCursor(newVisible ? null : getTransparentCursor()); 153 if (!newVisible) 154 recenterMouse(component); 155 } 156 }); 157 } 158 } 159 160 public void update() { 161 if (cursorMoved){ 162 int newX = location.x; 163 int newY = location.y; 164 int newWheel = wheelPos; 165 166 // invert DY 167 int actualX = lastKnownLocation.x; 168 int actualY = component.getHeight() - lastKnownLocation.y; 169 MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY, 170 newX - lastEventX, 171 lastEventY - newY, 172 wheelPos, lastEventWheel - wheelPos); 173 listener.onMouseMotionEvent(evt); 174 175 lastEventX = newX; 176 lastEventY = newY; 177 lastEventWheel = newWheel; 178 179 cursorMoved = false; 180 } 181 182 synchronized (eventQueue){ 183 eventQueueCopy.clear(); 184 eventQueueCopy.addAll(eventQueue); 185 eventQueue.clear(); 186 } 187 188 int size = eventQueueCopy.size(); 189 for (int i = 0; i < size; i++){ 190 listener.onMouseButtonEvent(eventQueueCopy.get(i)); 191 } 192 } 193 194 private Cursor getTransparentCursor() { 195 if (transparentCursor == null){ 196 BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 197 cursorImage.setRGB(0, 0, 0); 198 transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "empty cursor"); 199 } 200 return transparentCursor; 201 } 202 203 // public void setHardwareCursor(URL file, int xHotspot, int yHotspot) { 204 // //Create the image from the provided url 205 // java.awt.Image cursorImage = new ImageIcon( file ).getImage( ); 206 // //Create a custom cursor with this image 207 // opaqueCursor = Toolkit.getDefaultToolkit().createCustomCursor( cursorImage , new Point( xHotspot , yHotspot ) , "custom cursor" ); 208 // //Use this cursor 209 // setCursorVisible( isCursorVisible ); 210 // } 211 212 213 public int getButtonCount() { 214 return 3; 215 } 216 217 public void mouseClicked(MouseEvent arg0) { 218 // MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); 219 // listener.onMouseButtonEvent(evt); 220 } 221 222 public void mousePressed(MouseEvent arg0) { 223 MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), true, arg0.getX(), arg0.getY()); 224 evt.setTime(arg0.getWhen()); 225 synchronized (eventQueue){ 226 eventQueue.add(evt); 227 } 228 } 229 230 public void mouseReleased(MouseEvent arg0) { 231 MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false, arg0.getX(), arg0.getY()); 232 evt.setTime(arg0.getWhen()); 233 synchronized (eventQueue){ 234 eventQueue.add(evt); 235 } 236 } 237 238 public void mouseEntered(MouseEvent arg0) { 239 if (!visible) 240 recenterMouse(arg0.getComponent()); 241 } 242 243 public void mouseExited(MouseEvent arg0) { 244 if (!visible) 245 recenterMouse(arg0.getComponent()); 246 } 247 248 public void mouseWheelMoved(MouseWheelEvent arg0) { 249 int dwheel = arg0.getUnitsToScroll(); 250 wheelPos += dwheel * WHEEL_AMP; 251 cursorMoved = true; 252 } 253 254 public void mouseDragged(MouseEvent arg0) { 255 mouseMoved(arg0); 256 } 257 258 public void mouseMoved(MouseEvent arg0) { 259 if (isRecentering) { 260 // MHenze (cylab) Fix Issue 35: 261 // As long as the MouseInput is in recentering mode, nothing is done until the mouse is entered in the component 262 // by the events generated by the robot. If this happens, the last known location is resetted. 263 if ((centerLocation.x == arg0.getX() && centerLocation.y == arg0.getY()) || eventsSinceRecenter++ == 5) { 264 lastKnownLocation.x = arg0.getX(); 265 lastKnownLocation.y = arg0.getY(); 266 isRecentering = false; 267 } 268 } else { 269 // MHenze (cylab) Fix Issue 35: 270 // Compute the delta and absolute coordinates and recenter the mouse if necessary 271 int dx = arg0.getX() - lastKnownLocation.x; 272 int dy = arg0.getY() - lastKnownLocation.y; 273 location.x += dx; 274 location.y += dy; 275 if (!visible) { 276 recenterMouse(arg0.getComponent()); 277 } 278 lastKnownLocation.x = arg0.getX(); 279 lastKnownLocation.y = arg0.getY(); 280 281 cursorMoved = true; 282 } 283 } 284 285 // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse 286 private void recenterMouse(final Component component) { 287 if (robot != null) { 288 eventsSinceRecenter = 0; 289 isRecentering = true; 290 centerLocation.setLocation(component.getWidth()/2, component.getHeight()/2); 291 centerLocationOnScreen.setLocation(centerLocation); 292 SwingUtilities.convertPointToScreen(centerLocationOnScreen, component); 293 robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y); 294 } 295 } 296 297 private int getJMEButtonIndex( MouseEvent arg0 ) { 298 int index; 299 switch (arg0.getButton()) { 300 default: 301 case MouseEvent.BUTTON1: //left 302 index = MouseInput.BUTTON_LEFT; 303 break; 304 case MouseEvent.BUTTON2: //middle 305 index = MouseInput.BUTTON_MIDDLE; 306 break; 307 case MouseEvent.BUTTON3: //right 308 index = MouseInput.BUTTON_RIGHT; 309 break; 310 } 311 return index; 312 } 313 } 314