Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 77 additions & 33 deletions jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.jme3.input.lwjgl;

import com.jme3.cursors.plugins.JmeCursor;
Expand All @@ -39,26 +38,26 @@
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.system.lwjgl.LwjglWindow;
import com.jme3.util.BufferUtils;
import org.lwjgl.glfw.GLFWCursorPosCallback;
import org.lwjgl.glfw.GLFWMouseButtonCallback;
import org.lwjgl.glfw.GLFWScrollCallback;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.logging.Logger;

import static org.lwjgl.glfw.GLFW.*;
import org.lwjgl.glfw.GLFWCursorPosCallback;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.glfw.GLFWMouseButtonCallback;
import org.lwjgl.glfw.GLFWScrollCallback;
import org.lwjgl.system.MemoryUtil;

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

private static final int WHEEL_SCALE = 120;

private LwjglWindow context;
private final LwjglWindow context;
private RawInputListener listener;
private boolean cursorVisible = true;
private long[] currentCursor;
private IntBuffer currentCursorDelays;
private int currentCursorFrame = 0;
private double currentCursorFrameStartTime = 0.0;
private int mouseX;
private int mouseY;
private int mouseWheel;
private boolean initialized;
private GLFWCursorPosCallback cursorPosCallback;
private GLFWScrollCallback scrollCallback;
private GLFWMouseButtonCallback mouseButtonCallback;
private Queue<MouseMotionEvent> mouseMotionEvents = new LinkedList<MouseMotionEvent>();
private Queue<MouseButtonEvent> mouseButtonEvents = new LinkedList<MouseButtonEvent>();
private final Queue<MouseMotionEvent> mouseMotionEvents = new ArrayDeque<>();
private final Queue<MouseButtonEvent> mouseButtonEvents = new ArrayDeque<>();

private Map<JmeCursor, Long> jmeToGlfwCursorMap = new HashMap<JmeCursor, Long>();
private final Map<JmeCursor, long[]> jmeToGlfwCursorMap = new HashMap<>();

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

@Override
public void initialize() {
glfwSetCursorPosCallback(context.getWindowHandle(), cursorPosCallback = new GLFWCursorPosCallback() {
@Override
Expand All @@ -150,6 +154,7 @@ public void callback(long args) {
public void invoke(final long window, final double xOffset, final double yOffset) {
onWheelScroll(window, xOffset, yOffset * WHEEL_SCALE);
}

@Override
public void close() {
super.close();
Expand All @@ -166,6 +171,7 @@ public void callback(long args) {
public void invoke(final long window, final int button, final int action, final int mods) {
onMouseButton(window, button, action, mods);
}

@Override
public void close() {
super.close();
Expand All @@ -182,15 +188,31 @@ public void callback(long args) {
initialized = true;
}

@Override
public boolean isInitialized() {
return initialized;
}

@Override
public int getButtonCount() {
return GLFW_MOUSE_BUTTON_LAST + 1;
}

@Override
public void update() {

// Manage cursor animation
if (currentCursor != null && currentCursor.length > 1) {
double now = glfwGetTime();
double frameTime = (glfwGetTime() - currentCursorFrameStartTime) * 1000;
if (currentCursorDelays == null || frameTime >= currentCursorDelays.get(currentCursorFrame)) {
currentCursorFrame = ++currentCursorFrame % currentCursor.length;
currentCursorFrameStartTime = now;
glfwSetCursor(context.getWindowHandle(), currentCursor[currentCursorFrame]);
}
}

// Process events
while (!mouseMotionEvents.isEmpty()) {
listener.onMouseMotionEvent(mouseMotionEvents.poll());
}
Expand All @@ -200,6 +222,7 @@ public void update() {
}
}

@Override
public void destroy() {
if (!context.isRenderable()) {
return;
Expand All @@ -209,13 +232,19 @@ public void destroy() {
scrollCallback.close();
mouseButtonCallback.close();

for (long glfwCursor : jmeToGlfwCursorMap.values()) {
glfwDestroyCursor(glfwCursor);
currentCursor = null;
currentCursorDelays = null;
for (long[] glfwCursors : jmeToGlfwCursorMap.values()) {
for (long glfwCursor : glfwCursors) {
glfwDestroyCursor(glfwCursor);
}
}
jmeToGlfwCursorMap.clear();

logger.fine("Mouse destroyed.");
}

@Override
public void setCursorVisible(boolean visible) {
cursorVisible = visible;

Expand All @@ -230,24 +259,26 @@ public void setCursorVisible(boolean visible) {
}
}

@Override
public void setInputListener(RawInputListener listener) {
this.listener = listener;
}

@Override
public long getInputTimeNanos() {
return (long) (glfwGetTime() * 1000000000);
}

private ByteBuffer transformCursorImage(IntBuffer imageData, int w, int h) {
ByteBuffer buf = BufferUtils.createByteBuffer(imageData.capacity() * 4);
private ByteBuffer transformCursorImage(IntBuffer imageData, int w, int h, int index) {
ByteBuffer buf = BufferUtils.createByteBuffer(w * h * 4);

// Transform image: ARGB -> RGBA, vertical flip
for (int y = h-1; y >= 0; --y) {
for (int y = h - 1; y >= 0; --y) {
for (int x = 0; x < w; ++x) {
int pixel = imageData.get(y*w + x);
int pixel = imageData.get(w * h * index + y * w + x);
buf.put((byte) ((pixel >> 16) & 0xFF)); // red
buf.put((byte) ((pixel >> 8) & 0xFF)); // green
buf.put((byte) ( pixel & 0xFF)); // blue
buf.put((byte) ((pixel >> 8) & 0xFF)); // green
buf.put((byte) (pixel & 0xFF)); // blue
buf.put((byte) ((pixel >> 24) & 0xFF)); // alpha
}
}
Expand All @@ -256,30 +287,43 @@ private ByteBuffer transformCursorImage(IntBuffer imageData, int w, int h) {
return buf;
}

private long createGlfwCursor(JmeCursor jmeCursor) {
// TODO: currently animated cursors are not supported
IntBuffer imageData = jmeCursor.getImagesData();
ByteBuffer buf = transformCursorImage(imageData, jmeCursor.getWidth(), jmeCursor.getHeight());
private long[] createGlfwCursor(JmeCursor jmeCursor) {
long[] cursorArray = new long[jmeCursor.getNumImages()];
for (int i = 0; i < jmeCursor.getNumImages(); i++) {
ByteBuffer buf = transformCursorImage(jmeCursor.getImagesData(), jmeCursor.getWidth(), jmeCursor.getHeight(), i);

GLFWImage glfwImage = new GLFWImage(BufferUtils.createByteBuffer(GLFWImage.SIZEOF));
glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf);

GLFWImage glfwImage = new GLFWImage(BufferUtils.createByteBuffer(GLFWImage.SIZEOF));
glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf);
int hotspotX = jmeCursor.getXHotSpot();
int hotspotY = jmeCursor.getHeight() - jmeCursor.getYHotSpot();

int hotspotX = jmeCursor.getXHotSpot();
int hotspotY = jmeCursor.getHeight() - jmeCursor.getYHotSpot();
return glfwCreateCursor(glfwImage, hotspotX, hotspotY);
cursorArray[i] = glfwCreateCursor(glfwImage, hotspotX, hotspotY);
}
return cursorArray;
}

@Override
public void setNativeCursor(JmeCursor jmeCursor) {
if (jmeCursor != null) {
Long glfwCursor = jmeToGlfwCursorMap.get(jmeCursor);
long[] glfwCursor = jmeToGlfwCursorMap.get(jmeCursor);

if (glfwCursor == null) {
glfwCursor = createGlfwCursor(jmeCursor);
jmeToGlfwCursorMap.put(jmeCursor, glfwCursor);
}

glfwSetCursor(context.getWindowHandle(), glfwCursor);
currentCursorFrame = 0;
currentCursor = glfwCursor;
currentCursorDelays = null;
currentCursorFrameStartTime = glfwGetTime();
if (jmeCursor.getImagesDelay() != null) {
currentCursorDelays = jmeCursor.getImagesDelay();
}
glfwSetCursor(context.getWindowHandle(), glfwCursor[currentCursorFrame]);
} else {
currentCursor = null;
currentCursorDelays = null;
glfwSetCursor(context.getWindowHandle(), MemoryUtil.NULL);
}
}
Expand Down