Skip to content

Commit 09f4ae4

Browse files
toniheleNehon
authored andcommitted
Animated cursor support for LWJGL 3 (#785)
* Simple animated cursor support * Use primitive array and diamond constructor
1 parent cfaaec5 commit 09f4ae4

File tree

1 file changed

+77
-33
lines changed

1 file changed

+77
-33
lines changed

jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131
*/
32-
3332
package com.jme3.input.lwjgl;
3433

3534
import com.jme3.cursors.plugins.JmeCursor;
@@ -39,26 +38,26 @@
3938
import com.jme3.input.event.MouseMotionEvent;
4039
import com.jme3.system.lwjgl.LwjglWindow;
4140
import com.jme3.util.BufferUtils;
42-
import org.lwjgl.glfw.GLFWCursorPosCallback;
43-
import org.lwjgl.glfw.GLFWMouseButtonCallback;
44-
import org.lwjgl.glfw.GLFWScrollCallback;
45-
4641
import java.nio.ByteBuffer;
4742
import java.nio.IntBuffer;
43+
import java.util.ArrayDeque;
4844
import java.util.HashMap;
49-
import java.util.LinkedList;
5045
import java.util.Map;
5146
import java.util.Queue;
5247
import java.util.logging.Logger;
53-
5448
import static org.lwjgl.glfw.GLFW.*;
49+
import org.lwjgl.glfw.GLFWCursorPosCallback;
5550
import org.lwjgl.glfw.GLFWImage;
51+
import org.lwjgl.glfw.GLFWMouseButtonCallback;
52+
import org.lwjgl.glfw.GLFWScrollCallback;
5653
import org.lwjgl.system.MemoryUtil;
5754

5855
/**
59-
* Captures mouse input using GLFW callbacks. It then temporarily stores these in event queues which are processed in the
60-
* {@link #update()} method. Due to some of the GLFW button id's there is a conversion method in this class which will
61-
* convert the GLFW left, middle and right mouse button to JME3 left, middle and right button codes.
56+
* Captures mouse input using GLFW callbacks. It then temporarily stores these
57+
* in event queues which are processed in the {@link #update()} method. Due to
58+
* some of the GLFW button id's there is a conversion method in this class which
59+
* will convert the GLFW left, middle and right mouse button to JME3 left,
60+
* middle and right button codes.
6261
*
6362
* @author Daniel Johansson (dannyjo)
6463
* @since 3.1
@@ -69,20 +68,24 @@ public class GlfwMouseInput implements MouseInput {
6968

7069
private static final int WHEEL_SCALE = 120;
7170

72-
private LwjglWindow context;
71+
private final LwjglWindow context;
7372
private RawInputListener listener;
7473
private boolean cursorVisible = true;
74+
private long[] currentCursor;
75+
private IntBuffer currentCursorDelays;
76+
private int currentCursorFrame = 0;
77+
private double currentCursorFrameStartTime = 0.0;
7578
private int mouseX;
7679
private int mouseY;
7780
private int mouseWheel;
7881
private boolean initialized;
7982
private GLFWCursorPosCallback cursorPosCallback;
8083
private GLFWScrollCallback scrollCallback;
8184
private GLFWMouseButtonCallback mouseButtonCallback;
82-
private Queue<MouseMotionEvent> mouseMotionEvents = new LinkedList<MouseMotionEvent>();
83-
private Queue<MouseButtonEvent> mouseButtonEvents = new LinkedList<MouseButtonEvent>();
85+
private final Queue<MouseMotionEvent> mouseMotionEvents = new ArrayDeque<>();
86+
private final Queue<MouseButtonEvent> mouseButtonEvents = new ArrayDeque<>();
8487

85-
private Map<JmeCursor, Long> jmeToGlfwCursorMap = new HashMap<JmeCursor, Long>();
88+
private final Map<JmeCursor, long[]> jmeToGlfwCursorMap = new HashMap<>();
8689

8790
public GlfwMouseInput(LwjglWindow context) {
8891
this.context = context;
@@ -127,6 +130,7 @@ private void onMouseButton(final long window, final int button, final int action
127130
mouseButtonEvents.add(mouseButtonEvent);
128131
}
129132

133+
@Override
130134
public void initialize() {
131135
glfwSetCursorPosCallback(context.getWindowHandle(), cursorPosCallback = new GLFWCursorPosCallback() {
132136
@Override
@@ -150,6 +154,7 @@ public void callback(long args) {
150154
public void invoke(final long window, final double xOffset, final double yOffset) {
151155
onWheelScroll(window, xOffset, yOffset * WHEEL_SCALE);
152156
}
157+
153158
@Override
154159
public void close() {
155160
super.close();
@@ -166,6 +171,7 @@ public void callback(long args) {
166171
public void invoke(final long window, final int button, final int action, final int mods) {
167172
onMouseButton(window, button, action, mods);
168173
}
174+
169175
@Override
170176
public void close() {
171177
super.close();
@@ -182,15 +188,31 @@ public void callback(long args) {
182188
initialized = true;
183189
}
184190

191+
@Override
185192
public boolean isInitialized() {
186193
return initialized;
187194
}
188195

196+
@Override
189197
public int getButtonCount() {
190198
return GLFW_MOUSE_BUTTON_LAST + 1;
191199
}
192200

201+
@Override
193202
public void update() {
203+
204+
// Manage cursor animation
205+
if (currentCursor != null && currentCursor.length > 1) {
206+
double now = glfwGetTime();
207+
double frameTime = (glfwGetTime() - currentCursorFrameStartTime) * 1000;
208+
if (currentCursorDelays == null || frameTime >= currentCursorDelays.get(currentCursorFrame)) {
209+
currentCursorFrame = ++currentCursorFrame % currentCursor.length;
210+
currentCursorFrameStartTime = now;
211+
glfwSetCursor(context.getWindowHandle(), currentCursor[currentCursorFrame]);
212+
}
213+
}
214+
215+
// Process events
194216
while (!mouseMotionEvents.isEmpty()) {
195217
listener.onMouseMotionEvent(mouseMotionEvents.poll());
196218
}
@@ -200,6 +222,7 @@ public void update() {
200222
}
201223
}
202224

225+
@Override
203226
public void destroy() {
204227
if (!context.isRenderable()) {
205228
return;
@@ -209,13 +232,19 @@ public void destroy() {
209232
scrollCallback.close();
210233
mouseButtonCallback.close();
211234

212-
for (long glfwCursor : jmeToGlfwCursorMap.values()) {
213-
glfwDestroyCursor(glfwCursor);
235+
currentCursor = null;
236+
currentCursorDelays = null;
237+
for (long[] glfwCursors : jmeToGlfwCursorMap.values()) {
238+
for (long glfwCursor : glfwCursors) {
239+
glfwDestroyCursor(glfwCursor);
240+
}
214241
}
242+
jmeToGlfwCursorMap.clear();
215243

216244
logger.fine("Mouse destroyed.");
217245
}
218246

247+
@Override
219248
public void setCursorVisible(boolean visible) {
220249
cursorVisible = visible;
221250

@@ -230,24 +259,26 @@ public void setCursorVisible(boolean visible) {
230259
}
231260
}
232261

262+
@Override
233263
public void setInputListener(RawInputListener listener) {
234264
this.listener = listener;
235265
}
236266

267+
@Override
237268
public long getInputTimeNanos() {
238269
return (long) (glfwGetTime() * 1000000000);
239270
}
240271

241-
private ByteBuffer transformCursorImage(IntBuffer imageData, int w, int h) {
242-
ByteBuffer buf = BufferUtils.createByteBuffer(imageData.capacity() * 4);
272+
private ByteBuffer transformCursorImage(IntBuffer imageData, int w, int h, int index) {
273+
ByteBuffer buf = BufferUtils.createByteBuffer(w * h * 4);
243274

244275
// Transform image: ARGB -> RGBA, vertical flip
245-
for (int y = h-1; y >= 0; --y) {
276+
for (int y = h - 1; y >= 0; --y) {
246277
for (int x = 0; x < w; ++x) {
247-
int pixel = imageData.get(y*w + x);
278+
int pixel = imageData.get(w * h * index + y * w + x);
248279
buf.put((byte) ((pixel >> 16) & 0xFF)); // red
249-
buf.put((byte) ((pixel >> 8) & 0xFF)); // green
250-
buf.put((byte) ( pixel & 0xFF)); // blue
280+
buf.put((byte) ((pixel >> 8) & 0xFF)); // green
281+
buf.put((byte) (pixel & 0xFF)); // blue
251282
buf.put((byte) ((pixel >> 24) & 0xFF)); // alpha
252283
}
253284
}
@@ -256,30 +287,43 @@ private ByteBuffer transformCursorImage(IntBuffer imageData, int w, int h) {
256287
return buf;
257288
}
258289

259-
private long createGlfwCursor(JmeCursor jmeCursor) {
260-
// TODO: currently animated cursors are not supported
261-
IntBuffer imageData = jmeCursor.getImagesData();
262-
ByteBuffer buf = transformCursorImage(imageData, jmeCursor.getWidth(), jmeCursor.getHeight());
290+
private long[] createGlfwCursor(JmeCursor jmeCursor) {
291+
long[] cursorArray = new long[jmeCursor.getNumImages()];
292+
for (int i = 0; i < jmeCursor.getNumImages(); i++) {
293+
ByteBuffer buf = transformCursorImage(jmeCursor.getImagesData(), jmeCursor.getWidth(), jmeCursor.getHeight(), i);
294+
295+
GLFWImage glfwImage = new GLFWImage(BufferUtils.createByteBuffer(GLFWImage.SIZEOF));
296+
glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf);
263297

264-
GLFWImage glfwImage = new GLFWImage(BufferUtils.createByteBuffer(GLFWImage.SIZEOF));
265-
glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf);
298+
int hotspotX = jmeCursor.getXHotSpot();
299+
int hotspotY = jmeCursor.getHeight() - jmeCursor.getYHotSpot();
266300

267-
int hotspotX = jmeCursor.getXHotSpot();
268-
int hotspotY = jmeCursor.getHeight() - jmeCursor.getYHotSpot();
269-
return glfwCreateCursor(glfwImage, hotspotX, hotspotY);
301+
cursorArray[i] = glfwCreateCursor(glfwImage, hotspotX, hotspotY);
302+
}
303+
return cursorArray;
270304
}
271305

306+
@Override
272307
public void setNativeCursor(JmeCursor jmeCursor) {
273308
if (jmeCursor != null) {
274-
Long glfwCursor = jmeToGlfwCursorMap.get(jmeCursor);
309+
long[] glfwCursor = jmeToGlfwCursorMap.get(jmeCursor);
275310

276311
if (glfwCursor == null) {
277312
glfwCursor = createGlfwCursor(jmeCursor);
278313
jmeToGlfwCursorMap.put(jmeCursor, glfwCursor);
279314
}
280315

281-
glfwSetCursor(context.getWindowHandle(), glfwCursor);
316+
currentCursorFrame = 0;
317+
currentCursor = glfwCursor;
318+
currentCursorDelays = null;
319+
currentCursorFrameStartTime = glfwGetTime();
320+
if (jmeCursor.getImagesDelay() != null) {
321+
currentCursorDelays = jmeCursor.getImagesDelay();
322+
}
323+
glfwSetCursor(context.getWindowHandle(), glfwCursor[currentCursorFrame]);
282324
} else {
325+
currentCursor = null;
326+
currentCursorDelays = null;
283327
glfwSetCursor(context.getWindowHandle(), MemoryUtil.NULL);
284328
}
285329
}

0 commit comments

Comments
 (0)