Refactoring.
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / humaninput / UserInputTracker.java
1 /*
2  * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of version 3 of the GNU Lesser General Public License
6  * or later as published by the Free Software Foundation.
7  *
8  */
9
10 package eu.svjatoslav.sixth.e3d.gui.humaninput;
11
12 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
13 import eu.svjatoslav.sixth.e3d.gui.Avatar;
14 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
15 import eu.svjatoslav.sixth.e3d.gui.ViewRenderListener;
16
17 import javax.swing.*;
18 import java.awt.event.*;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 public class UserInputTracker implements
25         MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
26
27     /**
28      * <pre>
29      * Key is keyboard key code.
30      * Value is system milliseconds when key was pressed.
31      *
32      * So by reading the map one can determine currently pressed keys as well as duration.
33      * </pre>
34      */
35     private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
36     private final List<MouseClick> detectedMouseClicks = new ArrayList<>();
37     private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
38     private int wheelMovedDirection = 0;
39     private Point2D mouseDraggedDirection = new Point2D();
40     private Point2D oldMouseCoordinatesWhenDragging;
41     private ViewPanel viewPanel;
42     private Point2D currentMouseLocation;
43     private boolean mouseMoved;
44     private boolean mouseWithinWindow = false;
45
46     public UserInputTracker(final ViewPanel viewPanel) {
47         this.viewPanel = viewPanel;
48         bind(viewPanel);
49     }
50
51     /**
52      * {@inheritDoc}
53      */
54     @Override
55     public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
56
57         boolean viewUpdateNeeded = handleKeyboardEvents();
58         viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
59         viewUpdateNeeded |= handleMouseDragging();
60         viewUpdateNeeded |= handleMouseVerticalScrolling();
61
62         return viewUpdateNeeded;
63     }
64
65     private void bind(final JPanel panel) {
66         panel.addMouseMotionListener(this);
67
68         panel.addKeyListener(this);
69
70         panel.addMouseListener(this);
71
72         panel.addMouseWheelListener(this);
73     }
74
75     /**
76      * @return <code>true</code> if view needs to be repainted.
77      */
78     private boolean handleKeyboardEvents() {
79         final UserInputHandler currentFocusOwner = viewPanel.getKeyboardFocusTracker().getCurrentFocusOwner();
80         ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
81
82         return currentFocusOwner == null ? false :
83                 forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
84     }
85
86     private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
87         synchronized (detectedKeyEvents) {
88             ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
89             detectedKeyEvents.clear();
90             return result;
91         }
92     }
93
94     /**
95      * @return <code>true</code> if view update is needed.
96      */
97     private boolean forwardKeyboardEventsToFocusOwner(
98             UserInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
99         boolean viewUpdateNeeded = false;
100
101         for (KeyEvent keyEvent : keyEvents)
102             viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
103
104         return viewUpdateNeeded;
105     }
106
107     private boolean processKeyEvent(UserInputHandler currentFocusOwner, KeyEvent keyEvent) {
108         switch (keyEvent.getID()) {
109             case KeyEvent.KEY_PRESSED:
110                 return currentFocusOwner.keyPressed(keyEvent, viewPanel);
111
112             case KeyEvent.KEY_RELEASED:
113                 return currentFocusOwner.keyReleased(keyEvent, viewPanel);
114         }
115         return false;
116     }
117
118     /**
119      * @return <code>true</code> if view needs to be repainted.
120      */
121     private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
122         MouseClick unprocessedMouseClick = findUnprocessedMouseClick();
123
124         if (unprocessedMouseClick != null) {
125             viewPanel.getRenderingContext().mouseClick = unprocessedMouseClick;
126             return false;
127         } else
128             return handleMouseHovering(viewPanel);
129     }
130
131     private MouseClick findUnprocessedMouseClick() {
132         synchronized (detectedMouseClicks) {
133             if (detectedMouseClicks.isEmpty())
134                 return null;
135
136             return detectedMouseClicks.remove(0);
137         }
138     }
139
140     private boolean handleMouseHovering(ViewPanel viewPanel) {
141         if (currentMouseLocation != null)
142             // mouse click with button 0 amounts to mouse hovering event
143             viewPanel.getRenderingContext().mouseClick = new MouseClick(currentMouseLocation, 0);
144
145         if (mouseMoved) {
146             mouseMoved = false;
147             return true;
148         } else
149             return false;
150     }
151
152     boolean isKeyPressed(final int keyCode) {
153         return pressedKeysToPressedTimeMap.containsKey(keyCode);
154     }
155
156     @Override
157     public void keyPressed(final KeyEvent evt) {
158         synchronized (detectedKeyEvents) {
159             pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
160             detectedKeyEvents.add(evt);
161         }
162     }
163
164     @Override
165     public void keyReleased(final KeyEvent evt) {
166         synchronized (detectedKeyEvents) {
167             pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
168             detectedKeyEvents.add(evt);
169         }
170     }
171
172     @Override
173     public void keyTyped(final KeyEvent e) {
174     }
175
176     @Override
177     public void mouseClicked(final MouseEvent e) {
178         synchronized (detectedMouseClicks) {
179             detectedMouseClicks.add(new MouseClick(e.getX(), e.getY(), e.getButton()));
180         }
181     }
182
183     @Override
184     public void mouseDragged(final java.awt.event.MouseEvent evt) {
185         final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
186
187         if (oldMouseCoordinatesWhenDragging == null) {
188             oldMouseCoordinatesWhenDragging = mouseLocation;
189             return;
190         }
191
192         mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
193
194         oldMouseCoordinatesWhenDragging = mouseLocation;
195     }
196
197     @Override
198     public void mouseEntered(final MouseEvent e) {
199         mouseWithinWindow = true;
200     }
201
202     @Override
203     public synchronized void mouseExited(final MouseEvent e) {
204         mouseWithinWindow = false;
205         currentMouseLocation = null;
206     }
207
208     @Override
209     public synchronized void mouseMoved(final MouseEvent e) {
210         currentMouseLocation = new Point2D(e.getX(), e.getY());
211         mouseMoved = true;
212     }
213
214     @Override
215     public void mousePressed(final MouseEvent e) {
216     }
217
218     @Override
219     public void mouseReleased(final java.awt.event.MouseEvent evt) {
220         oldMouseCoordinatesWhenDragging = null;
221     }
222
223     @Override
224     public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
225         wheelMovedDirection += evt.getWheelRotation();
226     }
227
228     /**
229      * @return <code>true</code> if view needs to be repainted.
230      */
231     private boolean handleMouseVerticalScrolling() {
232         final Avatar avatar = viewPanel.getAvatar();
233         final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
234         avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
235         avatar.enforceSpeedLimit();
236         boolean repaintNeeded = wheelMovedDirection != 0;
237         wheelMovedDirection = 0;
238         return repaintNeeded;
239     }
240
241     /**
242      * @return <code>true</code> if view needs to be repainted.
243      */
244     private boolean handleMouseDragging() {
245         // TODO: need to detect whether user moved mouse or touch screen
246
247         final Avatar avatar = viewPanel.getAvatar();
248         // for mouse
249         avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
250         avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
251
252         // for touch screen
253         // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
254         // mouseDraggedDirection.x / 50));
255         // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
256         // mouseDraggedDirection.y / 50));
257
258         boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
259         mouseDraggedDirection.zero();
260         return viewUpdateNeeded;
261     }
262
263 }