1 /* 2 * Copyright (c) 2002-2008 LWJGL Project 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 'LWJGL' nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software 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 package org.lwjgl.input; 33 34 import java.nio.IntBuffer; 35 36 import org.lwjgl.BufferChecks; 37 import org.lwjgl.BufferUtils; 38 import org.lwjgl.LWJGLException; 39 import org.lwjgl.LWJGLUtil; 40 import org.lwjgl.Sys; 41 42 /** 43 * 44 * A class representing a native cursor. Instances of this 45 * class can be used with Mouse.setCursor(), if available. 46 * 47 * @author elias_naur <elias_naur@users.sourceforge.net> 48 * @version $Revision$ 49 * $Id$ 50 */ 51 52 public class Cursor { 53 /** 1 bit transparency for native cursor */ 54 public static final int CURSOR_ONE_BIT_TRANSPARENCY = 1; 55 56 /** 8 bit alhpa native cursor */ 57 public static final int CURSOR_8_BIT_ALPHA = 2; 58 59 /** animation native cursor */ 60 public static final int CURSOR_ANIMATION = 4; 61 62 /** First element to display */ 63 private final CursorElement[] cursors; 64 65 /** Index into list of cursors */ 66 private int index; 67 68 private boolean destroyed; 69 70 /** 71 * Constructs a new Cursor, with the given parameters. Mouse must have been created before you can create 72 * Cursor objects. Cursor images are in ARGB format, but only one bit transparancy is guaranteed to be supported. 73 * So to maximize portability, lwjgl applications should only create cursor images with 0x00 or 0xff as alpha values. 74 * The constructor will copy the images and delays, so there's no need to keep them around. 75 * 76 * @param width cursor image width 77 * @param height cursor image height 78 * @param xHotspot the x coordinate of the cursor hotspot 79 * @param yHotspot the y coordinate of the cursor hotspot 80 * @param numImages number of cursor images specified. Must be 1 if animations are not supported. 81 * @param images A buffer containing the images. The origin is at the lower left corner, like OpenGL. 82 * @param delays An int buffer of animation frame delays, if numImages is greater than 1, else null 83 * @throws LWJGLException if the cursor could not be created for any reason 84 */ Cursor(int width, int height, int xHotspot, int yHotspot, int numImages, IntBuffer images, IntBuffer delays)85 public Cursor(int width, int height, int xHotspot, int yHotspot, int numImages, IntBuffer images, IntBuffer delays) throws LWJGLException { 86 synchronized (OpenGLPackageAccess.global_lock) { 87 if ((getCapabilities() & CURSOR_ONE_BIT_TRANSPARENCY) == 0) 88 throw new LWJGLException("Native cursors not supported"); 89 BufferChecks.checkBufferSize(images, width*height*numImages); 90 if (delays != null) 91 BufferChecks.checkBufferSize(delays, numImages); 92 if (!Mouse.isCreated()) 93 throw new IllegalStateException("Mouse must be created before creating cursor objects"); 94 if (width*height*numImages > images.remaining()) 95 throw new IllegalArgumentException("width*height*numImages > images.remaining()"); 96 if (xHotspot >= width || xHotspot < 0) 97 throw new IllegalArgumentException("xHotspot > width || xHotspot < 0"); 98 if (yHotspot >= height || yHotspot < 0) 99 throw new IllegalArgumentException("yHotspot > height || yHotspot < 0"); 100 101 Sys.initialize(); 102 103 // Hmm 104 yHotspot = height - 1 - yHotspot; 105 106 // create cursor (or cursors if multiple images supplied) 107 cursors = createCursors(width, height, xHotspot, yHotspot, numImages, images, delays); 108 } 109 } 110 111 /** 112 * Gets the minimum size of a native cursor. Can only be called if 113 * The Mouse is created and cursor caps includes at least 114 * CURSOR_ONE_BIT_TRANSPARANCY. 115 * 116 * @return the maximum size of a native cursor 117 */ getMinCursorSize()118 public static int getMinCursorSize() { 119 synchronized (OpenGLPackageAccess.global_lock) { 120 if (!Mouse.isCreated()) 121 throw new IllegalStateException("Mouse must be created."); 122 return Mouse.getImplementation().getMinCursorSize(); 123 } 124 } 125 126 /** 127 * Gets the maximum size of a native cursor. Can only be called if 128 * The Mouse is created and cursor caps includes at least 129 * CURSOR_ONE_BIT_TRANSPARANCY. 130 * 131 * @return the maximum size of a native cursor 132 */ getMaxCursorSize()133 public static int getMaxCursorSize() { 134 synchronized (OpenGLPackageAccess.global_lock) { 135 if (!Mouse.isCreated()) 136 throw new IllegalStateException("Mouse must be created."); 137 return Mouse.getImplementation().getMaxCursorSize(); 138 } 139 } 140 141 /** 142 * Get the capabilities of the native cursor. Return a bit mask of the native cursor capabilities. 143 * The CURSOR_ONE_BIT_TRANSPARANCY indicates support for cursors with one bit transparancy, 144 * the CURSOR_8_BIT_ALPHA indicates support for 8 bit alpha and CURSOR_ANIMATION indicates 145 * support for cursor animations. 146 * 147 * @return A bit mask with native cursor capabilities. 148 */ getCapabilities()149 public static int getCapabilities() { 150 synchronized (OpenGLPackageAccess.global_lock) { 151 if (Mouse.getImplementation() != null) 152 return Mouse.getImplementation().getNativeCursorCapabilities(); 153 else 154 return OpenGLPackageAccess.createImplementation().getNativeCursorCapabilities(); 155 } 156 } 157 158 /** 159 * Creates the actual cursor, using a platform specific class 160 */ createCursors(int width, int height, int xHotspot, int yHotspot, int numImages, IntBuffer images, IntBuffer delays)161 private static CursorElement[] createCursors(int width, int height, int xHotspot, int yHotspot, int numImages, IntBuffer images, IntBuffer delays) throws LWJGLException { 162 // create copy and flip images to match ogl 163 IntBuffer images_copy = BufferUtils.createIntBuffer(images.remaining()); 164 flipImages(width, height, numImages, images, images_copy); 165 166 // Mac and Windows doesn't (afaik) allow for animation based cursors, except in the .ani 167 // format on Windows, which we don't support. 168 // The cursor animation was therefor developed using java side time tracking. 169 // unfortunately X flickers when changing cursor. We therefore check for either 170 // Windows, Mac or X and do accordingly. 171 // we might want to split it into a X/Win/Mac cursor if it gets too cluttered 172 173 CursorElement[] cursors; 174 switch (LWJGLUtil.getPlatform()) { 175 case LWJGLUtil.PLATFORM_MACOSX: 176 177 // OS X requires the image format to be in ABGR format 178 convertARGBtoABGR(images_copy); 179 180 // create our cursor elements 181 cursors = new CursorElement[numImages]; 182 for(int i=0; i<numImages; i++) { 183 Object handle = Mouse.getImplementation().createCursor(width, height, xHotspot, yHotspot, 1, images_copy, null); 184 long delay = (delays != null) ? delays.get(i) : 0; 185 long timeout = System.currentTimeMillis(); 186 cursors[i] = new CursorElement(handle, delay, timeout); 187 188 // offset to next image 189 images_copy.position(width*height*(i+1)); 190 } 191 break; 192 case LWJGLUtil.PLATFORM_WINDOWS: 193 // create our cursor elements 194 cursors = new CursorElement[numImages]; 195 for(int i=0; i<numImages; i++) { 196 197 // iterate through the images, and make sure that the pixels are either 0xffxxxxxx or 0x00000000 198 int size = width * height; 199 for(int j=0; j<size; j++) { 200 int index = j + (i*size); 201 int alpha = images_copy.get(index) >> 24 & 0xff; 202 if(alpha != 0xff) { 203 images_copy.put(index, 0); 204 } 205 } 206 207 Object handle = Mouse.getImplementation().createCursor(width, height, xHotspot, yHotspot, 1, images_copy, null); 208 long delay = (delays != null) ? delays.get(i) : 0; 209 long timeout = System.currentTimeMillis(); 210 cursors[i] = new CursorElement(handle, delay, timeout); 211 212 // offset to next image 213 images_copy.position(width*height*(i+1)); 214 } 215 break; 216 case LWJGLUtil.PLATFORM_LINUX: 217 // create our cursor elements 218 Object handle = Mouse.getImplementation().createCursor(width, height, xHotspot, yHotspot, numImages, images_copy, delays); 219 CursorElement cursor_element = new CursorElement(handle, -1, -1); 220 cursors = new CursorElement[]{cursor_element}; 221 break; 222 default: 223 throw new RuntimeException("Unknown OS"); 224 } 225 return cursors; 226 } 227 228 /** 229 * Convert an IntBuffer image of ARGB format into ABGR 230 * 231 * @param imageBuffer image to convert 232 */ convertARGBtoABGR(IntBuffer imageBuffer)233 private static void convertARGBtoABGR(IntBuffer imageBuffer) { 234 for (int i = 0; i < imageBuffer.limit(); i++) { 235 int argbColor = imageBuffer.get(i); 236 237 byte alpha = (byte)(argbColor >>> 24); 238 byte blue = (byte)(argbColor >>> 16); 239 byte green = (byte)(argbColor >>> 8); 240 byte red = (byte)argbColor; 241 242 int abgrColor = ((alpha & 0xff) << 24 ) + ((red & 0xff) << 16 ) + ((green & 0xff) << 8 ) + ((blue & 0xff) ); 243 244 imageBuffer.put(i, abgrColor); 245 } 246 } 247 248 /** 249 * Flips the images so they're oriented according to opengl 250 * 251 * @param width Width of image 252 * @param height Height of images 253 * @param numImages How many images to flip 254 * @param images Source images 255 * @param images_copy Destination images 256 */ flipImages(int width, int height, int numImages, IntBuffer images, IntBuffer images_copy)257 private static void flipImages(int width, int height, int numImages, IntBuffer images, IntBuffer images_copy) { 258 for (int i = 0; i < numImages; i++) { 259 int start_index = i*width*height; 260 flipImage(width, height, start_index, images, images_copy); 261 } 262 } 263 264 /** 265 * @param width Width of image 266 * @param height Height of images 267 * @param start_index index into source buffer to copy to 268 * @param images Source images 269 * @param images_copy Destination images 270 */ flipImage(int width, int height, int start_index, IntBuffer images, IntBuffer images_copy)271 private static void flipImage(int width, int height, int start_index, IntBuffer images, IntBuffer images_copy) { 272 for (int y = 0; y < height>>1; y++) { 273 int index_y_1 = y*width + start_index; 274 int index_y_2 = (height - y - 1)*width + start_index; 275 for (int x = 0; x < width; x++) { 276 int index1 = index_y_1 + x; 277 int index2 = index_y_2 + x; 278 int temp_pixel = images.get(index1 + images.position()); 279 images_copy.put(index1, images.get(index2 + images.position())); 280 images_copy.put(index2, temp_pixel); 281 } 282 } 283 } 284 285 /** 286 * Gets the native handle associated with the cursor object. 287 */ getHandle()288 Object getHandle() { 289 checkValid(); 290 return cursors[index].cursorHandle; 291 } 292 checkValid()293 private void checkValid() { 294 if (destroyed) 295 throw new IllegalStateException("The cursor is destroyed"); 296 } 297 298 /** 299 * Destroy the native cursor. If the cursor is current, 300 * the current native cursor is set to null (the default 301 * OS cursor) 302 */ destroy()303 public void destroy() { 304 synchronized (OpenGLPackageAccess.global_lock) { 305 if (destroyed) 306 return; 307 if (Mouse.getNativeCursor() == this) { 308 try { 309 Mouse.setNativeCursor(null); 310 } catch (LWJGLException e) { 311 // ignore 312 } 313 } 314 for ( CursorElement cursor : cursors ) { 315 Mouse.getImplementation().destroyCursor(cursor.cursorHandle); 316 } 317 destroyed = true; 318 } 319 } 320 321 /** 322 * Sets the timout property to the time it should be changed 323 */ setTimeout()324 protected void setTimeout() { 325 checkValid(); 326 cursors[index].timeout = System.currentTimeMillis() + cursors[index].delay; 327 } 328 329 /** 330 * Determines whether this cursor has timed out 331 * @return true if the this cursor has timed out, false if not 332 */ hasTimedOut()333 protected boolean hasTimedOut() { 334 checkValid(); 335 return cursors.length > 1 && cursors[index].timeout < System.currentTimeMillis(); 336 } 337 338 /** 339 * Changes to the next cursor 340 */ nextCursor()341 protected void nextCursor() { 342 checkValid(); 343 index = ++index % cursors.length; 344 } 345 346 /** 347 * A single cursor element, used when animating 348 */ 349 private static class CursorElement { 350 /** Handle to cursor */ 351 final Object cursorHandle; 352 353 /** How long a delay this element should have */ 354 final long delay; 355 356 /** Absolute time this element times out */ 357 long timeout; 358 CursorElement(Object cursorHandle, long delay, long timeout)359 CursorElement(Object cursorHandle, long delay, long timeout) { 360 this.cursorHandle = cursorHandle; 361 this.delay = delay; 362 this.timeout = timeout; 363 } 364 } 365 } 366