2 * Sixth 3D engine. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
8 package eu.svjatoslav.sixth.e3d.gui.humaninput;
10 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
11 import eu.svjatoslav.sixth.e3d.gui.Avatar;
12 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
13 import eu.svjatoslav.sixth.e3d.gui.ViewRenderListener;
16 import java.awt.event.*;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
22 public class HIDInputTracker implements
23 MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
27 * Key is keyboard key code.
28 * Value is system milliseconds when key was pressed.
30 * So by reading the map one can determine currently pressed keys as well as duration.
33 private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
34 private final List<MouseEvent> detectedMouseEvents = new ArrayList<>();
35 private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
36 private int wheelMovedDirection = 0;
37 private Point2D mouseDraggedDirection = new Point2D();
38 private Point2D oldMouseCoordinatesWhenDragging;
39 private ViewPanel viewPanel;
40 private Point2D currentMouseLocation;
41 private boolean mouseMoved;
42 private boolean mouseWithinWindow = false;
44 public HIDInputTracker(final ViewPanel viewPanel) {
45 this.viewPanel = viewPanel;
53 public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
54 boolean viewUpdateNeeded = handleKeyboardEvents();
55 viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
56 viewUpdateNeeded |= handleMouseDragging();
57 viewUpdateNeeded |= handleMouseVerticalScrolling();
58 return viewUpdateNeeded;
61 private void bind(final JPanel panel) {
62 panel.addMouseMotionListener(this);
64 panel.addKeyListener(this);
66 panel.addMouseListener(this);
68 panel.addMouseWheelListener(this);
72 * @return <code>true</code> if view needs to be repainted.
74 private boolean handleKeyboardEvents() {
75 final UserInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
76 ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
78 return currentFocusOwner != null
79 && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
82 private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
83 synchronized (detectedKeyEvents) {
84 ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
85 detectedKeyEvents.clear();
91 * @return <code>true</code> if view update is needed.
93 private boolean forwardKeyboardEventsToFocusOwner(
94 UserInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
95 boolean viewUpdateNeeded = false;
97 for (KeyEvent keyEvent : keyEvents)
98 viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
100 return viewUpdateNeeded;
103 private boolean processKeyEvent(UserInputHandler currentFocusOwner, KeyEvent keyEvent) {
104 switch (keyEvent.getID()) {
105 case KeyEvent.KEY_PRESSED:
106 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
108 case KeyEvent.KEY_RELEASED:
109 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
115 * @return <code>true</code> if view needs to be repainted.
117 private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
118 boolean rerenderNeeded = false;
119 MouseEvent event = findClickLocationToTrace();
121 // process mouse clicks as a first priority
122 rerenderNeeded = true;
124 // when there are no mouse clicks, process mouse hovering
128 // we would like to re-render frame when user moved mouse, to see what objects mouse is hovering over
129 rerenderNeeded = true;
132 if (currentMouseLocation != null) {
133 // mouse click with button 0 amounts to mouse hovering event
134 event = new MouseEvent(currentMouseLocation, 0);
138 if (viewPanel.getRenderingContext() != null)
139 viewPanel.getRenderingContext().setMouseEvent(event);
141 return rerenderNeeded;
144 private MouseEvent findClickLocationToTrace() {
145 synchronized (detectedMouseEvents) {
146 if (detectedMouseEvents.isEmpty())
149 return detectedMouseEvents.remove(0);
153 boolean isKeyPressed(final int keyCode) {
154 return pressedKeysToPressedTimeMap.containsKey(keyCode);
158 public void keyPressed(final KeyEvent evt) {
159 synchronized (detectedKeyEvents) {
160 pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
161 detectedKeyEvents.add(evt);
166 public void keyReleased(final KeyEvent evt) {
167 synchronized (detectedKeyEvents) {
168 pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
169 detectedKeyEvents.add(evt);
174 public void keyTyped(final KeyEvent e) {
178 public void mouseClicked(final java.awt.event.MouseEvent e) {
179 synchronized (detectedMouseEvents) {
180 detectedMouseEvents.add(new MouseEvent(e.getX(), e.getY(), e.getButton()));
185 public void mouseDragged(final java.awt.event.MouseEvent evt) {
186 final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
188 if (oldMouseCoordinatesWhenDragging == null) {
189 oldMouseCoordinatesWhenDragging = mouseLocation;
193 mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
195 oldMouseCoordinatesWhenDragging = mouseLocation;
199 public void mouseEntered(final java.awt.event.MouseEvent e) {
200 mouseWithinWindow = true;
204 public synchronized void mouseExited(final java.awt.event.MouseEvent e) {
205 mouseWithinWindow = false;
206 currentMouseLocation = null;
210 public synchronized void mouseMoved(final java.awt.event.MouseEvent e) {
211 currentMouseLocation = new Point2D(e.getX(), e.getY());
216 public void mousePressed(final java.awt.event.MouseEvent e) {
220 public void mouseReleased(final java.awt.event.MouseEvent evt) {
221 oldMouseCoordinatesWhenDragging = null;
225 public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
226 wheelMovedDirection += evt.getWheelRotation();
230 * @return <code>true</code> if view needs to be repainted.
232 private boolean handleMouseVerticalScrolling() {
233 final Avatar avatar = viewPanel.getAvatar();
234 final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
235 avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
236 avatar.enforceSpeedLimit();
237 boolean repaintNeeded = wheelMovedDirection != 0;
238 wheelMovedDirection = 0;
239 return repaintNeeded;
243 * @return <code>true</code> if view needs to be repainted.
245 private boolean handleMouseDragging() {
246 // TODO: need to detect whether user moved mouse or touch screen
248 final Avatar avatar = viewPanel.getAvatar();
250 avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
251 avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
254 // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
255 // mouseDraggedDirection.x / 50));
256 // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
257 // mouseDraggedDirection.y / 50));
259 boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
260 mouseDraggedDirection.zero();
261 return viewUpdateNeeded;