Formatting update
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / humaninput / HIDInputTracker.java
1 /*
2  * Sixth 3D engine. Author: Svjatoslav Agejenko. 
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.sixth.e3d.gui.humaninput;
6
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;
11
12 import javax.swing.*;
13 import java.awt.event.*;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18
19 public class HIDInputTracker implements
20         MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
21
22     /**
23      * <pre>
24      * Key is keyboard key code.
25      * Value is system milliseconds when key was pressed.
26      *
27      * So by reading the map one can determine currently pressed keys as well as duration.
28      * </pre>
29      */
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;
40
41     public HIDInputTracker(final ViewPanel viewPanel) {
42         this.viewPanel = viewPanel;
43         bind(viewPanel);
44     }
45
46     /**
47      * {@inheritDoc}
48      */
49     @Override
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;
56     }
57
58     private void bind(final JPanel panel) {
59         panel.addMouseMotionListener(this);
60
61         panel.addKeyListener(this);
62
63         panel.addMouseListener(this);
64
65         panel.addMouseWheelListener(this);
66     }
67
68     /**
69      * @return <code>true</code> if view needs to be repainted.
70      */
71     private boolean handleKeyboardEvents() {
72         final UserInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
73         ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
74
75         return currentFocusOwner != null
76                 && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
77     }
78
79     private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
80         synchronized (detectedKeyEvents) {
81             ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
82             detectedKeyEvents.clear();
83             return result;
84         }
85     }
86
87     /**
88      * @return <code>true</code> if view update is needed.
89      */
90     private boolean forwardKeyboardEventsToFocusOwner(
91             UserInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
92         boolean viewUpdateNeeded = false;
93
94         for (KeyEvent keyEvent : keyEvents)
95             viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
96
97         return viewUpdateNeeded;
98     }
99
100     private boolean processKeyEvent(UserInputHandler currentFocusOwner, KeyEvent keyEvent) {
101         switch (keyEvent.getID()) {
102             case KeyEvent.KEY_PRESSED:
103                 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
104
105             case KeyEvent.KEY_RELEASED:
106                 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
107         }
108         return false;
109     }
110
111     /**
112      * @return <code>true</code> if view needs to be repainted.
113      */
114     private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
115         boolean rerenderNeeded = false;
116         MouseEvent event = findClickLocationToTrace();
117         if (event != null){
118             // process mouse clicks as a first priority
119             rerenderNeeded = true;
120         } else {
121             // when there are no mouse clicks, process mouse hovering
122
123             if (mouseMoved) {
124                 mouseMoved = false;
125                 // we would like to re-render frame when user moved mouse, to see what objects mouse is hovering over
126                 rerenderNeeded = true;
127             }
128
129             if (currentMouseLocation != null) {
130                 // mouse click with button 0 amounts to mouse hovering event
131                 event = new MouseEvent(currentMouseLocation, 0);
132             }
133         }
134
135         if (viewPanel.getRenderingContext() != null)
136             viewPanel.getRenderingContext().setMouseEvent(event);
137
138         return rerenderNeeded;
139     }
140
141     private MouseEvent findClickLocationToTrace() {
142         synchronized (detectedMouseEvents) {
143             if (detectedMouseEvents.isEmpty())
144                 return null;
145
146             return detectedMouseEvents.remove(0);
147         }
148     }
149
150     boolean isKeyPressed(final int keyCode) {
151         return pressedKeysToPressedTimeMap.containsKey(keyCode);
152     }
153
154     @Override
155     public void keyPressed(final KeyEvent evt) {
156         synchronized (detectedKeyEvents) {
157             pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
158             detectedKeyEvents.add(evt);
159         }
160     }
161
162     @Override
163     public void keyReleased(final KeyEvent evt) {
164         synchronized (detectedKeyEvents) {
165             pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
166             detectedKeyEvents.add(evt);
167         }
168     }
169
170     @Override
171     public void keyTyped(final KeyEvent e) {
172     }
173
174     @Override
175     public void mouseClicked(final java.awt.event.MouseEvent e) {
176         synchronized (detectedMouseEvents) {
177             detectedMouseEvents.add(new MouseEvent(e.getX(), e.getY(), e.getButton()));
178         }
179     }
180
181     @Override
182     public void mouseDragged(final java.awt.event.MouseEvent evt) {
183         final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
184
185         if (oldMouseCoordinatesWhenDragging == null) {
186             oldMouseCoordinatesWhenDragging = mouseLocation;
187             return;
188         }
189
190         mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
191
192         oldMouseCoordinatesWhenDragging = mouseLocation;
193     }
194
195     @Override
196     public void mouseEntered(final java.awt.event.MouseEvent e) {
197         mouseWithinWindow = true;
198     }
199
200     @Override
201     public synchronized void mouseExited(final java.awt.event.MouseEvent e) {
202         mouseWithinWindow = false;
203         currentMouseLocation = null;
204     }
205
206     @Override
207     public synchronized void mouseMoved(final java.awt.event.MouseEvent e) {
208         currentMouseLocation = new Point2D(e.getX(), e.getY());
209         mouseMoved = true;
210     }
211
212     @Override
213     public void mousePressed(final java.awt.event.MouseEvent e) {
214     }
215
216     @Override
217     public void mouseReleased(final java.awt.event.MouseEvent evt) {
218         oldMouseCoordinatesWhenDragging = null;
219     }
220
221     @Override
222     public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
223         wheelMovedDirection += evt.getWheelRotation();
224     }
225
226     /**
227      * @return <code>true</code> if view needs to be repainted.
228      */
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;
237     }
238
239     /**
240      * @return <code>true</code> if view needs to be repainted.
241      */
242     private boolean handleMouseDragging() {
243         // TODO: need to detect whether user moved mouse or touch screen
244
245         final Avatar avatar = viewPanel.getAvatar();
246         // for mouse
247         avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
248         avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
249
250         // for touch screen
251         // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
252         // mouseDraggedDirection.x / 50));
253         // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
254         // mouseDraggedDirection.y / 50));
255
256         boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
257         mouseDraggedDirection.zero();
258         return viewUpdateNeeded;
259     }
260
261 }