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