/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.ui;
import com.grapeshot.halfnes.PrefsSingleton;
import static com.grapeshot.halfnes.utils.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.prefs.Preferences;
import javafx.scene.Scene;
import net.java.games.input.Component;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import net.java.games.input.Event;
import net.java.games.input.EventQueue;
/**
*
* @author Andrew, Zlika This class uses the JInput Java game controller API
* (cf. http://java.net/projects/jinput).
*/
public class ControllerImpl implements ControllerInterface, KeyListener {
//private final java.awt.Component parent;
private Controller gameController;
private Component[] buttons;
private final ScheduledExecutorService thread = Executors.newSingleThreadScheduledExecutor();
private int latchbyte = 0, controllerbyte = 0, prevbyte = 0, outbyte = 0, gamepadbyte = 0;
private final HashMap<Integer, Integer> m = new HashMap<>(10);
private final int controllernum;
public ControllerImpl(final java.awt.Component parent, final int controllernum) {
this(controllernum);
//this.parent = parent;
parent.addKeyListener(this);
}
public ControllerImpl(final Scene scene, final int controllernum) {
this(controllernum);
scene.addEventHandler(javafx.scene.input.KeyEvent.KEY_PRESSED, e -> pressKey(e.getCode().impl_getCode()));
scene.addEventHandler(javafx.scene.input.KeyEvent.KEY_RELEASED, e -> releaseKey(e.getCode().impl_getCode()));
}
public ControllerImpl(final int controllernum) {
if ((controllernum != 0) && (controllernum != 1)) {
throw new IllegalArgumentException("controllerNum must be 0 or 1");
}
this.controllernum = controllernum;
setButtons();
}
@Override
public void keyPressed(final KeyEvent keyEvent) {
pressKey(keyEvent.getKeyCode());
}
private void pressKey(int keyCode) {
//enable the byte of whatever is found
prevbyte = controllerbyte;
if (!m.containsKey(keyCode)) {
return;
}
//enable the corresponding bit to the key
controllerbyte |= m.get(keyCode);
//special case: if up and down are pressed at once, use whichever was pressed previously
if ((controllerbyte & (BIT4 | BIT5)) == (BIT4 | BIT5)) {
controllerbyte &= ~(BIT4 | BIT5);
controllerbyte |= (prevbyte & ~(BIT4 | BIT5));
}
//same for left and right
if ((controllerbyte & (BIT6 | BIT7)) == (BIT6 | BIT7)) {
controllerbyte &= ~(BIT6 | BIT7);
controllerbyte |= (prevbyte & ~(BIT6 | BIT7));
}
}
@Override
public void keyReleased(final KeyEvent keyEvent) {
releaseKey(keyEvent.getKeyCode());
}
private void releaseKey(int keyCode) {
prevbyte = controllerbyte;
if (!m.containsKey(keyCode)) {
return;
}
controllerbyte &= ~m.get(keyCode);
}
@Override
public int getbyte() {
return outbyte;
}
@Override
public int peekOutput() {
return latchbyte;
}
@Override
public void keyTyped(final KeyEvent arg0) {
// TODO Auto-generated method stub
}
public void strobe() {
//shifts a byte out
outbyte = latchbyte & 1;
latchbyte = ((latchbyte >> 1) | 0x100);
}
public void output(final boolean state) {
latchbyte = gamepadbyte | controllerbyte;
}
/**
* Start in a separate thread the processing of the controller event queue.
* Must be called after construction of the class to enable the processing
* of the joystick / gamepad events.
*/
public void startEventQueue() {
// if (System.getProperty("java.class.path").contains("jinput")) {
thread.execute(eventQueueLoop());
// }
}
double threshold = 0.25;
private Runnable eventQueueLoop() {
return new Runnable() {
@Override
public void run() {
if (gameController != null) {
Event event = new Event();
while (!Thread.interrupted()) {
gameController.poll();
EventQueue queue = gameController.getEventQueue();
while (queue.getNextEvent(event)) {
Component component = event.getComponent();
if (component.getIdentifier() == Component.Identifier.Axis.X) {
if (event.getValue() > threshold) {
gamepadbyte |= BIT7;//left on, right off
gamepadbyte &= ~BIT6;
} else if (event.getValue() < -threshold) {
gamepadbyte |= BIT6;
gamepadbyte &= ~BIT7;
} else {
gamepadbyte &= ~(BIT7 | BIT6);
}
} else if (component.getIdentifier() == Component.Identifier.Axis.Y) {
if (event.getValue() > threshold) {
gamepadbyte |= BIT5;//up on, down off
gamepadbyte &= ~BIT4;
} else if (event.getValue() < -threshold) {
gamepadbyte |= BIT4;//down on, up off
gamepadbyte &= ~BIT5;
} else {
gamepadbyte &= ~(BIT4 | BIT5);
}
} else if (component == buttons[0]) {
if (isPressed(event)) {
gamepadbyte |= BIT0;
} else {
gamepadbyte &= ~BIT0;
}
} else if (component == buttons[1]) {
if (isPressed(event)) {
gamepadbyte |= BIT1;
} else {
gamepadbyte &= ~BIT1;
}
} else if (component == buttons[2]) {
if (isPressed(event)) {
gamepadbyte |= BIT2;
} else {
gamepadbyte &= ~BIT2;
}
} else if (component == buttons[3]) {
if (isPressed(event)) {
gamepadbyte |= BIT3;
} else {
gamepadbyte &= ~BIT3;
}
}
}
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
}
}
};
}
private boolean isPressed(Event event) {
Component component = event.getComponent();
if (component.isAnalog()) {
if (Math.abs(event.getValue()) > 0.2f) {
return true;
} else {
return false;
}
} else if (event.getValue() == 0) {
return false;
} else {
return true;
}
}
/**
* Stop the controller event queue thread. Must be called before closing the
* application.
*/
public void stopEventQueue() {
thread.shutdownNow();
}
/**
* This method detects the available joysticks / gamepads on the computer
* and return them in a list.
*
* @return List of available joysticks / gamepads connected to the computer
*/
private static Controller[] getAvailablePadControllers() {
List<Controller> gameControllers = new ArrayList<>();
// Get a list of the controllers JInput knows about and can interact
// with
Controller[] controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
// Check the useable controllers (gamepads or joysticks with at least 2
// axis and 2 buttons)
for (Controller controller : controllers) {
if ((controller.getType() == Controller.Type.GAMEPAD) || (controller.getType() == Controller.Type.STICK)) {
int nbOfAxis = 0;
// Get this controllers components (buttons and axis)
Component[] components = controller.getComponents();
// Check the availability of X/Y axis and at least 2 buttons
// (for A and B, because select and start can use the keyboard)
for (Component component : components) {
if ((component.getIdentifier() == Component.Identifier.Axis.X)
|| (component.getIdentifier() == Component.Identifier.Axis.Y)) {
nbOfAxis++;
}
}
if ((nbOfAxis >= 2) && (getButtons(controller).length >= 2)) {
// Valid game controller
gameControllers.add(controller);
}
}
}
return gameControllers.toArray(new Controller[0]);
}
/**
* Return the available buttons on this controller (by priority order).
*/
private static Component[] getButtons(Controller controller) {
List<Component> buttons = new ArrayList<>();
// Get this controllers components (buttons and axis)
Component[] components = controller.getComponents();
for (Component component : components) {
if (component.getIdentifier() instanceof Component.Identifier.Button) {
buttons.add(component);
}
}
return buttons.toArray(new Component[0]);
}
public final void setButtons() {
Preferences prefs = PrefsSingleton.get();
//reset the buttons from prefs
m.clear();
switch (controllernum) {
case 0:
m.put(prefs.getInt("keyUp1", KeyEvent.VK_UP), BIT4);
m.put(prefs.getInt("keyDown1", KeyEvent.VK_DOWN), BIT5);
m.put(prefs.getInt("keyLeft1", KeyEvent.VK_LEFT), BIT6);
m.put(prefs.getInt("keyRight1", KeyEvent.VK_RIGHT), BIT7);
m.put(prefs.getInt("keyA1", KeyEvent.VK_X), BIT0);
m.put(prefs.getInt("keyB1", KeyEvent.VK_Z), BIT1);
m.put(prefs.getInt("keySelect1", KeyEvent.VK_SHIFT), BIT2);
m.put(prefs.getInt("keyStart1", KeyEvent.VK_ENTER), BIT3);
break;
case 1:
default:
m.put(prefs.getInt("keyUp2", KeyEvent.VK_W), BIT4);
m.put(prefs.getInt("keyDown2", KeyEvent.VK_S), BIT5);
m.put(prefs.getInt("keyLeft2", KeyEvent.VK_A), BIT6);
m.put(prefs.getInt("keyRight2", KeyEvent.VK_D), BIT7);
m.put(prefs.getInt("keyA2", KeyEvent.VK_G), BIT0);
m.put(prefs.getInt("keyB2", KeyEvent.VK_F), BIT1);
m.put(prefs.getInt("keySelect2", KeyEvent.VK_R), BIT2);
m.put(prefs.getInt("keyStart2", KeyEvent.VK_T), BIT3);
break;
}
Controller[] controllers = getAvailablePadControllers();
if (controllers.length > controllernum) {
this.gameController = controllers[controllernum];
PrefsSingleton.get().put("controller" + controllernum, gameController.getName());
System.err.println(controllernum + 1 + ". " + gameController.getName());
this.buttons = getButtons(controllers[controllernum]);
} else {
PrefsSingleton.get().put("controller" + controllernum, "");
this.gameController = null;
this.buttons = null;
}
}
}