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 * This class is responsible for tracking human input devices (keyboard, mouse, etc.) and
21 * forwarding those inputs to subsequent virtual components.
23 public class HIDEventTracker implements
24 MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
27 * <p> Map of pressed keys. </p>
28 * <p> Key is mouse button code. </p>
29 * <p> Value is system milliseconds when button was pressed. </p>
30 * <p> So by reading the map one can determine currently pressed buttons as well as duration. </p>
32 private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
33 private final List<MouseEvent> detectedMouseEvents = new ArrayList<>();
34 private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
35 private final Point2D mouseDraggedDirection = new Point2D();
36 private final ViewPanel viewPanel;
37 private int wheelMovedDirection = 0;
38 private Point2D oldMouseCoordinatesWhenDragging;
39 private Point2D currentMouseLocation;
40 private boolean mouseMoved;
41 private boolean mouseWithinWindow = false;
44 * Construct new tracker for specified panel.
46 public HIDEventTracker(final ViewPanel viewPanel) {
47 this.viewPanel = viewPanel;
55 public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
56 boolean viewUpdateNeeded = handleKeyboardEvents();
57 viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
58 viewUpdateNeeded |= handleMouseDragging();
59 viewUpdateNeeded |= handleMouseVerticalScrolling();
60 return viewUpdateNeeded;
64 * Bind this tracker to specified panel.
65 * @param panel panel to bind to.
67 private void bind(final JPanel panel) {
68 panel.addMouseMotionListener(this);
70 panel.addKeyListener(this);
72 panel.addMouseListener(this);
74 panel.addMouseWheelListener(this);
78 * @return <code>true</code> if view needs to be repainted.
80 private boolean handleKeyboardEvents() {
81 final KeyboardInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
82 ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
84 return currentFocusOwner != null
85 && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
88 private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
89 synchronized (detectedKeyEvents) {
90 ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
91 detectedKeyEvents.clear();
97 * @return <code>true</code> if view update is needed.
99 private boolean forwardKeyboardEventsToFocusOwner(
100 KeyboardInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
101 boolean viewUpdateNeeded = false;
103 for (KeyEvent keyEvent : keyEvents)
104 viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
106 return viewUpdateNeeded;
109 private boolean processKeyEvent(KeyboardInputHandler currentFocusOwner, KeyEvent keyEvent) {
110 switch (keyEvent.getID()) {
111 case KeyEvent.KEY_PRESSED:
112 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
114 case KeyEvent.KEY_RELEASED:
115 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
121 * @return <code>true</code> if view needs to be repainted.
123 private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
124 boolean rerenderNeeded = false;
125 MouseEvent event = findClickLocationToTrace();
127 // process mouse clicks as a first priority
128 rerenderNeeded = true;
130 // when there are no mouse clicks, process mouse hovering
134 // we would like to re-render frame when user moved mouse, to see what objects mouse is hovering over
135 rerenderNeeded = true;
138 if (currentMouseLocation != null) {
139 // mouse click with button 0 amounts to mouse hovering event
140 event = new MouseEvent(currentMouseLocation, 0);
144 if (viewPanel.getRenderingContext() != null)
145 viewPanel.getRenderingContext().setMouseEvent(event);
147 return rerenderNeeded;
150 private MouseEvent findClickLocationToTrace() {
151 synchronized (detectedMouseEvents) {
152 if (detectedMouseEvents.isEmpty())
155 return detectedMouseEvents.remove(0);
159 boolean isKeyPressed(final int keyCode) {
160 return pressedKeysToPressedTimeMap.containsKey(keyCode);
164 public void keyPressed(final KeyEvent evt) {
165 synchronized (detectedKeyEvents) {
166 pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
167 detectedKeyEvents.add(evt);
172 public void keyReleased(final KeyEvent evt) {
173 synchronized (detectedKeyEvents) {
174 pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
175 detectedKeyEvents.add(evt);
180 public void keyTyped(final KeyEvent e) {
184 public void mouseClicked(final java.awt.event.MouseEvent e) {
185 synchronized (detectedMouseEvents) {
186 detectedMouseEvents.add(new MouseEvent(e.getX(), e.getY(), e.getButton()));
191 public void mouseDragged(final java.awt.event.MouseEvent evt) {
192 final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
194 if (oldMouseCoordinatesWhenDragging == null) {
195 oldMouseCoordinatesWhenDragging = mouseLocation;
199 mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
201 oldMouseCoordinatesWhenDragging = mouseLocation;
205 public void mouseEntered(final java.awt.event.MouseEvent e) {
206 mouseWithinWindow = true;
210 public synchronized void mouseExited(final java.awt.event.MouseEvent e) {
211 mouseWithinWindow = false;
212 currentMouseLocation = null;
216 public synchronized void mouseMoved(final java.awt.event.MouseEvent e) {
217 currentMouseLocation = new Point2D(e.getX(), e.getY());
222 public void mousePressed(final java.awt.event.MouseEvent e) {
226 public void mouseReleased(final java.awt.event.MouseEvent evt) {
227 oldMouseCoordinatesWhenDragging = null;
231 public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
232 wheelMovedDirection += evt.getWheelRotation();
236 * @return <code>true</code> if view needs to be repainted.
238 private boolean handleMouseVerticalScrolling() {
239 final Avatar avatar = viewPanel.getAvatar();
240 final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
241 avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
242 avatar.enforceSpeedLimit();
243 boolean repaintNeeded = wheelMovedDirection != 0;
244 wheelMovedDirection = 0;
245 return repaintNeeded;
249 * @return <code>true</code> if view needs to be repainted.
251 private boolean handleMouseDragging() {
252 // TODO: It would be nice here to detect somehow whether user moved mouse or touch screen.
253 // in case of touch screen, we would like to reverse movement along X and Y axis.
255 final Avatar avatar = viewPanel.getAvatar();
257 avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
258 avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
261 // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
262 // mouseDraggedDirection.x / 50));
263 // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
264 // mouseDraggedDirection.y / 50));
266 boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
267 mouseDraggedDirection.zero();
268 return viewUpdateNeeded;