Code refactoring.
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / gui / humaninput / HIDInputTracker.java
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/HIDInputTracker.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/HIDInputTracker.java
new file mode 100755 (executable)
index 0000000..6015ee7
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 3 of the GNU Lesser General Public License
+ * or later as published by the Free Software Foundation.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.gui.humaninput;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.gui.Avatar;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.ViewRenderListener;
+
+import javax.swing.*;
+import java.awt.event.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HIDInputTracker implements
+        MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
+
+    /**
+     * <pre>
+     * Key is keyboard key code.
+     * Value is system milliseconds when key was pressed.
+     *
+     * So by reading the map one can determine currently pressed keys as well as duration.
+     * </pre>
+     */
+    private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
+    private final List<MouseClick> detectedMouseClicks = new ArrayList<>();
+    private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
+    private int wheelMovedDirection = 0;
+    private Point2D mouseDraggedDirection = new Point2D();
+    private Point2D oldMouseCoordinatesWhenDragging;
+    private ViewPanel viewPanel;
+    private Point2D currentMouseLocation;
+    private boolean mouseMoved;
+    private boolean mouseWithinWindow = false;
+
+    public HIDInputTracker(final ViewPanel viewPanel) {
+        this.viewPanel = viewPanel;
+        bind(viewPanel);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean beforeRender(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
+        boolean viewUpdateNeeded = handleKeyboardEvents();
+        viewUpdateNeeded |= handleMouseClicksAndHover(viewPanel);
+        viewUpdateNeeded |= handleMouseDragging();
+        viewUpdateNeeded |= handleMouseVerticalScrolling();
+        return viewUpdateNeeded;
+    }
+
+    private void bind(final JPanel panel) {
+        panel.addMouseMotionListener(this);
+
+        panel.addKeyListener(this);
+
+        panel.addMouseListener(this);
+
+        panel.addMouseWheelListener(this);
+    }
+
+    /**
+     * @return <code>true</code> if view needs to be repainted.
+     */
+    private boolean handleKeyboardEvents() {
+        final UserInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
+        ArrayList<KeyEvent> unprocessedKeyboardEvents = getUnprocessedKeyboardEvents();
+
+        return currentFocusOwner != null
+                && forwardKeyboardEventsToFocusOwner(currentFocusOwner, unprocessedKeyboardEvents);
+    }
+
+    private ArrayList<KeyEvent> getUnprocessedKeyboardEvents() {
+        synchronized (detectedKeyEvents) {
+            ArrayList<KeyEvent> result = new ArrayList<>(detectedKeyEvents);
+            detectedKeyEvents.clear();
+            return result;
+        }
+    }
+
+    /**
+     * @return <code>true</code> if view update is needed.
+     */
+    private boolean forwardKeyboardEventsToFocusOwner(
+            UserInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
+        boolean viewUpdateNeeded = false;
+
+        for (KeyEvent keyEvent : keyEvents)
+            viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
+
+        return viewUpdateNeeded;
+    }
+
+    private boolean processKeyEvent(UserInputHandler currentFocusOwner, KeyEvent keyEvent) {
+        switch (keyEvent.getID()) {
+            case KeyEvent.KEY_PRESSED:
+                return currentFocusOwner.keyPressed(keyEvent, viewPanel);
+
+            case KeyEvent.KEY_RELEASED:
+                return currentFocusOwner.keyReleased(keyEvent, viewPanel);
+        }
+        return false;
+    }
+
+    /**
+     * @return <code>true</code> if view needs to be repainted.
+     */
+    private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
+        MouseClick unprocessedMouseClick = findUnprocessedMouseClick();
+
+        if (unprocessedMouseClick != null) {
+            viewPanel.getRenderingContext().mouseClick = unprocessedMouseClick;
+            return false;
+        } else
+            return handleMouseHovering(viewPanel);
+    }
+
+    private MouseClick findUnprocessedMouseClick() {
+        synchronized (detectedMouseClicks) {
+            if (detectedMouseClicks.isEmpty())
+                return null;
+
+            return detectedMouseClicks.remove(0);
+        }
+    }
+
+    private boolean handleMouseHovering(ViewPanel viewPanel) {
+        if (currentMouseLocation != null)
+            // mouse click with button 0 amounts to mouse hovering event
+            viewPanel.getRenderingContext().mouseClick = new MouseClick(currentMouseLocation, 0);
+
+        if (mouseMoved) {
+            mouseMoved = false;
+            return true;
+        } else
+            return false;
+    }
+
+    boolean isKeyPressed(final int keyCode) {
+        return pressedKeysToPressedTimeMap.containsKey(keyCode);
+    }
+
+    @Override
+    public void keyPressed(final KeyEvent evt) {
+        synchronized (detectedKeyEvents) {
+            pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
+            detectedKeyEvents.add(evt);
+        }
+    }
+
+    @Override
+    public void keyReleased(final KeyEvent evt) {
+        synchronized (detectedKeyEvents) {
+            pressedKeysToPressedTimeMap.remove(evt.getKeyCode());
+            detectedKeyEvents.add(evt);
+        }
+    }
+
+    @Override
+    public void keyTyped(final KeyEvent e) {
+    }
+
+    @Override
+    public void mouseClicked(final MouseEvent e) {
+        synchronized (detectedMouseClicks) {
+            detectedMouseClicks.add(new MouseClick(e.getX(), e.getY(), e.getButton()));
+        }
+    }
+
+    @Override
+    public void mouseDragged(final java.awt.event.MouseEvent evt) {
+        final Point2D mouseLocation = new Point2D(evt.getX(), evt.getY());
+
+        if (oldMouseCoordinatesWhenDragging == null) {
+            oldMouseCoordinatesWhenDragging = mouseLocation;
+            return;
+        }
+
+        mouseDraggedDirection.add(mouseLocation.clone().subtract(oldMouseCoordinatesWhenDragging));
+
+        oldMouseCoordinatesWhenDragging = mouseLocation;
+    }
+
+    @Override
+    public void mouseEntered(final MouseEvent e) {
+        mouseWithinWindow = true;
+    }
+
+    @Override
+    public synchronized void mouseExited(final MouseEvent e) {
+        mouseWithinWindow = false;
+        currentMouseLocation = null;
+    }
+
+    @Override
+    public synchronized void mouseMoved(final MouseEvent e) {
+        currentMouseLocation = new Point2D(e.getX(), e.getY());
+        mouseMoved = true;
+    }
+
+    @Override
+    public void mousePressed(final MouseEvent e) {
+    }
+
+    @Override
+    public void mouseReleased(final java.awt.event.MouseEvent evt) {
+        oldMouseCoordinatesWhenDragging = null;
+    }
+
+    @Override
+    public void mouseWheelMoved(final java.awt.event.MouseWheelEvent evt) {
+        wheelMovedDirection += evt.getWheelRotation();
+    }
+
+    /**
+     * @return <code>true</code> if view needs to be repainted.
+     */
+    private boolean handleMouseVerticalScrolling() {
+        final Avatar avatar = viewPanel.getAvatar();
+        final double actualAcceleration = 50 * avatar.avatarAcceleration * (1 + (avatar.getMovementSpeed() / 10));
+        avatar.getMovementVector().y += (wheelMovedDirection * actualAcceleration);
+        avatar.enforceSpeedLimit();
+        boolean repaintNeeded = wheelMovedDirection != 0;
+        wheelMovedDirection = 0;
+        return repaintNeeded;
+    }
+
+    /**
+     * @return <code>true</code> if view needs to be repainted.
+     */
+    private boolean handleMouseDragging() {
+        // TODO: need to detect whether user moved mouse or touch screen
+
+        final Avatar avatar = viewPanel.getAvatar();
+        // for mouse
+        avatar.setAngleXZ(avatar.getAngleXZ() - ((float) mouseDraggedDirection.x / 50));
+        avatar.setAngleYZ(avatar.getAngleYZ() - ((float) mouseDraggedDirection.y / 50));
+
+        // for touch screen
+        // avatar.setAngleXZ(avatar.getAngleXZ() + ((float)
+        // mouseDraggedDirection.x / 50));
+        // avatar.setAngleYZ(avatar.getAngleYZ() + ((float)
+        // mouseDraggedDirection.y / 50));
+
+        boolean viewUpdateNeeded = !mouseDraggedDirection.isZero();
+        mouseDraggedDirection.zero();
+        return viewUpdateNeeded;
+    }
+
+}