2 * Sixth 3D engine. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.sixth.e3d.gui.humaninput;
7 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
8 import eu.svjatoslav.sixth.e3d.gui.Avatar;
9 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
10 import eu.svjatoslav.sixth.e3d.gui.ViewRenderListener;
13 import java.awt.event.*;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.List;
20 * Human input device input tracker.
22 * Idea is to capture all keyboard and mouse inputs from underlying operating system in this class
23 * and forward those as needed to subsequent virtual components.
25 public class HIDInputTracker implements
26 MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
30 * Key is keyboard key code.
31 * Value is system milliseconds when key was pressed.
33 * So by reading the map one can determine currently pressed keys as well as duration.
36 private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
37 private final List<MouseEvent> detectedMouseEvents = new ArrayList<>();
38 private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
39 private int wheelMovedDirection = 0;
40 private Point2D mouseDraggedDirection = new Point2D();
41 private Point2D oldMouseCoordinatesWhenDragging;
42 private ViewPanel viewPanel;
43 private Point2D currentMouseLocation;
44 private boolean mouseMoved;
45 private boolean mouseWithinWindow = false;
47 public HIDInputTracker(final ViewPanel viewPanel) {
48 this.viewPanel = viewPanel;
56 public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
57 boolean viewUpdateNeeded = handleKeyboardEvents();
58 viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
59 viewUpdateNeeded |= handleMouseDragging();
60 viewUpdateNeeded |= handleMouseVerticalScrolling();
61 return viewUpdateNeeded;
64 private void bind(final JPanel panel) {
65 panel.addMouseMotionListener(this);
67 panel.addKeyListener(this);
69 panel.addMouseListener(this);
71 panel.addMouseWheelListener(this);
75 * @return <code>true</code> if view needs to be repainted.
77 private boolean handleKeyboardEvents() {
78 final KeyboardInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
79 ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
81 return currentFocusOwner != null
82 && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
85 private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
86 synchronized (detectedKeyEvents) {
87 ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
88 detectedKeyEvents.clear();
94 * @return <code>true</code> if view update is needed.
96 private boolean forwardKeyboardEventsToFocusOwner(
97 KeyboardInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
98 boolean viewUpdateNeeded = false;
100 for (KeyEvent keyEvent : keyEvents)
101 viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
103 return viewUpdateNeeded;
106 private boolean processKeyEvent(KeyboardInputHandler currentFocusOwner, KeyEvent keyEvent) {
107 switch (keyEvent.getID()) {
108 case KeyEvent.KEY_PRESSED:
109 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
111 case KeyEvent.KEY_RELEASED:
112 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
118 * @return <code>true</code> if view needs to be repainted.
120 private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
121 boolean rerenderNeeded = false;
122 MouseEvent event = findClickLocationToTrace();
124 // process mouse clicks as a first priority
125 rerenderNeeded = true;
127 // when there are no mouse clicks, process mouse hovering
131 // we would like to re-render frame when user moved mouse, to see what objects mouse is hovering over
132 rerenderNeeded = true;
135 if (currentMouseLocation != null) {
136 // mouse click with button 0 amounts to mouse hovering event
137 event = new MouseEvent(currentMouseLocation, 0);
141 if (viewPanel.getRenderingContext() != null)
142 viewPanel.getRenderingContext().setMouseEvent(event);
144 return rerenderNeeded;
147 private MouseEvent findClickLocationToTrace() {
148 synchronized (detectedMouseEvents) {
149 if (detectedMouseEvents.isEmpty())
152 return detectedMouseEvents.remove(0);
156 boolean isKeyPressed(final int keyCode) {
157 return pressedKeysToPressedTimeMap.containsKey(keyCode);
161 public void keyPressed(final KeyEvent evt) {
162 synchronized (detectedKeyEvents) {
163 pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
164 detectedKeyEvents.add(evt);
169 public void keyReleased(final KeyEvent evt) {
170 synchronized (detectedKeyEvents) {
171 pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
172 detectedKeyEvents.add(evt);
177 public void keyTyped(final KeyEvent e) {
181 public void mouseClicked(final java.awt.event.MouseEvent e) {
182 synchronized (detectedMouseEvents) {
183 detectedMouseEvents.add(new MouseEvent(e.getX(), e.getY(), e.getButton()));
188 public void mouseDragged(final java.awt.event.MouseEvent evt) {
189 final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
191 if (oldMouseCoordinatesWhenDragging == null) {
192 oldMouseCoordinatesWhenDragging = mouseLocation;
196 mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
198 oldMouseCoordinatesWhenDragging = mouseLocation;
202 public void mouseEntered(final java.awt.event.MouseEvent e) {
203 mouseWithinWindow = true;
207 public synchronized void mouseExited(final java.awt.event.MouseEvent e) {
208 mouseWithinWindow = false;
209 currentMouseLocation = null;
213 public synchronized void mouseMoved(final java.awt.event.MouseEvent e) {
214 currentMouseLocation = new Point2D(e.getX(), e.getY());
219 public void mousePressed(final java.awt.event.MouseEvent e) {
223 public void mouseReleased(final java.awt.event.MouseEvent evt) {
224 oldMouseCoordinatesWhenDragging = null;
228 public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
229 wheelMovedDirection += evt.getWheelRotation();
233 * @return <code>true</code> if view needs to be repainted.
235 private boolean handleMouseVerticalScrolling() {
236 final Avatar avatar = viewPanel.getAvatar();
237 final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
238 avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
239 avatar.enforceSpeedLimit();
240 boolean repaintNeeded = wheelMovedDirection != 0;
241 wheelMovedDirection = 0;
242 return repaintNeeded;
246 * @return <code>true</code> if view needs to be repainted.
248 private boolean handleMouseDragging() {
249 // TODO: It would be nice here to detect somehow whether user moved mouse or touch screen.
250 // in case of touch screen, we would like to reverse movement along X and Y axis.
252 final Avatar avatar = viewPanel.getAvatar();
254 avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
255 avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
258 // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
259 // mouseDraggedDirection.x / 50));
260 // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
261 // mouseDraggedDirection.y / 50));
263 boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
264 mouseDraggedDirection.zero();
265 return viewUpdateNeeded;