我试图为 Swing 创建 GLFW 无限鼠标模式,这是我迄今为止的进展
package javax.swing.extras;
// AWT Imports
import java.awt.AWTException;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
// Swing Imports
import javax.swing.JFrame;
/**
* An extension class that allows JFrames to have infinite input as seen in most
* 3D games.
*/
public class InfiniteMouse {
/** Image for the blank cursor */
private static final BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
/** Blank cursor */
private static final Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0),
"blank cursor");
/** Mouse X */
private int x;
/** Mouse Y */
private int y;
/** Previous Mouse X */
private int previousX;
/** Previous Mouse Y */
private int previousY;
/** Center X */
private int centerX;
/** Center Y */
private int centerY;
/**
* Create a InfiniteMouse extension for a JFrame
*
* @param f - The JFrame to add the extension
* @param c - The Container to detect mouse motion
* @return an instance of InfiniteMouse containing the x and y
* @throws AWTException if the platform configuration does not allowlow-level
* input control. This exception is always thrown
* whenGraphicsEnvironment.isHeadless() returns true
*/
public static InfiniteMouse addExtension(JFrame f, Container c) throws AWTException {
// Create a new instance
InfiniteMouse infiniteMouse = new InfiniteMouse();
// Create a final Robot class to later move the mouse to the center
final Robot robot = new Robot();
// Store the on-screen center x of the window
infiniteMouse.centerX = f.getX() + f.getWidth() / 2;
// Store the on-screen center y of the window
infiniteMouse.centerY = f.getY() + f.getHeight() / 2;
// Add a resize listener
f.addComponentListener(new ComponentAdapter() {
// If the frame is resized
public void componentResized(ComponentEvent e) {
// Update the X position
infiniteMouse.centerX = f.getX() + f.getWidth() / 2;
// Update the Y position
infiniteMouse.centerY = f.getY() + f.getHeight() / 2;
}
});
// Make the cursor invisible
f.setCursor(blankCursor);
// Add a mouse listener
c.addMouseMotionListener(new MouseMotionListener() {
// If the mouse is moved
public void mouseMoved(MouseEvent e) {
// Get the current X
int currentX = e.getX();
// Get the current Y
int currentY = e.getY();
System.out.println(infiniteMouse.x);
// Get the mouse X position delta
infiniteMouse.x += currentX - infiniteMouse.previousX;
// Get the mouse Y position delta
infiniteMouse.y += currentY - infiniteMouse.previousY;
// Remove the listener when moving to the center
c.removeMouseMotionListener(this);
// Move the mouse to the center
robot.mouseMove(infiniteMouse.centerX, infiniteMouse.centerY);
// Add the listener back
c.addMouseMotionListener(this);
// Update the previous X
infiniteMouse.previousX = currentX;
// Update the previous Y
infiniteMouse.previousY = currentY;
}
@Override
public void mouseDragged(MouseEvent e) {
}
});
// Return the newly created instance
return infiniteMouse;
}
/**
* Get the X position
*
* @return the X position
*/
public int getX() {
return x;
}
/**
* Get the Y position
*
* @return the Y position
*/
public int getY() {
return y;
}
}
在此代码中使用
package javax.swing.extras;
import java.awt.AWTException;
import java.awt.Color;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) throws AWTException {
JFrame jf = new JFrame();
JPanel panel1 = new JPanel();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setTitle("Window");
jf.setSize(800,600);
jf.setLocationRelativeTo(null);
jf.add(panel1);
panel1.grabFocus();
InfiniteMouse.addExtension(jf, panel1);
jf.setVisible(true);
}
}
然而,我在这段代码中面临的问题是,在控制台中调试的鼠标 X 确实没有增加,我怀疑这是这些行的问题
c.removeMouseMotionListener(this);
robot.mouseMove(infiniteMouse.centerX, infiniteMouse.centerY);
c.addMouseMotionListener(this);
因为它保持在相同的值附近,除非我退出并移动
JFrame
并将其移回到窗口中。嗯,这一行的目的是暂停鼠标运动侦听器,以便我可以将鼠标移回,然后恢复侦听器。我可以使用此方法的有效替代方法吗?还是其他问题?
代码中的以下片段:
// Remove the listener when moving to the center
c.removeMouseMotionListener(this);
// Move the mouse to the center
robot.mouseMove(infiniteMouse.centerX, infiniteMouse.centerY);
// Add the listener back
c.addMouseMotionListener(this);
应该与以下效果相同:
// Move the mouse to the center
robot.mouseMove(infiniteMouse.centerX, infiniteMouse.centerY);
事件调度线程 (EDT) 是一个单线程,据我所知,所有 Swing 事件都发布到它。在 EDT 上调用
mouseMoved
方法。您不会得到交错的 mouseMoved
调用,而是按顺序得到它们。因此,在第一个代码片段中,您实际上是在同一线程上的同一触发事件代码内删除侦听器并将其重新添加到组件中。据我了解,robot.mouseMove
最终会在EDTqueue上创建另一个
mouseMoved
事件,但由于EDT是单线程并且其队列中的所有事件都是顺序执行的,因此由mouseMoved
将在当前之后的某个时间点调度
robot.mouseMove
打电话。但监听器始终存在于组件中(只要我们在外部mouseMoved
)。当调度 mouseMoved
生成的事件时,将调用组件的侦听器,其中将包括第一个片段的侦听器(出于上述原因和代码)。其他一些次要考虑因素是:
以下代码:
robot.mouseMove
// Get the mouse Y position delta
infiniteMouse.y += currentY - infiniteMouse.previousY;
不过,这个问题并不重要,只是说而已。您将鼠标设置到框架的中心。您的意思是将其设置为组件吗?不过,这个问题并不重要,只是说而已。// Get the mouse Y position delta
infiniteMouse.y += infiniteMouse.previousY - currentY;
预计会生成鼠标事件,因此一种解决方案可能是在调度的某个时刻丢弃它们。我认为最方便的方法是在鼠标侦听器内部,这是我们可以轻松控制的事件的最后目的地。尽管如此,我找不到一种简单的(或非假设的)方法来区分侦听器内用户和机器人生成的鼠标移动事件。我还假设我们无法知道
robot.mouseMove
何时真正完成光标的本机移动(这是我在文档中找不到发生的保证)。例如,robot.mouseMove
可能只是向操作系统发出移动鼠标的请求,然后立即返回。因此我们无法确定该请求何时完成。另外,我们不能阻止等待鼠标到达目的地,因为我们将丢失实际导致鼠标移动到目的地的信息(是用户吗?只是 robot.mouseMove
?他们的移动的组合?等等) 。此外,Robot
无法在 EDT 内调用(引发异常),因此需要额外的线程,但其文档指出:
等待当前事件队列中的所有事件均已处理完毕。对我来说,处理(...这并不(至少对我来说)意味着本机事件将完成。
robot.waitForIdle
)队列中的事件
仅意味着对操作系统的请求成功,但这并不意味着操作系统尚未完成它。然后我意识到我们可以主动取消
Robot
的事件,因为我们知道
Robot
将生成的事件,因为在任何时候我们都知道鼠标的当前位置,如以及我们想要它去的地方。可以在我们的代码调用 robot.mouseMove
之前(或之后)从 InfiniteMouse
位置减去这两个位置产生的增量。当尝试在代码中编写此内容时,我最终使用了一个更简单的概念:只需假设鼠标始终(在每次 robot.mouseMove
调用之后)位于同一位置,并且 不保持 前一个 位置(我们总是知道/假设它位于我们指定的位置)。当然,这需要在每个
mouseMoved
事件上调用 robot.mouseMove
到该恒定位置,以便假设成立:mouseMoved
上面的代码是尝试解决该问题的 MVC 设计,其中grid
被假设为用户在绘图面板中看到的 2D 地图。在某个时刻,增量会添加到网格的位置,从而移动网格。我认为这可以更改为 3D 游戏中的视线度数,例如您的情况。单击绘图面板内部开始捕获鼠标,然后按 ESCAPE 停止捕获。代码被记录下来以指导读者完成。