Refactored input event pipeline
- Merged Input functionality into InputEvent; removed Input - InputEvents can now be consumed directly - Removed Input.Target; functionality moved into InputBus - Refactored GUI input event handling - Optimized and reduced recursion by unrolling recursion - All input events are now delivered to all components - Filtering occurs at listener level - Refactored InputBus - Listeners may override former Input.Target logic - Improved registration method - Added exception handling - Improved KeyMatcher - Removed builder in favour of copying with modifications - Added String parsing - Added KeyMatcher.matchesIgnoringAction() - Added integration with InputBus - Renamed Component.{add,remove}Listener methods about InputListeners to Component.{add,remove}InputListener to avoid confusion
This commit is contained in:
parent
efc6a8ecd0
commit
b4ff5114bd
@ -19,7 +19,7 @@
|
|||||||
package ru.windcorp.progressia.client.comms.controls;
|
package ru.windcorp.progressia.client.comms.controls;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.Client;
|
import ru.windcorp.progressia.client.Client;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.common.comms.packets.Packet;
|
import ru.windcorp.progressia.common.comms.packets.Packet;
|
||||||
|
|
||||||
public class InputBasedControls {
|
public class InputBasedControls {
|
||||||
@ -36,12 +36,12 @@ public class InputBasedControls {
|
|||||||
.toArray(ControlTriggerInputBased[]::new);
|
.toArray(ControlTriggerInputBased[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
for (ControlTriggerInputBased c : controls) {
|
for (ControlTriggerInputBased c : controls) {
|
||||||
Packet packet = c.onInputEvent(input.getEvent());
|
Packet packet = c.onInputEvent(event);
|
||||||
|
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
input.consume();
|
event.consume();
|
||||||
client.getComms().sendPacket(packet);
|
client.getComms().sendPacket(packet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
|
|
||||||
public class GUI {
|
public class GUI {
|
||||||
|
|
||||||
@ -46,15 +39,6 @@ public class GUI {
|
|||||||
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
||||||
.synchronizedList(new ArrayList<>());
|
.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
private static class ModifiableInput extends Input {
|
|
||||||
@Override
|
|
||||||
public void initialize(InputEvent event, Target target) {
|
|
||||||
super.initialize(event, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ModifiableInput THE_INPUT = new ModifiableInput();
|
|
||||||
|
|
||||||
private GUI() {
|
private GUI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,43 +110,12 @@ public class GUI {
|
|||||||
LAYERS.forEach(Layer::invalidate);
|
LAYERS.forEach(Layer::invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dispatchInputEvent(InputEvent event) {
|
public static void dispatchInput(InputEvent event) {
|
||||||
Input.Target target;
|
synchronized (LAYERS) {
|
||||||
|
for (int i = 0; i < LAYERS.size(); ++i) {
|
||||||
if (event instanceof KeyEvent) {
|
LAYERS.get(i).handleInput(event);
|
||||||
if (((KeyEvent) event).isMouse()) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else {
|
|
||||||
target = Input.Target.FOCUSED;
|
|
||||||
}
|
}
|
||||||
} else if (event instanceof CursorEvent) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else if (event instanceof WheelEvent) {
|
|
||||||
target = Input.Target.HOVERED;
|
|
||||||
} else if (event instanceof FrameResizeEvent) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
target = Input.Target.ALL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
THE_INPUT.initialize(event, target);
|
|
||||||
LAYERS.forEach(l -> l.handleInput(THE_INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object getEventSubscriber() {
|
|
||||||
return new Object() {
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onFrameResized(FrameResizeEvent event) {
|
|
||||||
GUI.invalidateEverything();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onInput(InputEvent event) {
|
|
||||||
dispatchInputEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
|
||||||
public abstract class Layer {
|
public abstract class Layer {
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ public abstract class Layer {
|
|||||||
|
|
||||||
protected abstract void doRender();
|
protected abstract void doRender();
|
||||||
|
|
||||||
protected abstract void handleInput(Input input);
|
public abstract void handleInput(InputEvent input);
|
||||||
|
|
||||||
protected int getWidth() {
|
protected int getWidth() {
|
||||||
return GraphicsInterface.getFrameWidth();
|
return GraphicsInterface.getFrameWidth();
|
||||||
|
@ -68,6 +68,10 @@ public class GraphicsInterface {
|
|||||||
public static void subscribeToInputEvents(Object listener) {
|
public static void subscribeToInputEvents(Object listener) {
|
||||||
InputHandler.register(listener);
|
InputHandler.register(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void unsubscribeFromInputEvents(Object listener) {
|
||||||
|
InputHandler.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public static void startNextLayer() {
|
public static void startNextLayer() {
|
||||||
GraphicsBackend.startNextLayer();
|
GraphicsBackend.startNextLayer();
|
||||||
|
@ -39,6 +39,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(int key, int scancode, int action, int mods) {
|
public void initialize(int key, int scancode, int action, int mods) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.scancode = scancode;
|
this.scancode = scancode;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
@ -59,7 +60,7 @@ public class InputHandler {
|
|||||||
if (GraphicsBackend.getWindowHandle() != window)
|
if (GraphicsBackend.getWindowHandle() != window)
|
||||||
return;
|
return;
|
||||||
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
||||||
dispatch(THE_KEY_EVENT);
|
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case GLFW.GLFW_PRESS:
|
case GLFW.GLFW_PRESS:
|
||||||
@ -90,6 +91,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(double x, double y) {
|
public void initialize(double x, double y) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
getNewPosition().set(x, y);
|
getNewPosition().set(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ public class InputHandler {
|
|||||||
InputTracker.initializeCursorPosition(x, y);
|
InputTracker.initializeCursorPosition(x, y);
|
||||||
|
|
||||||
THE_CURSOR_MOVE_EVENT.initialize(x, y);
|
THE_CURSOR_MOVE_EVENT.initialize(x, y);
|
||||||
dispatch(THE_CURSOR_MOVE_EVENT);
|
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
|
||||||
|
|
||||||
InputTracker.getCursorPosition().set(x, y);
|
InputTracker.getCursorPosition().set(x, y);
|
||||||
}
|
}
|
||||||
@ -124,6 +126,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(double xOffset, double yOffset) {
|
public void initialize(double xOffset, double yOffset) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.getOffset().set(xOffset, yOffset);
|
this.getOffset().set(xOffset, yOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +142,7 @@ public class InputHandler {
|
|||||||
if (GraphicsBackend.getWindowHandle() != window)
|
if (GraphicsBackend.getWindowHandle() != window)
|
||||||
return;
|
return;
|
||||||
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
||||||
dispatch(THE_WHEEL_SCROLL_EVENT);
|
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FrameResizeEvent
|
// FrameResizeEvent
|
||||||
@ -152,6 +155,7 @@ public class InputHandler {
|
|||||||
|
|
||||||
public void initialize(int width, int height) {
|
public void initialize(int width, int height) {
|
||||||
this.setTime(GraphicsInterface.getTime());
|
this.setTime(GraphicsInterface.getTime());
|
||||||
|
this.setConsumed(false);
|
||||||
this.getNewSize().set(width, height);
|
this.getNewSize().set(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,17 +171,17 @@ public class InputHandler {
|
|||||||
int height
|
int height
|
||||||
) {
|
) {
|
||||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||||
dispatch(THE_FRAME_RESIZE_EVENT);
|
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
|
||||||
private static void dispatch(InputEvent event) {
|
|
||||||
INPUT_EVENT_BUS.post(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void register(Object listener) {
|
public static void register(Object listener) {
|
||||||
INPUT_EVENT_BUS.register(listener);
|
INPUT_EVENT_BUS.register(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void unregister(Object listener) {
|
||||||
|
INPUT_EVENT_BUS.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,11 @@ import static org.lwjgl.system.MemoryUtil.*;
|
|||||||
|
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.GUI;
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
|
||||||
class LWJGLInitializer {
|
class LWJGLInitializer {
|
||||||
|
|
||||||
@ -107,7 +111,20 @@ class LWJGLInitializer {
|
|||||||
|
|
||||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
||||||
|
|
||||||
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
|
GraphicsInterface.subscribeToInputEvents(new Object() {
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onFrameResized(FrameResizeEvent event) {
|
||||||
|
GUI.invalidateEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onInputEvent(InputEvent event) {
|
||||||
|
GUI.dispatchInput(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -54,18 +54,17 @@ public abstract class BasicButton extends Component {
|
|||||||
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||||
|
|
||||||
// Click triggers
|
// Click triggers
|
||||||
addListener(KeyEvent.class, e -> {
|
addInputListener(KeyEvent.class, e -> {
|
||||||
if (e.isRepeat()) {
|
if (e.isRepeat())
|
||||||
return false;
|
return;
|
||||||
} else if (
|
|
||||||
|
if (
|
||||||
e.isLeftMouseButton() ||
|
e.isLeftMouseButton() ||
|
||||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||||
) {
|
) {
|
||||||
setPressed(e.isPress());
|
setPressed(e.isPress());
|
||||||
return true;
|
e.consume();
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,8 +25,6 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
@ -39,9 +37,10 @@ import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
|||||||
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
|
||||||
import ru.windcorp.progressia.common.util.Named;
|
import ru.windcorp.progressia.common.util.Named;
|
||||||
@ -55,7 +54,7 @@ public class Component extends Named {
|
|||||||
private Component parent = null;
|
private Component parent = null;
|
||||||
|
|
||||||
private EventBus eventBus = null;
|
private EventBus eventBus = null;
|
||||||
private InputBus inputBus = null;
|
private final InputBus inputBus = new InputBus(this);
|
||||||
|
|
||||||
private int x, y;
|
private int x, y;
|
||||||
private int width, height;
|
private int width, height;
|
||||||
@ -76,6 +75,9 @@ public class Component extends Named {
|
|||||||
|
|
||||||
public Component(String name) {
|
public Component(String name) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
|
// Update hover flag when cursor moves
|
||||||
|
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getParent() {
|
public Component getParent() {
|
||||||
@ -521,6 +523,10 @@ public class Component extends Named {
|
|||||||
dispatchEvent(new HoverEvent(this, isHovered));
|
dispatchEvent(new HoverEvent(this, isHovered));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateHoverFlag(CursorMoveEvent e) {
|
||||||
|
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener(Object listener) {
|
public void addListener(Object listener) {
|
||||||
if (eventBus == null) {
|
if (eventBus == null) {
|
||||||
@ -542,121 +548,28 @@ public class Component extends Named {
|
|||||||
eventBus.post(event);
|
eventBus.post(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addListener(
|
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
||||||
Class<? extends T> type,
|
inputBus.register(type, listener, options);
|
||||||
boolean handlesConsumed,
|
}
|
||||||
InputListener<T> listener
|
|
||||||
) {
|
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
||||||
if (inputBus == null) {
|
inputBus.register(matcher, listener, options);
|
||||||
inputBus = new InputBus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBus.register(type, handlesConsumed, listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
public void removeInputListener(InputListener<?> listener) {
|
||||||
if (inputBus == null) {
|
|
||||||
inputBus = new InputBus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBus.register(type, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeListener(InputListener<?> listener) {
|
|
||||||
if (inputBus != null) {
|
if (inputBus != null) {
|
||||||
inputBus.unregister(listener);
|
inputBus.unregister(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleInput(Input input) {
|
InputBus getInputBus() {
|
||||||
if (inputBus != null && isEnabled()) {
|
return inputBus;
|
||||||
inputBus.dispatch(input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchInput(Input input) {
|
|
||||||
try {
|
|
||||||
switch (input.getTarget()) {
|
|
||||||
case FOCUSED:
|
|
||||||
dispatchInputToFocused(input);
|
|
||||||
break;
|
|
||||||
case HOVERED:
|
|
||||||
dispatchInputToHovered(input);
|
|
||||||
break;
|
|
||||||
case ALL:
|
|
||||||
default:
|
|
||||||
dispatchInputToAll(input);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToFocused(Input input) {
|
|
||||||
Component c = findFocused();
|
|
||||||
|
|
||||||
if (c == null)
|
|
||||||
return;
|
|
||||||
if (attemptFocusTransfer(input, c))
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (c != null) {
|
|
||||||
c.handleInput(input);
|
|
||||||
c = c.getParent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToHovered(Input input) {
|
|
||||||
getChildren().forEach(child -> {
|
|
||||||
if (child.containsCursor()) {
|
|
||||||
child.setHovered(true);
|
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
|
||||||
child.dispatchInput(input);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child.setHovered(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
handleInput(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchInputToAll(Input input) {
|
|
||||||
getChildren().forEach(c -> c.dispatchInput(input));
|
|
||||||
handleInput(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean attemptFocusTransfer(Input input, Component focused) {
|
|
||||||
if (input.isConsumed())
|
|
||||||
return false;
|
|
||||||
if (!(input.getEvent() instanceof KeyEvent))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
KeyEvent keyInput = (KeyEvent) input.getEvent();
|
|
||||||
|
|
||||||
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
|
|
||||||
input.consume();
|
|
||||||
if (keyInput.hasShift()) {
|
|
||||||
focused.focusPrevious();
|
|
||||||
} else {
|
|
||||||
focused.focusNext();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean contains(int x, int y) {
|
public synchronized boolean contains(int x, int y) {
|
||||||
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsCursor() {
|
|
||||||
return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestReassembly() {
|
public void requestReassembly() {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.requestReassembly();
|
parent.requestReassembly();
|
||||||
|
@ -15,12 +15,19 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
|
||||||
|
import ru.windcorp.progressia.common.util.StashingStack;
|
||||||
|
|
||||||
public abstract class GUILayer extends AssembledFlatLayer {
|
public abstract class GUILayer extends AssembledFlatLayer {
|
||||||
|
|
||||||
@ -33,7 +40,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
|||||||
|
|
||||||
public GUILayer(String name, Layout layout) {
|
public GUILayer(String name, Layout layout) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
getRoot().setLayout(layout);
|
getRoot().setLayout(layout);
|
||||||
|
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getRoot() {
|
public Component getRoot() {
|
||||||
@ -47,9 +56,81 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
|||||||
getRoot().assemble(target);
|
getRoot().assemble(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack frame for {@link #handleInput(InputEvent)}.
|
||||||
|
*/
|
||||||
|
private static class EventHandlingFrame {
|
||||||
|
Component component;
|
||||||
|
Iterator<Component> children;
|
||||||
|
|
||||||
|
void init(Component c) {
|
||||||
|
component = c;
|
||||||
|
children = c.getChildren().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
component = null;
|
||||||
|
children = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack for {@link #handleInput(InputEvent)}.
|
||||||
|
*/
|
||||||
|
private StashingStack<EventHandlingFrame> path = new StashingStack<>(64, EventHandlingFrame::new);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is essentially a depth-first iteration of the component tree. The
|
||||||
|
* recursive procedure has been unrolled to reduce call stack length.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
getRoot().dispatchInput(input);
|
if (!path.isEmpty()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"path is not empty: " + path + ". Are events being processed concurrently?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push().init(root);
|
||||||
|
|
||||||
|
while (!path.isEmpty()) {
|
||||||
|
|
||||||
|
Iterator<Component> it = path.peek().children;
|
||||||
|
if (it.hasNext()) {
|
||||||
|
|
||||||
|
Component c = it.next();
|
||||||
|
|
||||||
|
if (c.isEnabled()) {
|
||||||
|
if (c.getChildren().isEmpty()) {
|
||||||
|
c.getInputBus().dispatch(event);
|
||||||
|
} else {
|
||||||
|
path.push().init(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
path.peek().component.getInputBus().dispatch(event);
|
||||||
|
path.pop().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptFocusTransfer(KeyEvent e) {
|
||||||
|
Component focused = getRoot().findFocused();
|
||||||
|
|
||||||
|
if (focused == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
|
||||||
|
e.consume();
|
||||||
|
if (e.hasShift()) {
|
||||||
|
focused.focusPrevious();
|
||||||
|
} else {
|
||||||
|
focused.focusNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -104,26 +104,22 @@ public class RadioButton extends BasicButton {
|
|||||||
group.addChild(basicChild);
|
group.addChild(basicChild);
|
||||||
addChild(group);
|
addChild(group);
|
||||||
|
|
||||||
addListener(KeyEvent.class, e -> {
|
addInputListener(KeyEvent.class, e -> {
|
||||||
if (e.isRelease())
|
if (e.isRelease()) return;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||||
if (this.group != null) {
|
if (this.group != null) {
|
||||||
this.group.selectPrevious();
|
this.group.selectPrevious();
|
||||||
this.group.getSelected().takeFocus();
|
this.group.getSelected().takeFocus();
|
||||||
}
|
}
|
||||||
|
e.consume();
|
||||||
return true;
|
|
||||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||||
if (this.group != null) {
|
if (this.group != null) {
|
||||||
this.group.selectNext();
|
this.group.selectNext();
|
||||||
this.group.getSelected().takeFocus();
|
this.group.getSelected().takeFocus();
|
||||||
}
|
}
|
||||||
return true;
|
e.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addAction(b -> setChecked(true));
|
addAction(b -> setChecked(true));
|
||||||
|
@ -33,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
|
|||||||
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
import ru.windcorp.progressia.client.localization.MutableString;
|
import ru.windcorp.progressia.client.localization.MutableString;
|
||||||
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
|
||||||
|
|
||||||
@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
if (!event.isConsumed()) {
|
||||||
InputEvent event = input.getEvent();
|
|
||||||
|
|
||||||
if (event instanceof KeyEvent) {
|
if (event instanceof KeyEvent) {
|
||||||
KeyEvent keyEvent = (KeyEvent) event;
|
KeyEvent keyEvent = (KeyEvent) event;
|
||||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.handleInput(input);
|
super.handleInput(event);
|
||||||
input.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,35 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of user input.
|
||||||
|
* <p>
|
||||||
|
* User input events are typically generated by graphics backend between frames
|
||||||
|
* and passed to the graphics layers from top to bottom. Layers that use
|
||||||
|
* {@link Component}s will forward this event through the Component hierarchy.
|
||||||
|
* <p>
|
||||||
|
* Events have a {@code consumed} flag. A freshly-generated event will have this
|
||||||
|
* flag set to {@code false}. Event listeners that process the event will
|
||||||
|
* usually choose to raise the flag ("consume the event") to ask future
|
||||||
|
* listeners to ignore this event. This is done to avoid multiple UI interfaces
|
||||||
|
* reacting to single input. By default, listeners will not receive consumed
|
||||||
|
* events; however, some listeners may choose to receive, handle and even
|
||||||
|
* un-consume the event.
|
||||||
|
* <p>
|
||||||
|
* {@code InputEvent} objects may be reused for future input events after their
|
||||||
|
* processing is complete; to obtain a static copy, use {@link #snapshot()}.
|
||||||
|
*/
|
||||||
public abstract class InputEvent {
|
public abstract class InputEvent {
|
||||||
|
|
||||||
private double time;
|
private double time;
|
||||||
|
|
||||||
|
private boolean isConsumed = false;
|
||||||
|
|
||||||
public InputEvent(double time) {
|
public InputEvent(double time) {
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
@ -36,4 +58,16 @@ public abstract class InputEvent {
|
|||||||
|
|
||||||
public abstract InputEvent snapshot();
|
public abstract InputEvent snapshot();
|
||||||
|
|
||||||
|
public boolean isConsumed() {
|
||||||
|
return isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsumed(boolean isConsumed) {
|
||||||
|
this.isConsumed = isConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consume() {
|
||||||
|
setConsumed(true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,75 @@
|
|||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input;
|
package ru.windcorp.progressia.client.graphics.input;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
public class KeyMatcher {
|
public class KeyMatcher {
|
||||||
|
|
||||||
|
private static final Pattern DECLAR_SPLIT_REGEX = Pattern.compile("\\s*\\+\\s*");
|
||||||
|
private static final Map<String, Integer> MOD_TOKENS = ImmutableMap.of(
|
||||||
|
"SHIFT", GLFW.GLFW_MOD_SHIFT,
|
||||||
|
"CONTROL", GLFW.GLFW_MOD_CONTROL,
|
||||||
|
"ALT", GLFW.GLFW_MOD_ALT,
|
||||||
|
"SUPER", GLFW.GLFW_MOD_SUPER
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final KeyMatcher LMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT);
|
||||||
|
public static final KeyMatcher RMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
|
||||||
|
public static final KeyMatcher MMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE);
|
||||||
|
|
||||||
private final int key;
|
private final int key;
|
||||||
private final int mods;
|
private final int mods;
|
||||||
|
|
||||||
protected KeyMatcher(int key, int mods) {
|
public KeyMatcher(int key, int mods) {
|
||||||
|
this.key = key;
|
||||||
|
this.mods = mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMatcher(int key) {
|
||||||
|
this.key = key;
|
||||||
|
this.mods = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyMatcher(String declar) {
|
||||||
|
String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
|
||||||
|
if (tokens.length == 0) {
|
||||||
|
throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
int key = -1;
|
||||||
|
int mods = 0;
|
||||||
|
|
||||||
|
for (String token : tokens) {
|
||||||
|
token = token.toUpperCase();
|
||||||
|
|
||||||
|
if (MOD_TOKENS.containsKey(token)) {
|
||||||
|
int mod = MOD_TOKENS.get(token);
|
||||||
|
if ((mods & mod) != 0) {
|
||||||
|
throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
mods |= mod;
|
||||||
|
} else if (key != -1) {
|
||||||
|
throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
|
||||||
|
} else {
|
||||||
|
token = token.replace(' ', '_');
|
||||||
|
|
||||||
|
if (token.startsWith("KEYPAD_")) {
|
||||||
|
token = "KP_" + token.substring("KEYPAD_".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
key = Keys.getCode(token);
|
||||||
|
|
||||||
|
if (key == -1) {
|
||||||
|
throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
}
|
}
|
||||||
@ -42,6 +101,15 @@ public class KeyMatcher {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matchesIgnoringAction(KeyEvent event) {
|
||||||
|
if (event.getKey() != getKey())
|
||||||
|
return false;
|
||||||
|
if ((event.getMods() & getMods()) != getMods())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public int getKey() {
|
public int getKey() {
|
||||||
return key;
|
return key;
|
||||||
@ -50,49 +118,25 @@ public class KeyMatcher {
|
|||||||
public int getMods() {
|
public int getMods() {
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyMatcher.Builder of(int key) {
|
public KeyMatcher with(int modifier) {
|
||||||
return new KeyMatcher.Builder(key);
|
return new KeyMatcher(key, mods | modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public KeyMatcher withShift() {
|
||||||
|
return with(GLFW.GLFW_MOD_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
private final int key;
|
public KeyMatcher withCtrl() {
|
||||||
private int mods = 0;
|
return with(GLFW.GLFW_MOD_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
public Builder(int key) {
|
public KeyMatcher withAlt() {
|
||||||
this.key = key;
|
return with(GLFW.GLFW_MOD_ALT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder with(int modifier) {
|
|
||||||
this.mods += modifier;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withShift() {
|
|
||||||
return with(GLFW.GLFW_MOD_SHIFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withCtrl() {
|
|
||||||
return with(GLFW.GLFW_MOD_CONTROL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAlt() {
|
|
||||||
return with(GLFW.GLFW_MOD_ALT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withSuper() {
|
|
||||||
return with(GLFW.GLFW_MOD_SUPER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyMatcher build() {
|
|
||||||
return new KeyMatcher(key, mods);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate<KeyEvent> matcher() {
|
|
||||||
return build()::matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public KeyMatcher withSuper() {
|
||||||
|
return with(GLFW.GLFW_MOD_SUPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ public class Keys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getCode(String internalName) {
|
public static int getCode(String internalName) {
|
||||||
if (NAMES_TO_CODES.containsKey(internalName)) {
|
if (!NAMES_TO_CODES.containsKey(internalName)) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return NAMES_TO_CODES.get(internalName);
|
return NAMES_TO_CODES.get(internalName);
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Progressia
|
|
||||||
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
|
||||||
*
|
|
||||||
* This program 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.
|
|
||||||
*
|
|
||||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input.bus;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
|
||||||
|
|
||||||
public class Input {
|
|
||||||
|
|
||||||
public static enum Target {
|
|
||||||
FOCUSED, HOVERED, ALL
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputEvent event;
|
|
||||||
|
|
||||||
private boolean isConsumed;
|
|
||||||
|
|
||||||
private Target target;
|
|
||||||
|
|
||||||
protected void initialize(InputEvent event, Target target) {
|
|
||||||
this.event = event;
|
|
||||||
this.target = target;
|
|
||||||
|
|
||||||
this.isConsumed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputEvent getEvent() {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConsumed() {
|
|
||||||
return isConsumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsumed(boolean isConsumed) {
|
|
||||||
this.isConsumed = isConsumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consume() {
|
|
||||||
setConsumed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Target getTarget() {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -15,73 +15,401 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.windcorp.progressia.client.graphics.input.bus;
|
package ru.windcorp.progressia.client.graphics.input.bus;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ru.windcorp.jputil.ArrayUtil;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||||
|
import ru.windcorp.progressia.common.util.crash.CrashReports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event bus optionally related to a {@link Component} that delivers input
|
||||||
|
* events to input listeners. This bus may skip listeners based on circumstance;
|
||||||
|
* behavior can be customized for each listener with {@link Option}s.
|
||||||
|
* <p>
|
||||||
|
* By default, events are filtered by four checks before being delivered to each
|
||||||
|
* listener:
|
||||||
|
* <ol>
|
||||||
|
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
|
||||||
|
* RECEIVE_CONSUMED} is set, events that are consumed will not be
|
||||||
|
* delivered.</li>
|
||||||
|
* <li><em>Hover check</em>: for certain event types (for example,
|
||||||
|
* {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
|
||||||
|
* isMouse()}), the event will only be delivered if the component is hovered.
|
||||||
|
* This check may be bypassed with option {@link Option#IGNORE_HOVER
|
||||||
|
* IGNORE_HOVER} or made mandatory for all events with
|
||||||
|
* {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
|
||||||
|
* succeeds if no component is provided.</li>
|
||||||
|
* <li><em>Focus check</em>: for certain event types (for example,
|
||||||
|
* {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
|
||||||
|
* if the component has focus. This check may be bypassed with option
|
||||||
|
* {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
|
||||||
|
* with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
|
||||||
|
* succeeds if no component is provided.</li>
|
||||||
|
* <li><em>Type check</em>: events of type {@code E} are only delivered to
|
||||||
|
* listeners registered with event type {@code T} if objects of type {@code E}
|
||||||
|
* can be cast to {@code T}.</li>
|
||||||
|
* </ol>
|
||||||
|
* Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
public class InputBus {
|
public class InputBus {
|
||||||
|
|
||||||
private static class WrappedListener {
|
/**
|
||||||
|
* Options that allow customization of checks for listeners.
|
||||||
|
*/
|
||||||
|
public enum Option {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore checks for consumed events, hover and focus; deliver event if
|
||||||
|
* at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
|
||||||
|
* {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
|
||||||
|
*/
|
||||||
|
ALWAYS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive events that were previously consumed.
|
||||||
|
*/
|
||||||
|
RECEIVE_CONSUMED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not process events if the listener is registered with a component
|
||||||
|
* and the component is not hovered.
|
||||||
|
*/
|
||||||
|
REQUIRE_HOVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliver events even if the event is limited to hovered components by
|
||||||
|
* default.
|
||||||
|
*/
|
||||||
|
IGNORE_HOVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not process events if the listener is registered with a component
|
||||||
|
* and the component is not focused.
|
||||||
|
*/
|
||||||
|
REQUIRE_FOCUS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliver events even if the event is limited to focused components by
|
||||||
|
* default.
|
||||||
|
*/
|
||||||
|
IGNORE_FOCUS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliver events according to
|
||||||
|
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||||
|
* {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
|
IGNORE_ACTION;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum YesNoDefault {
|
||||||
|
YES, NO, DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener with check preferences resolved and type specified.
|
||||||
|
*/
|
||||||
|
private class WrappedListener {
|
||||||
|
|
||||||
private final Class<?> type;
|
private final Class<?> type;
|
||||||
private final boolean handleConsumed;
|
|
||||||
|
private final boolean dropIfConsumed;
|
||||||
|
private final YesNoDefault dropIfNotHovered;
|
||||||
|
private final YesNoDefault dropIfNotFocused;
|
||||||
|
|
||||||
private final InputListener<?> listener;
|
private final InputListener<?> listener;
|
||||||
|
|
||||||
public WrappedListener(
|
public WrappedListener(
|
||||||
Class<?> type,
|
Class<?> type,
|
||||||
boolean handleConsumed,
|
boolean dropIfConsumed,
|
||||||
|
YesNoDefault dropIfNotHovered,
|
||||||
|
YesNoDefault dropIfNotFocused,
|
||||||
InputListener<?> listener
|
InputListener<?> listener
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.handleConsumed = handleConsumed;
|
this.dropIfConsumed = dropIfConsumed;
|
||||||
|
this.dropIfNotHovered = dropIfNotHovered;
|
||||||
|
this.dropIfNotFocused = dropIfNotFocused;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handles(Input input) {
|
private boolean handles(InputEvent input) {
|
||||||
return (!input.isConsumed() || handleConsumed) &&
|
if (dropIfConsumed && input.isConsumed())
|
||||||
type.isInstance(input.getEvent());
|
return false;
|
||||||
|
|
||||||
|
switch (dropIfNotHovered) {
|
||||||
|
case YES:
|
||||||
|
if (!isHovered())
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case NO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
|
if (isHovered())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (input instanceof CursorEvent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (input instanceof WheelEvent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dropIfNotFocused) {
|
||||||
|
case YES:
|
||||||
|
if (!isFocused())
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case NO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
|
if (isFocused())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type.isInstance(input))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the listener if the event is deemed appropriate by the four
|
||||||
|
* checks.
|
||||||
|
*
|
||||||
|
* @param event the event to deliver
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void handle(Input input) {
|
public void handle(InputEvent event) {
|
||||||
if (handles(input)) {
|
if (handles(event)) {
|
||||||
boolean consumed = ((InputListener<InputEvent>) listener)
|
// A runtime check of types has been performed; this is safe.
|
||||||
.handle(
|
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
||||||
(InputEvent) type.cast(input.getEvent())
|
|
||||||
);
|
|
||||||
|
|
||||||
input.setConsumed(consumed);
|
try {
|
||||||
|
castListener.handle(event);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw CrashReports.report(
|
||||||
|
e,
|
||||||
|
"InputListener %s for component %s has failed to receive event %s",
|
||||||
|
listener,
|
||||||
|
owner,
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component queried for focus and hover. May be {@code null}.
|
||||||
|
*/
|
||||||
|
private final Component owner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered listeners.
|
||||||
|
*/
|
||||||
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
||||||
|
|
||||||
public void dispatch(Input input) {
|
/**
|
||||||
listeners.forEach(l -> l.handle(input));
|
* Creates a new input bus that consults the specified {@link Component} to
|
||||||
|
* determine hover and focus.
|
||||||
|
*
|
||||||
|
* @param owner the component to use for hover and focus tests
|
||||||
|
* @see #InputBus()
|
||||||
|
*/
|
||||||
|
public InputBus(Component owner) {
|
||||||
|
this.owner = Objects.requireNonNull(owner, "owner");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new input bus that assumes all hover and focus checks are
|
||||||
|
* successful.
|
||||||
|
*
|
||||||
|
* @see #InputBus(Component)
|
||||||
|
*/
|
||||||
|
public InputBus() {
|
||||||
|
this.owner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether hover should be assumed for this event bus.
|
||||||
|
*
|
||||||
|
* @return {@code true} iff no component is linked or the linked component
|
||||||
|
* is hovered
|
||||||
|
*/
|
||||||
|
private boolean isHovered() {
|
||||||
|
return owner == null ? true : owner.isHovered();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether focus should be assumed for this event bus.
|
||||||
|
*
|
||||||
|
* @return {@code true} iff no component is linked or the linked component
|
||||||
|
* is focused
|
||||||
|
*/
|
||||||
|
private boolean isFocused() {
|
||||||
|
return owner == null ? true : owner.isFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches (delivers) the provided event to all appropriate listeners.
|
||||||
|
*
|
||||||
|
* @param event the event to process
|
||||||
|
*/
|
||||||
|
public void dispatch(InputEvent event) {
|
||||||
|
Objects.requireNonNull(event, "event");
|
||||||
|
for (WrappedListener listener : listeners) {
|
||||||
|
listener.handle(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener on this bus.
|
||||||
|
* <p>
|
||||||
|
* {@code type} specifies the class of events that should be passed to this
|
||||||
|
* listener. Only events of types that extend, implement or equal
|
||||||
|
* {@code type} are processed.
|
||||||
|
* <p>
|
||||||
|
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||||
|
* processing of certain events in certain circumstances. See
|
||||||
|
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||||
|
* checks performed and the effects of various options. When providing
|
||||||
|
* options to this method, later options override the effects of previous
|
||||||
|
* options.
|
||||||
|
* <p>
|
||||||
|
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
|
||||||
|
*
|
||||||
|
* @param type the event class to deliver
|
||||||
|
* @param listener the listener
|
||||||
|
* @param options the options for this listener
|
||||||
|
*/
|
||||||
public <T extends InputEvent> void register(
|
public <T extends InputEvent> void register(
|
||||||
Class<? extends T> type,
|
Class<? extends T> type,
|
||||||
boolean handlesConsumed,
|
InputListener<T> listener,
|
||||||
InputListener<T> listener
|
Option... options
|
||||||
) {
|
) {
|
||||||
listeners.add(new WrappedListener(type, handlesConsumed, listener));
|
Objects.requireNonNull(type, "type");
|
||||||
|
Objects.requireNonNull(listener, "listener");
|
||||||
|
|
||||||
|
boolean dropIfConsumed = true;
|
||||||
|
YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
|
||||||
|
YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
for (Option option : options) {
|
||||||
|
switch (option) {
|
||||||
|
case ALWAYS:
|
||||||
|
dropIfConsumed = false;
|
||||||
|
dropIfNotHovered = YesNoDefault.NO;
|
||||||
|
dropIfNotFocused = YesNoDefault.NO;
|
||||||
|
break;
|
||||||
|
case RECEIVE_CONSUMED:
|
||||||
|
dropIfConsumed = false;
|
||||||
|
break;
|
||||||
|
case REQUIRE_HOVER:
|
||||||
|
dropIfNotHovered = YesNoDefault.YES;
|
||||||
|
break;
|
||||||
|
case IGNORE_HOVER:
|
||||||
|
dropIfNotFocused = YesNoDefault.NO;
|
||||||
|
break;
|
||||||
|
case REQUIRE_FOCUS:
|
||||||
|
dropIfNotHovered = YesNoDefault.YES;
|
||||||
|
break;
|
||||||
|
case IGNORE_FOCUS:
|
||||||
|
dropIfNotFocused = YesNoDefault.NO;
|
||||||
|
break;
|
||||||
|
case IGNORE_ACTION:
|
||||||
|
// Ignore
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unexpected option " + option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends InputEvent> void register(
|
/**
|
||||||
Class<? extends T> type,
|
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
||||||
InputListener<T> listener
|
* the provided {@link KeyMatcher} to be delivered to the listener.
|
||||||
) {
|
* <p>
|
||||||
register(type, false, listener);
|
* Zero or more {@link Option}s may be specified to enable or disable the
|
||||||
|
* processing of certain events in certain circumstances. See
|
||||||
|
* {@linkplain InputBus class description} for a detailed breakdown of the
|
||||||
|
* checks performed and the effects of various options. When providing
|
||||||
|
* options to this method, later options override the effects of previous
|
||||||
|
* options.
|
||||||
|
* <p>
|
||||||
|
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
|
||||||
|
* are delivered according to
|
||||||
|
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
|
||||||
|
* {@link KeyMatcher#matches(KeyEvent)}.
|
||||||
|
* specified.
|
||||||
|
*
|
||||||
|
* @param matcher an event filter
|
||||||
|
* @param listener the listener
|
||||||
|
* @param options the options for this listener
|
||||||
|
*/
|
||||||
|
public void register(KeyMatcher matcher, InputListener<? super KeyEvent> listener, Option... options) {
|
||||||
|
Objects.requireNonNull(matcher, "matcher");
|
||||||
|
Objects.requireNonNull(listener, "listener");
|
||||||
|
|
||||||
|
InputListener<KeyEvent> filteringListener;
|
||||||
|
|
||||||
|
if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
|
||||||
|
filteringListener = e -> {
|
||||||
|
if (matcher.matchesIgnoringAction(e)) {
|
||||||
|
listener.handle(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
filteringListener = e -> {
|
||||||
|
if (matcher.matches(e)) {
|
||||||
|
listener.handle(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
register(KeyEvent.class, filteringListener, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all occurrences of the provided listener from this bus.
|
||||||
|
*
|
||||||
|
* @param listener the listener to unregister
|
||||||
|
*/
|
||||||
public void unregister(InputListener<?> listener) {
|
public void unregister(InputListener<?> listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
listeners.removeIf(l -> l.listener == listener);
|
listeners.removeIf(l -> l.listener == listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface InputListener<T extends InputEvent> {
|
public interface InputListener<T extends InputEvent> {
|
||||||
|
|
||||||
boolean handle(T event);
|
void handle(T event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
|
|||||||
import ru.windcorp.progressia.client.graphics.Layer;
|
import ru.windcorp.progressia.client.graphics.Layer;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
||||||
@ -225,14 +225,14 @@ public class LayerWorld extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
if (input.isConsumed())
|
if (event.isConsumed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tmp_testControls.handleInput(input);
|
tmp_testControls.handleInput(event);
|
||||||
|
|
||||||
if (!input.isConsumed()) {
|
if (!event.isConsumed()) {
|
||||||
inputBasedControls.handleInput(input);
|
inputBasedControls.handleInput(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ import ru.windcorp.progressia.client.graphics.Colors;
|
|||||||
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
|
|
||||||
public class LayerTestUI extends AssembledFlatLayer {
|
public class LayerTestUI extends AssembledFlatLayer {
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ public class LayerTestUI extends AssembledFlatLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import glm.vec._3.i.Vec3i;
|
import glm.vec._3.i.Vec3i;
|
||||||
import ru.windcorp.progressia.client.ClientState;
|
import ru.windcorp.progressia.client.ClientState;
|
||||||
@ -298,7 +297,7 @@ public class TestContent {
|
|||||||
"Test:BreakBlock",
|
"Test:BreakBlock",
|
||||||
KeyEvent.class,
|
KeyEvent.class,
|
||||||
TestContent::onBlockBreakTrigger,
|
TestContent::onBlockBreakTrigger,
|
||||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_LEFT).matcher(),
|
KeyMatcher.LMB::matches,
|
||||||
i -> isAnythingSelected()
|
i -> isAnythingSelected()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -310,7 +309,7 @@ public class TestContent {
|
|||||||
"Test:PlaceBlock",
|
"Test:PlaceBlock",
|
||||||
KeyEvent.class,
|
KeyEvent.class,
|
||||||
TestContent::onBlockPlaceTrigger,
|
TestContent::onBlockPlaceTrigger,
|
||||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
KeyMatcher.RMB::matches,
|
||||||
i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected()
|
i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -323,7 +322,7 @@ public class TestContent {
|
|||||||
"Test:PlaceTile",
|
"Test:PlaceTile",
|
||||||
KeyEvent.class,
|
KeyEvent.class,
|
||||||
TestContent::onTilePlaceTrigger,
|
TestContent::onTilePlaceTrigger,
|
||||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
KeyMatcher.RMB::matches,
|
||||||
i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected()
|
i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -334,7 +333,7 @@ public class TestContent {
|
|||||||
"Test:StartNextMusic",
|
"Test:StartNextMusic",
|
||||||
KeyEvent.class,
|
KeyEvent.class,
|
||||||
TestMusicPlayer::startNextNow,
|
TestMusicPlayer::startNextNow,
|
||||||
KeyMatcher.of(GLFW.GLFW_KEY_M).matcher()
|
new KeyMatcher("M")::matches
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
|
|||||||
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
|
||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
|
||||||
import ru.windcorp.progressia.client.localization.Localizer;
|
import ru.windcorp.progressia.client.localization.Localizer;
|
||||||
import ru.windcorp.progressia.common.Units;
|
import ru.windcorp.progressia.common.Units;
|
||||||
@ -162,19 +161,17 @@ public class TestPlayerControls {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleInput(Input input) {
|
public void handleInput(InputEvent event) {
|
||||||
InputEvent event = input.getEvent();
|
|
||||||
|
|
||||||
if (event instanceof KeyEvent) {
|
if (event instanceof KeyEvent) {
|
||||||
if (onKeyEvent((KeyEvent) event)) {
|
if (onKeyEvent((KeyEvent) event)) {
|
||||||
input.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
} else if (event instanceof CursorMoveEvent) {
|
} else if (event instanceof CursorMoveEvent) {
|
||||||
onMouseMoved((CursorMoveEvent) event);
|
onMouseMoved((CursorMoveEvent) event);
|
||||||
input.consume();
|
event.consume();
|
||||||
} else if (event instanceof WheelScrollEvent) {
|
} else if (event instanceof WheelScrollEvent) {
|
||||||
onWheelScroll((WheelScrollEvent) event);
|
onWheelScroll((WheelScrollEvent) event);
|
||||||
input.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user