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