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;
19 public class HIDInputTracker implements
20 MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
24 * Key is keyboard key code.
25 * Value is system milliseconds when key was pressed.
27 * So by reading the map one can determine currently pressed keys as well as duration.
30 private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
31 private final List<MouseEvent> detectedMouseEvents = new ArrayList<>();
32 private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
33 private int wheelMovedDirection = 0;
34 private Point2D mouseDraggedDirection = new Point2D();
35 private Point2D oldMouseCoordinatesWhenDragging;
36 private ViewPanel viewPanel;
37 private Point2D currentMouseLocation;
38 private boolean mouseMoved;
39 private boolean mouseWithinWindow = false;
41 public HIDInputTracker(final ViewPanel viewPanel) {
42 this.viewPanel = viewPanel;
50 public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
51 boolean viewUpdateNeeded = handleKeyboardEvents();
52 viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
53 viewUpdateNeeded |= handleMouseDragging();
54 viewUpdateNeeded |= handleMouseVerticalScrolling();
55 return viewUpdateNeeded;
58 private void bind(final JPanel panel) {
59 panel.addMouseMotionListener(this);
61 panel.addKeyListener(this);
63 panel.addMouseListener(this);
65 panel.addMouseWheelListener(this);
69 * @return <code>true</code> if view needs to be repainted.
71 private boolean handleKeyboardEvents() {
72 final UserInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
73 ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
75 return currentFocusOwner != null
76 && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
79 private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
80 synchronized (detectedKeyEvents) {
81 ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
82 detectedKeyEvents.clear();
88 * @return <code>true</code> if view update is needed.
90 private boolean forwardKeyboardEventsToFocusOwner(
91 UserInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
92 boolean viewUpdateNeeded = false;
94 for (KeyEvent keyEvent : keyEvents)
95 viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
97 return viewUpdateNeeded;
100 private boolean processKeyEvent(UserInputHandler currentFocusOwner, KeyEvent keyEvent) {
101 switch (keyEvent.getID()) {
102 case KeyEvent.KEY_PRESSED:
103 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
105 case KeyEvent.KEY_RELEASED:
106 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
112 * @return <code>true</code> if view needs to be repainted.
114 private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
115 boolean rerenderNeeded = false;
116 MouseEvent event = findClickLocationToTrace();
118 // process mouse clicks as a first priority
119 rerenderNeeded = true;
121 // when there are no mouse clicks, process mouse hovering
125 // we would like to re-render frame when user moved mouse, to see what objects mouse is hovering over
126 rerenderNeeded = true;
129 if (currentMouseLocation != null) {
130 // mouse click with button 0 amounts to mouse hovering event
131 event = new MouseEvent(currentMouseLocation, 0);
135 if (viewPanel.getRenderingContext() != null)
136 viewPanel.getRenderingContext().setMouseEvent(event);
138 return rerenderNeeded;
141 private MouseEvent findClickLocationToTrace() {
142 synchronized (detectedMouseEvents) {
143 if (detectedMouseEvents.isEmpty())
146 return detectedMouseEvents.remove(0);
150 boolean isKeyPressed(final int keyCode) {
151 return pressedKeysToPressedTimeMap.containsKey(keyCode);
155 public void keyPressed(final KeyEvent evt) {
156 synchronized (detectedKeyEvents) {
157 pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
158 detectedKeyEvents.add(evt);
163 public void keyReleased(final KeyEvent evt) {
164 synchronized (detectedKeyEvents) {
165 pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
166 detectedKeyEvents.add(evt);
171 public void keyTyped(final KeyEvent e) {
175 public void mouseClicked(final java.awt.event.MouseEvent e) {
176 synchronized (detectedMouseEvents) {
177 detectedMouseEvents.add(new MouseEvent(e.getX(), e.getY(), e.getButton()));
182 public void mouseDragged(final java.awt.event.MouseEvent evt) {
183 final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
185 if (oldMouseCoordinatesWhenDragging == null) {
186 oldMouseCoordinatesWhenDragging = mouseLocation;
190 mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
192 oldMouseCoordinatesWhenDragging = mouseLocation;
196 public void mouseEntered(final java.awt.event.MouseEvent e) {
197 mouseWithinWindow = true;
201 public synchronized void mouseExited(final java.awt.event.MouseEvent e) {
202 mouseWithinWindow = false;
203 currentMouseLocation = null;
207 public synchronized void mouseMoved(final java.awt.event.MouseEvent e) {
208 currentMouseLocation = new Point2D(e.getX(), e.getY());
213 public void mousePressed(final java.awt.event.MouseEvent e) {
217 public void mouseReleased(final java.awt.event.MouseEvent evt) {
218 oldMouseCoordinatesWhenDragging = null;
222 public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
223 wheelMovedDirection += evt.getWheelRotation();
227 * @return <code>true</code> if view needs to be repainted.
229 private boolean handleMouseVerticalScrolling() {
230 final Avatar avatar = viewPanel.getAvatar();
231 final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
232 avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
233 avatar.enforceSpeedLimit();
234 boolean repaintNeeded = wheelMovedDirection != 0;
235 wheelMovedDirection = 0;
236 return repaintNeeded;
240 * @return <code>true</code> if view needs to be repainted.
242 private boolean handleMouseDragging() {
243 // TODO: need to detect whether user moved mouse or touch screen
245 final Avatar avatar = viewPanel.getAvatar();
247 avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
248 avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
251 // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
252 // mouseDraggedDirection.x / 50));
253 // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
254 // mouseDraggedDirection.y / 50));
256 boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
257 mouseDraggedDirection.zero();
258 return viewUpdateNeeded;