/* * Copyright 2016 Nathan Howard * * This file is part of OpenGrave * * OpenGrave is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenGrave is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenGrave. If not, see <http://www.gnu.org/licenses/>. */ package com.opengrave.og.input; import static org.lwjgl.glfw.GLFW.glfwSetCursorPosCallback; import static org.lwjgl.glfw.GLFW.glfwSetKeyCallback; import static org.lwjgl.glfw.GLFW.glfwSetMouseButtonCallback; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import net.java.games.input.Component; import net.java.games.input.Controller; import net.java.games.input.ControllerEnvironment; import net.java.games.input.ControllerListener; import com.opengrave.common.event.EventDispatcher; import com.opengrave.common.event.EventHandler; import com.opengrave.common.event.EventHandlerPriority; import com.opengrave.common.event.EventListener; import com.opengrave.og.MainThread; import com.opengrave.og.Util; import com.opengrave.og.base.Picking; import com.opengrave.og.base.PickingResult; import com.opengrave.og.engine.Location; import com.opengrave.og.gui.UIElement; import com.opengrave.og.gui.callback.UIElementFocusEvent; import com.opengrave.og.gui.callback.UIElementMouseOverEvent; import com.opengrave.og.states.BaseState; public class InputMain implements EventListener { UIElement lastScreen = null; UIElement lastMouseOverElement = null; public static UIElement lastFocused = null; public static boolean CONTROL_WITH_MKB = true, CONTROL_WITH_GPAD = true; Location lastLoc = null; // boolean[] buttonsDown = new boolean[12]; public static ArrayList<ControlBinding> bindings = new ArrayList<ControlBinding>(); public static Controller c = null; public static ControllerLayout cl = null; public ArrayList<Float> controlLastValue = new ArrayList<Float>(); private MouseRenderableHoverEvent lastHover; private MouseHandler mouseCallback; private KeyboardRawHandler keyCallback; private MouseButtonHandler mouseButtonCallback; private static InputMain instance; public InputMain() { instance = this; ControllerListener controllerPluggedListener = new ControllerPluggedListener(); ControllerEnvironment.getDefaultEnvironment().addControllerListener(controllerPluggedListener); EventDispatcher.addHandler(this); mouseCallback = new MouseHandler(); keyCallback = new KeyboardRawHandler(); mouseButtonCallback = new MouseButtonHandler(); glfwSetCursorPosCallback(MainThread.window, mouseCallback); glfwSetKeyCallback(MainThread.window, keyCallback); glfwSetMouseButtonCallback(MainThread.window, mouseButtonCallback); setupBasicControls(); } public static int getMaxOptions() { // Assume cancel for (int i = 1; i < 10; i++) { String menu = "menu_" + i; ControlDescription cs = InputMain.getControlIcon(menu); if (cs == null) { return i - 1; } } return 9; } public static void saveCustomBindings() { HashMap<String, ArrayList<InputBinding>> savingbindings = new HashMap<String, ArrayList<InputBinding>>(); // Fill a list with every single binding synchronized (InputMain.bindings) { for (ControlBinding cb : InputMain.bindings) { for (InputBinding ib : cb.getList()) { if (!savingbindings.containsKey(cb.getControlName())) { savingbindings.put(cb.getControlName(), new ArrayList<InputBinding>()); } savingbindings.get(cb.getControlName()).add(ib); } } } HashMap<String, ArrayList<InputBinding>> defaultBindings = loadDefaultBindings(); for (String key : defaultBindings.keySet()) { if (savingbindings.containsKey(key)) { for (InputBinding ib : defaultBindings.get(key)) { if (savingbindings.get(key).contains(ib)) { ArrayList<InputBinding> list = savingbindings.get(key); System.out.println("Removing default binding from customs " + key + " " + ib.toString()); System.out.println(list.size()); while (list.remove(ib)) { // Not sure why there'd be multiple, but whatever :) } System.out.println(list.size()); if (savingbindings.get(key).size() == 0) { savingbindings.remove(key); } } else { // If we got here, it is in defaults but not in current. // Put in a "remove this binding" special key if (savingbindings.get("-" + key) == null) { savingbindings.put("-" + key, new ArrayList<InputBinding>()); } savingbindings.get("-" + key).add(ib); } } } else { for (InputBinding ib : defaultBindings.get(key)) { // Removed all bindings for this option. Not likely to happen. but just in case if (savingbindings.get("-" + key) == null) { savingbindings.put("-" + key, new ArrayList<InputBinding>()); } savingbindings.get("-" + key).add(ib); continue; } } } File f = new File(MainThread.cache, "input/custom.bind"); try (BufferedWriter out = new BufferedWriter(new FileWriter(f))) { for (String s : savingbindings.keySet()) { for (InputBinding ib : savingbindings.get(s)) { StringBuilder sb = new StringBuilder(); boolean bother = false; sb.append(s); if (ib instanceof KeyBinding) { KeyBinding kb = (KeyBinding) ib; sb.append(" key ").append(kb.getKey()); bother = true; } else if (ib instanceof PadBinding) { PadBinding pb = (PadBinding) ib; sb.append(" pad ").append(pb.getPadName()).append(" ").append(pb.getIndex()); bother = true; } else if (ib instanceof MouseBtnBinding) { MouseBtnBinding mbb = (MouseBtnBinding) ib; sb.append(" msb ").append(mbb.getButtonIndex()); bother = true; } System.out.println("Adding to Customs " + s + " " + ib.toString()); sb.append("\n"); if (bother) { out.write(sb.toString()); } } } } catch (IOException e) { e.printStackTrace(); } } public static HashMap<String, ArrayList<InputBinding>> loadDefaultBindings() { HashMap<String, ArrayList<InputBinding>> loadingbindings = new HashMap<String, ArrayList<InputBinding>>(); File dir = new File(MainThread.cache, "input"); File[] listing = dir.listFiles(); if (listing != null) { for (File file : listing) { String fn = file.getName().toLowerCase(); if (fn.startsWith("default") && fn.endsWith(".bind")) { loadBindings(file, loadingbindings); } } } return loadingbindings; } public static HashMap<String, ArrayList<InputBinding>> loadBindings(File file, HashMap<String, ArrayList<InputBinding>> list) { if (list == null) { list = new HashMap<String, ArrayList<InputBinding>>(); } try (BufferedReader in = new BufferedReader(new FileReader(file))) { String line = null; while ((line = in.readLine()) != null) { String[] split = line.split(" ", 2); if (split.length == 2) { String key = split[0]; InputBinding ib = makeBinding(split[1]); if (ib != null) { if (!list.containsKey(key)) { list.put(key, new ArrayList<InputBinding>()); } list.get(key).add(ib); } } } } catch (FileNotFoundException e) { System.err.println("No input binding file '" + file.getPath() + "'"); } catch (IOException e) { e.printStackTrace(); } return list; } private static InputBinding makeBinding(String string) { InputBinding ib = null; String[] split = string.split(" "); if (split[0].equalsIgnoreCase("key") && split.length == 2) { String key = split[1]; ib = new KeyBinding(key); } else if (split[0].equalsIgnoreCase("pad") && split.length == 3) { String pad = split[1]; Integer number = Integer.parseInt(split[2]); ib = new PadBinding(pad, number); } else if (split[0].equalsIgnoreCase("msb") && split.length == 2) { Integer number = Integer.parseInt(split[1]); ib = new MouseBtnBinding(number); } return ib; } public static void setupBasicControls() { // Load all default bindings HashMap<String, ArrayList<InputBinding>> defaults = loadDefaultBindings(); for (String s : defaults.keySet()) { ControlBinding cb = new ControlBinding(s); for (InputBinding ib : defaults.get(s)) { System.out.println(cb.getControlName() + " " + ib); cb.addInput(ib); } synchronized (bindings) { bindings.add(cb); } } // Load Custom bindings over the top. Be aware of negatives defaults = loadBindings(new File(MainThread.cache, "input/custom.bind"), null); synchronized (bindings) { for (String s : defaults.keySet()) { System.out.println(s + " " + defaults.get(s)); if (s.startsWith("-")) { s = s.substring(1); // Remove the minus for (ControlBinding cb : bindings) { if (cb.getControlName().equalsIgnoreCase(s)) { cb.removeInput(defaults.get("-" + s)); } } } else { ControlBinding cb = getControlBinding(s); cb.addInput(defaults.get(s)); } } } } public void doEet(BaseState state, float delta) { Util.checkErr(); if (state.screen != lastScreen) { lastScreen = MainThread.getGameState().screen; lastMouseOverElement = null; lastFocused = null; } if (lastFocused != null && state.screen != null) { } Picking.pickRender(state); if (CONTROL_WITH_GPAD) { Controller[] list = ControllerEnvironment.getDefaultEnvironment().getControllers(); for (int coun = list.length - 1; coun > -1; coun--) { Controller c = list[coun]; if (!c.poll()) { c = null; cl = null; CONTROL_WITH_GPAD = false; CONTROL_WITH_MKB = true; // Currently linux does not ever get an updated list of // controllers or connection/disconnection messages. // Until one or the other works we just assume to revert // back to MKB break; } if (InputMain.c == null || c == InputMain.c) { Component[] comp = c.getComponents(); for (int count = 0; count < comp.length; count++) { JoystickRawAxisEvent event = new JoystickRawAxisEvent(c, count, comp[count].getPollData(), delta, comp[count].getIdentifier()); EventDispatcher.dispatchEvent(event); } } } PickingResult pr = Picking.pick(MainThread.lastW / 2, MainThread.lastH / 2); if (pr.picked instanceof UIElement) { // No idea what should be done here. // Realistically we should have one RenderView marked as "primary" and do 1/2 W & H of that, but it's so much extra work! } else { if (pr.worldLoc != null) { EventDispatcher.dispatchEvent(new MouseRenderableHoverEvent(pr.picked, pr.worldLoc)); } } } Util.checkErr(); if (CONTROL_WITH_MKB) { Util.checkErr(); /* * while (Keyboard.next()) { * Util.checkErr(); * Integer keycode = Keyboard.getEventKey(); * Character k = Keyboard.getEventCharacter(); * boolean s = Keyboard.getEventKeyState(); * Util.checkErr(); * System.out.println(Keyboard.getKeyName(keycode) + " " + s); * KeyboardRawPressEvent event = new KeyboardRawPressEvent(lastFocused, Keyboard.getKeyName(keycode), s, keycode, k); * EventDispatcher.dispatchEvent(event); * } */ /* * while (Mouse.next()) { * int x = Mouse.getEventX(), y = Mouse.getEventY(); * int button = Mouse.getEventButton(); * boolean s = Mouse.getEventButtonState(); * int wheel = Mouse.getEventDWheel(); * pr = Picking.pick(x, y); * if (pr.picked instanceof UIElement) { * UIElement ele = (UIElement) pr.picked; * if (button == 0) { * UIElementFocusEvent eventFocus = new UIElementFocusEvent(ele); * EventDispatcher.dispatchEvent(eventFocus); * } * } else { * * } * if (button >= 0) { * System.out.println(button + ":" + s); * if (s) { * if (!buttonsDown[button]) { * buttonsDown[button] = true; * } * } else { * if (pr.picked != null) { * MouseButtonRenderableEvent event; * if (pr.picked instanceof UIElement) { * int rx = x - ((UIElement) pr.picked).getParentX(); * int ry = (HGMainThread.lastH - y) - ((UIElement) pr.picked).getParentY(); * event = new MouseButtonRenderableEvent(pr.picked, pr.worldLoc, button, rx, ry, x, HGMainThread.lastH - y); * } else { * int rx = x - pr.picked.getContext().totalx; * int ry = (HGMainThread.lastH - y) - pr.picked.getContext().totaly; * event = new MouseButtonRenderableEvent(pr.picked, pr.worldLoc, button, rx, ry, x, HGMainThread.lastH - y); * } * EventDispatcher.dispatchEvent(event); * } * * if (buttonsDown[button]) { * buttonsDown[button] = false; * } * } * } * if (wheel != 0) { * if (pr.picked != null) { * MouseWheelRenderableEvent event = new MouseWheelRenderableEvent(pr.picked, pr.worldLoc, wheel); * EventDispatcher.dispatchEvent(event); * } * } * if (pr.worldLoc == null) { * lastLoc = null; * } else { * lastLoc = new Location(pr.worldLoc); * } * * } */ PickingResult pr = Picking.pick((int) mouseCallback.x, (int) mouseCallback.y); keyCallback.update(delta); mouseButtonCallback.update(delta); Util.checkErr(); // We have a resting position. Now to check out our focus/mouse over if (pr.picked instanceof UIElement) { UIElement ele = (UIElement) pr.picked; if (ele.isFocusable() && !ele.equals(lastMouseOverElement)) { if (lastMouseOverElement != null) { EventDispatcher.dispatchEvent(new UIElementMouseOverEvent(lastMouseOverElement, false)); } EventDispatcher.dispatchEvent(new UIElementMouseOverEvent(ele, true)); lastMouseOverElement = ele; } } else { if (!CONTROL_WITH_GPAD) { if (pr.worldLoc != null) { for (int i = 0; i < 12; i++) { // if (buttonsDown[i]) { // EventDispatcher.dispatchEvent(new MouseRenderableDragEvent(pr.picked, pr.worldLoc, i)); // } } EventDispatcher.dispatchEvent(new MouseRenderableHoverEvent(pr.picked, pr.worldLoc)); } } } Util.checkErr(); } Picking.pickCleanup(); Util.checkErr(); } @EventHandler(priority = EventHandlerPriority.EARLY) public void onFocus(MouseRenderableHoverEvent event) { // Cache for later usage lastHover = event; } public MouseRenderableHoverEvent getLastHovered() { return lastHover; } @EventHandler(priority = EventHandlerPriority.LATE) public void onMouseButtonRenderable(MouseButtonRenderableEvent event) { if (!event.isConsumed()) { // Check to see if it's a disembodied click. MouseButtonEvent event2 = new MouseButtonEvent(event.getButton()); EventDispatcher.dispatchEvent(event2); } } @EventHandler(priority = EventHandlerPriority.LATE) public void onMouseWheelRenderable(MouseWheelRenderableEvent event) { if (!event.isConsumed()) { MouseWheelEvent event2 = new MouseWheelEvent(event.getDelta()); EventDispatcher.dispatchEvent(event2); } } @EventHandler(priority = EventHandlerPriority.LATE) public void onUIElementFocus(UIElementFocusEvent event) { lastFocused = event.getElement(); } /* * Input Section for keeping track of key/pad bindings */ public static ControlBinding getControlBinding(String string) { synchronized (bindings) { for (ControlBinding cb : bindings) { if (cb.getControlName().equals(string)) { return cb; } } ControlBinding cb = new ControlBinding(string); bindings.add(cb); return cb; } } public static ControlBinding getControlBindingFor(InputBinding ib) { synchronized (bindings) { for (ControlBinding cb : bindings) { if (cb.hasInput(ib)) { return cb; } } } return null; } /* * Now the Input section which turns raw events into InputCapturedEvent(s) */ /** * Key state change -> InputChange via keybindings * * @param event */ @EventHandler(priority = EventHandlerPriority.LATE) public void onKeyPress(KeyboardRawPressEvent event) { KeyBinding kb = new KeyBinding(event.getKey()); ControlBinding cb = getControlBindingFor(kb); if (cb != null) { EventDispatcher.dispatchEvent(new InputChangeEvent(cb, kb, event.getState(), event.getState() ? 1f : 0f)); } } /** * Held keys -> InputHeld via keybindings * * @param event */ @EventHandler(priority = EventHandlerPriority.LATE) public void onKeyHold(KeyboardRawHeldEvent event) { if (event.isConsumed()) { return; } KeyBinding kb = new KeyBinding(event.getKey()); ControlBinding cb = getControlBindingFor(kb); if (cb != null) { EventDispatcher.dispatchEvent(new InputHeldEvent(cb, kb, event.getDelta(), event.getMagnitude())); } } /** * Pad state & held -> InputChange and InputHeld via keybindings * * @param event */ @EventHandler(priority = EventHandlerPriority.LATE) public void onKeyHold(JoystickRawAxisEvent event) { if (event.isConsumed()) { return; } if (cl == null) { return; } // Ensure space in list of last values while (event.getAxisIndex() >= controlLastValue.size()) { controlLastValue.add(0f); } // Check if it's a trigger. Differs from otgher axis ControllerAxisTrigger trig = cl.getTrigger(event.getAxisIndex()); // First send of raw press/release events if (!isPressed(controlLastValue.get(event.getAxisIndex()), trig != null) && isPressed(event.getValue(), trig != null)) { // Gone from off to on EventDispatcher.dispatchEvent(new JoystickRawChangeEvent(cl.getName(), event.getAxisIndex(), true, event.getValue())); } else if (isPressed(controlLastValue.get(event.getAxisIndex()), trig != null) && !isPressed(event.getValue(), trig != null)) { // Gone from on to off EventDispatcher.dispatchEvent(new JoystickRawChangeEvent(cl.getName(), event.getAxisIndex(), false, event.getValue())); } controlLastValue.set(event.getAxisIndex(), event.getValue()); // Now try to bind to control PadBinding pb = new PadBinding(cl.getName(), event.getAxisIndex()); ControlBinding cb = getControlBindingFor(pb); if (cb == null) { return; } // Send events for push/release. Technically allows axis too, but may be // nonsensical. event.setConsumed(); ControllerAxisPairs hat = cl.getPair(event.getAxisIndex()); if (hat != null) { EventDispatcher.dispatchEvent(new InputHeldEvent(cb, pb, event.getDelta(), hat.getValueWithDeadZone(event.getPad(), event.getAxisIndex(), 0.2f))); } else { EventDispatcher.dispatchEvent(new InputHeldEvent(cb, pb, event.getDelta(), event.getValue())); } } @EventHandler(priority = EventHandlerPriority.LATE) public void onJoyStickPress(JoystickRawChangeEvent event) { if (event.isConsumed() || cl == null) { return; } PadBinding pb = new PadBinding(cl.getName(), event.getAxis()); ControlBinding cb = getControlBindingFor(pb); if (cb == null) { return; } event.setConsumed(); EventDispatcher.dispatchEvent(new InputChangeEvent(cb, pb, event.getState(), event.getValue())); } public static boolean isPressed(float value, boolean isAxis) { if ((value <= -0.6f && !isAxis) || value >= 0.6f) { return true; } return false; } public static ControlDescription getControlIcon(String string) { ControlBinding cb = InputMain.getControlBinding(string); if (cb != null) { if (CONTROL_WITH_GPAD && cl != null) { PadBinding pb = cb.getInputController(cl.getName()); if (pb != null) { return cl.getComponent(pb.getIndex()); } } else if (CONTROL_WITH_MKB) { KeyBinding kb = cb.getInputKeyboard(); return new KeyDescription(kb.getKey()); } } return null; } public static ControlDescription getControlIcon(InputBinding ib) { if (ib instanceof PadBinding && CONTROL_WITH_GPAD && cl != null) { PadBinding pb = (PadBinding) ib; if (pb.getPadName().equalsIgnoreCase(cl.getName())) { // The one requested is the selected controller layout. Woop return cl.getComponent(pb.getIndex()); } } else if (ib instanceof KeyBinding && CONTROL_WITH_MKB) { return new KeyDescription(((KeyBinding) ib).getKey()); } return null; } public static MouseHandler getMouse() { return instance.mouseCallback; } }