/* * This file is part of the Illarion project. * * Copyright © 2015 - Illarion e.V. * * Illarion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Illarion 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. */ package illarion.client.input; import illarion.client.IllaClient; import illarion.client.world.World; import illarion.common.gui.AbstractMultiActionHelper; import org.bushe.swing.event.EventBus; import org.illarion.engine.input.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.util.EnumSet; import java.util.Set; /** * This class is used to receive and forward all user input. * * @author Martin Karing <nitram@illarion.org> */ public final class InputReceiver implements InputListener { @Nonnull private static final Logger log = LoggerFactory.getLogger(InputReceiver.class); private static final int MOVE_KEY = 1; /** * This is the multi click helper that is used to detect single and multiple clicks on the game map. * * @author Martin Karing <nitram@illarion.org> */ private static final class ButtonMultiClickHelper extends AbstractMultiActionHelper { /** * The x coordinate of the last reported click. */ private int x; /** * The Y coordinate of the last reported click. */ private int y; /** * The key that is used. */ private Button key; /** * The constructor that sets the used timeout interval to the system default double click interval. */ private ButtonMultiClickHelper() { super(IllaClient.getCfg().getInteger("doubleClickInterval"), 2); } /** * Update the data that is needed to report the state of the last click properly. * * @param mouseKey the key that was clicked * @param posX the x coordinate where the click happened * @param posY the y coordinate where the click happened */ public void setInputData(@Nonnull Button mouseKey, int posX, int posY) { x = posX; y = posY; key = mouseKey; } @Override public void executeAction(int count) { switch (count) { case 1: if (log.isDebugEnabled()) { log.debug("Raising single click event for {} button at {} {}", key.name(), x, y); } EventBus.publish(new ClickOnMapEvent(key, x, y)); break; case 2: if (log.isDebugEnabled()) { log.debug("Raising double click event for {} button at {} {}", key.name(), x, y); } World.getMapDisplay().getGameScene().publishEvent(new DoubleClickOnMapEvent(key, x, y)); break; } } } /** * This class is used as helper for the point at events. */ private static final class PointAtHelper extends AbstractMultiActionHelper { /** * The x coordinate of the last reported click. */ private int x; /** * The Y coordinate of the last reported click. */ private int y; /** * Default constructor. */ private PointAtHelper() { super(50); } /** * Update the data that is needed to report the state of the last move properly. * * @param posX the x coordinate where the click happened * @param posY the y coordinate where the click happened */ public void setInputData(int posX, int posY) { x = posX; y = posY; } @Override public void executeAction(int count) { EventBus.publish(new PointOnMapEvent(x, y)); } } /** * The topic that is in general used to publish input events. */ @Nonnull public static final String EB_TOPIC = "InputEvent"; /** * The key mapper stores the keep-action assignments of the client. */ @Nonnull private final KeyMapper keyMapper; /** * The instance of the button multi-click helper that is used in this instance of the input receiver. */ @Nonnull private final ButtonMultiClickHelper buttonMultiClickHelper = new ButtonMultiClickHelper(); /** * The instance of the point at helper used by this instance of the input receiver. */ @Nonnull private final PointAtHelper pointAtHelper = new PointAtHelper(); /** * The input engine. */ @Nonnull private final Input input; /** * In case this value is set {@code true} this receiver is active and working. If not will discard all events * received. */ private boolean enabled; @Nonnull private final Set<Button> buttonDownReceived; @Nonnull private final Set<Button> buttonDownDragged; /** * Create a new instance of the input receiver. * * @param input the input system this class is receiving its data from */ public InputReceiver(@Nonnull Input input) { this.input = input; buttonDownReceived = EnumSet.noneOf(Button.class); buttonDownDragged = EnumSet.noneOf(Button.class); enabled = false; keyMapper = new KeyMapper(input); } /** * Set the enabled flag of this input receiver. * * @param value the new enabled flag */ public void setEnabled(boolean value) { enabled = value; } @Override public void keyDown(@Nonnull Key key) { if (enabled) { keyMapper.handleKeyPressedInput(key); } } @Override public void keyUp(@Nonnull Key key) { if (enabled) { keyMapper.handleKeyReleasedInput(key); } } @Override public void keyTyped(char character) { // nothing } @Override public void buttonDown(int mouseX, int mouseY, @Nonnull Button button) { if (enabled) { buttonDownDragged.remove(button); buttonDownReceived.add(button); log.debug("Received {} mouse button down at {}, {}", button, mouseX, mouseY); } } @Override public void buttonUp(int mouseX, int mouseY, @Nonnull Button button) { if (enabled) { if (buttonDownReceived.remove(button)) { if (!buttonDownDragged.contains(button)) { buttonMultiClickHelper.setInputData(button, mouseX, mouseY); //buttonMultiClickHelper.pulse(); } log.debug("Received {} mouse button up at {}, {}", button, mouseX, mouseY); World.getPlayer().getMovementHandler().getTargetMouseMovementHandler().disengage(true); World.getPlayer().getMovementHandler().getFollowMouseHandler().disengage(true); input.disableForwarding(ForwardingTarget.Mouse); } else { log.debug("Received {} mouse button up at {}, {} but skipped it.", button, mouseX, mouseY); } } } @Override public void buttonClicked(int mouseX, int mouseY, @Nonnull Button button, int count) { if (enabled) { if (!buttonDownReceived.contains(button) || buttonDownDragged.contains(button)) { log.debug("Received {} mouse clicked {} times at {}, {} but skipped it.", button, count, mouseX, mouseY); } else { log.debug("Received {} mouse clicked {} times at {}, {}", button, count, mouseX, mouseY); switch (count) { case 1: World.getMapDisplay().getGameScene().publishEvent(new ClickOnMapEvent(button, mouseX, mouseY)); break; case 2: World.getMapDisplay().getGameScene() .publishEvent(new DoubleClickOnMapEvent(button, mouseX, mouseY)); break; default: log.warn("Too many {} mouse clicks received: {}", button, count); } } } } @Override public void mouseMoved(int mouseX, int mouseY) { if (enabled) { pointAtHelper.setInputData(mouseX, mouseY); pointAtHelper.pulse(); EventBus.publish(new MoveOnMapEvent(mouseX, mouseY)); } } @Override public void mouseDragged(@Nonnull Button button, int fromX, int fromY, int toX, int toY) { if (enabled) { buttonMultiClickHelper.reset(); if (buttonDownReceived.contains(button)) { boolean isFirst = buttonDownDragged.add(button); log.debug("Received {} mouse button dragged from {}, {} to {}, {}", button, fromX, fromY, toX, toY); EventBus.publish(new DragOnMapEvent(fromX, fromY, toX, toY, button, isFirst, this)); } else { log.debug("Received {} mouse button dragged from {}, {} to {}, {} but skipped it.", button, fromX, fromY, toX, toY); } } } /** * Inform the input handler that the GUI took control over the last actions and the input receiver needs to reset. */ public void guiTookControl() { log.debug("GUI is taking over all input. Receiver reset."); buttonDownReceived.clear(); buttonDownDragged.clear(); } @Override public void mouseWheelMoved(int mouseX, int mouseY, int delta) { if (enabled) { log.debug("Received mouse wheel turned by {} at {}, {}", delta, mouseX, mouseY); } } }