-/*
- * Sixth 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-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;
-
-/**
- * This class is responsible for tracking human input devices (keyboard, mouse, etc.) and
- * forwarding those inputs to subsequent virtual components.
- */
-public class HIDInputTracker implements
- MouseMotionListener, KeyListener, MouseListener, MouseWheelListener, ViewRenderListener {
-
- /**
- * <p> Map of pressed keys. </p>
- * <p> Key is mouse button code. </p>
- * <p> Value is system milliseconds when button was pressed. </p>
- * <p> So by reading the map one can determine currently pressed buttons as well as duration. </p>
- */
- private final Map<Integer, Long> pressedKeysToPressedTimeMap = new HashMap<>();
- private final List<MouseEvent> detectedMouseEvents = new ArrayList<>();
- private final List<KeyEvent> detectedKeyEvents = new ArrayList<>();
- private final Point2D mouseDraggedDirection = new Point2D();
- private final ViewPanel viewPanel;
- private int wheelMovedDirection = 0;
- private Point2D oldMouseCoordinatesWhenDragging;
- 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 KeyboardInputHandler 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(
- KeyboardInputHandler currentFocusOwner, ArrayList<KeyEvent> keyEvents) {
- boolean viewUpdateNeeded = false;
-
- for (KeyEvent keyEvent : keyEvents)
- viewUpdateNeeded |= processKeyEvent(currentFocusOwner, keyEvent);
-
- return viewUpdateNeeded;
- }
-
- private boolean processKeyEvent(KeyboardInputHandler 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) {
- boolean rerenderNeeded = false;
- MouseEvent event = findClickLocationToTrace();
- if (event != null) {
- // process mouse clicks as a first priority
- rerenderNeeded = true;
- } else {
- // when there are no mouse clicks, process mouse hovering
-
- if (mouseMoved) {
- mouseMoved = false;
- // we would like to re-render frame when user moved mouse, to see what objects mouse is hovering over
- rerenderNeeded = true;
- }
-
- if (currentMouseLocation != null) {
- // mouse click with button 0 amounts to mouse hovering event
- event = new MouseEvent(currentMouseLocation, 0);
- }
- }
-
- if (viewPanel.getRenderingContext() != null)
- viewPanel.getRenderingContext().setMouseEvent(event);
-
- return rerenderNeeded;
- }
-
- private MouseEvent findClickLocationToTrace() {
- synchronized (detectedMouseEvents) {
- if (detectedMouseEvents.isEmpty())
- return null;
-
- return detectedMouseEvents.remove(0);
- }
- }
-
- 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 java.awt.event.MouseEvent e) {
- synchronized (detectedMouseEvents) {
- detectedMouseEvents.add(new MouseEvent(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 java.awt.event.MouseEvent e) {
- mouseWithinWindow = true;
- }
-
- @Override
- public synchronized void mouseExited(final java.awt.event.MouseEvent e) {
- mouseWithinWindow = false;
- currentMouseLocation = null;
- }
-
- @Override
- public synchronized void mouseMoved(final java.awt.event.MouseEvent e) {
- currentMouseLocation = new Point2D(e.getX(), e.getY());
- mouseMoved = true;
- }
-
- @Override
- public void mousePressed(final java.awt.event.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: It would be nice here to detect somehow whether user moved mouse or touch screen.
- // in case of touch screen, we would like to reverse movement along X and Y axis.
-
- 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;
- }
-
-}