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;
|
||||
|
||||
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;
|
||||
|
||||
public class InputBasedControls {
|
||||
@ -36,12 +36,12 @@ public class InputBasedControls {
|
||||
.toArray(ControlTriggerInputBased[]::new);
|
||||
}
|
||||
|
||||
public void handleInput(Input input) {
|
||||
public void handleInput(InputEvent event) {
|
||||
for (ControlTriggerInputBased c : controls) {
|
||||
Packet packet = c.onInputEvent(input.getEvent());
|
||||
Packet packet = c.onInputEvent(event);
|
||||
|
||||
if (packet != null) {
|
||||
input.consume();
|
||||
event.consume();
|
||||
client.getComms().sendPacket(packet);
|
||||
break;
|
||||
}
|
||||
|
@ -23,15 +23,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
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.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
|
||||
public class GUI {
|
||||
|
||||
@ -46,15 +39,6 @@ public class GUI {
|
||||
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
|
||||
.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() {
|
||||
}
|
||||
|
||||
@ -126,43 +110,12 @@ public class GUI {
|
||||
LAYERS.forEach(Layer::invalidate);
|
||||
}
|
||||
|
||||
private static void dispatchInputEvent(InputEvent event) {
|
||||
Input.Target target;
|
||||
|
||||
if (event instanceof KeyEvent) {
|
||||
if (((KeyEvent) event).isMouse()) {
|
||||
target = Input.Target.HOVERED;
|
||||
} else {
|
||||
target = Input.Target.FOCUSED;
|
||||
public static void dispatchInput(InputEvent event) {
|
||||
synchronized (LAYERS) {
|
||||
for (int i = 0; i < LAYERS.size(); ++i) {
|
||||
LAYERS.get(i).handleInput(event);
|
||||
}
|
||||
} 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 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 {
|
||||
|
||||
@ -106,7 +106,7 @@ public abstract class Layer {
|
||||
|
||||
protected abstract void doRender();
|
||||
|
||||
protected abstract void handleInput(Input input);
|
||||
public abstract void handleInput(InputEvent input);
|
||||
|
||||
protected int getWidth() {
|
||||
return GraphicsInterface.getFrameWidth();
|
||||
|
@ -68,6 +68,10 @@ public class GraphicsInterface {
|
||||
public static void subscribeToInputEvents(Object listener) {
|
||||
InputHandler.register(listener);
|
||||
}
|
||||
|
||||
public static void unsubscribeFromInputEvents(Object listener) {
|
||||
InputHandler.unregister(listener);
|
||||
}
|
||||
|
||||
public static void startNextLayer() {
|
||||
GraphicsBackend.startNextLayer();
|
||||
|
@ -39,6 +39,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(int key, int scancode, int action, int mods) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.key = key;
|
||||
this.scancode = scancode;
|
||||
this.action = action;
|
||||
@ -59,7 +60,7 @@ public class InputHandler {
|
||||
if (GraphicsBackend.getWindowHandle() != window)
|
||||
return;
|
||||
THE_KEY_EVENT.initialize(key, scancode, action, mods);
|
||||
dispatch(THE_KEY_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
|
||||
|
||||
switch (action) {
|
||||
case GLFW.GLFW_PRESS:
|
||||
@ -90,6 +91,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(double x, double y) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
getNewPosition().set(x, y);
|
||||
}
|
||||
|
||||
@ -109,7 +111,7 @@ public class InputHandler {
|
||||
InputTracker.initializeCursorPosition(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);
|
||||
}
|
||||
@ -124,6 +126,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(double xOffset, double yOffset) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.getOffset().set(xOffset, yOffset);
|
||||
}
|
||||
|
||||
@ -139,7 +142,7 @@ public class InputHandler {
|
||||
if (GraphicsBackend.getWindowHandle() != window)
|
||||
return;
|
||||
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
|
||||
dispatch(THE_WHEEL_SCROLL_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
|
||||
}
|
||||
|
||||
// FrameResizeEvent
|
||||
@ -152,6 +155,7 @@ public class InputHandler {
|
||||
|
||||
public void initialize(int width, int height) {
|
||||
this.setTime(GraphicsInterface.getTime());
|
||||
this.setConsumed(false);
|
||||
this.getNewSize().set(width, height);
|
||||
}
|
||||
|
||||
@ -167,17 +171,17 @@ public class InputHandler {
|
||||
int height
|
||||
) {
|
||||
THE_FRAME_RESIZE_EVENT.initialize(width, height);
|
||||
dispatch(THE_FRAME_RESIZE_EVENT);
|
||||
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
|
||||
}
|
||||
|
||||
// Misc
|
||||
|
||||
private static void dispatch(InputEvent event) {
|
||||
INPUT_EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void register(Object 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 com.google.common.eventbus.Subscribe;
|
||||
|
||||
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 {
|
||||
|
||||
@ -107,7 +111,20 @@ class LWJGLInitializer {
|
||||
|
||||
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);
|
||||
|
||||
// Click triggers
|
||||
addListener(KeyEvent.class, e -> {
|
||||
if (e.isRepeat()) {
|
||||
return false;
|
||||
} else if (
|
||||
addInputListener(KeyEvent.class, e -> {
|
||||
if (e.isRepeat())
|
||||
return;
|
||||
|
||||
if (
|
||||
e.isLeftMouseButton() ||
|
||||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||
) {
|
||||
setPressed(e.isPress());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -25,8 +25,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
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.HoverEvent;
|
||||
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.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.InputListener;
|
||||
import ru.windcorp.progressia.common.util.Named;
|
||||
@ -55,7 +54,7 @@ public class Component extends Named {
|
||||
private Component parent = null;
|
||||
|
||||
private EventBus eventBus = null;
|
||||
private InputBus inputBus = null;
|
||||
private final InputBus inputBus = new InputBus(this);
|
||||
|
||||
private int x, y;
|
||||
private int width, height;
|
||||
@ -76,6 +75,9 @@ public class Component extends Named {
|
||||
|
||||
public Component(String name) {
|
||||
super(name);
|
||||
|
||||
// Update hover flag when cursor moves
|
||||
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
|
||||
}
|
||||
|
||||
public Component getParent() {
|
||||
@ -521,6 +523,10 @@ public class Component extends Named {
|
||||
dispatchEvent(new HoverEvent(this, isHovered));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHoverFlag(CursorMoveEvent e) {
|
||||
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
|
||||
}
|
||||
|
||||
public void addListener(Object listener) {
|
||||
if (eventBus == null) {
|
||||
@ -542,121 +548,28 @@ public class Component extends Named {
|
||||
eventBus.post(event);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
|
||||
inputBus.register(type, handlesConsumed, listener);
|
||||
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
|
||||
inputBus.register(type, listener, options);
|
||||
}
|
||||
|
||||
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
|
||||
inputBus.register(matcher, listener, options);
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
|
||||
if (inputBus == null) {
|
||||
inputBus = new InputBus();
|
||||
}
|
||||
|
||||
inputBus.register(type, listener);
|
||||
}
|
||||
|
||||
public void removeListener(InputListener<?> listener) {
|
||||
public void removeInputListener(InputListener<?> listener) {
|
||||
if (inputBus != null) {
|
||||
inputBus.unregister(listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleInput(Input input) {
|
||||
if (inputBus != null && isEnabled()) {
|
||||
inputBus.dispatch(input);
|
||||
}
|
||||
|
||||
InputBus getInputBus() {
|
||||
return inputBus;
|
||||
}
|
||||
|
||||
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) {
|
||||
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() {
|
||||
if (parent != null) {
|
||||
parent.requestReassembly();
|
||||
|
@ -15,12 +15,19 @@
|
||||
* 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.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.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 {
|
||||
|
||||
@ -33,7 +40,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
||||
|
||||
public GUILayer(String name, Layout layout) {
|
||||
super(name);
|
||||
|
||||
getRoot().setLayout(layout);
|
||||
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
|
||||
}
|
||||
|
||||
public Component getRoot() {
|
||||
@ -47,9 +56,81 @@ public abstract class GUILayer extends AssembledFlatLayer {
|
||||
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
|
||||
protected void handleInput(Input input) {
|
||||
getRoot().dispatchInput(input);
|
||||
public void handleInput(InputEvent event) {
|
||||
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
|
||||
|
@ -104,26 +104,22 @@ public class RadioButton extends BasicButton {
|
||||
group.addChild(basicChild);
|
||||
addChild(group);
|
||||
|
||||
addListener(KeyEvent.class, e -> {
|
||||
if (e.isRelease())
|
||||
return false;
|
||||
addInputListener(KeyEvent.class, e -> {
|
||||
if (e.isRelease()) return;
|
||||
|
||||
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||
if (this.group != null) {
|
||||
this.group.selectPrevious();
|
||||
this.group.getSelected().takeFocus();
|
||||
}
|
||||
|
||||
return true;
|
||||
e.consume();
|
||||
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||
if (this.group != null) {
|
||||
this.group.selectNext();
|
||||
this.group.getSelected().takeFocus();
|
||||
}
|
||||
return true;
|
||||
e.consume();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
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.input.InputEvent;
|
||||
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.MutableStringLocalized;
|
||||
|
||||
@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
public void handleInput(InputEvent event) {
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
InputEvent event = input.getEvent();
|
||||
|
||||
if (!event.isConsumed()) {
|
||||
if (event instanceof KeyEvent) {
|
||||
KeyEvent keyEvent = (KeyEvent) event;
|
||||
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||
@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
|
||||
}
|
||||
}
|
||||
|
||||
super.handleInput(input);
|
||||
input.consume();
|
||||
super.handleInput(event);
|
||||
event.consume();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,13 +15,35 @@
|
||||
* 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;
|
||||
|
||||
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 {
|
||||
|
||||
private double time;
|
||||
|
||||
private boolean isConsumed = false;
|
||||
|
||||
public InputEvent(double time) {
|
||||
this.time = time;
|
||||
}
|
||||
@ -36,4 +58,16 @@ public abstract class InputEvent {
|
||||
|
||||
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;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
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 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.mods = mods;
|
||||
}
|
||||
@ -42,6 +101,15 @@ public class KeyMatcher {
|
||||
|
||||
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() {
|
||||
return key;
|
||||
@ -50,49 +118,25 @@ public class KeyMatcher {
|
||||
public int getMods() {
|
||||
return mods;
|
||||
}
|
||||
|
||||
public static KeyMatcher.Builder of(int key) {
|
||||
return new KeyMatcher.Builder(key);
|
||||
|
||||
public KeyMatcher with(int modifier) {
|
||||
return new KeyMatcher(key, mods | modifier);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
public KeyMatcher withShift() {
|
||||
return with(GLFW.GLFW_MOD_SHIFT);
|
||||
}
|
||||
|
||||
private final int key;
|
||||
private int mods = 0;
|
||||
public KeyMatcher withCtrl() {
|
||||
return with(GLFW.GLFW_MOD_CONTROL);
|
||||
}
|
||||
|
||||
public Builder(int key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
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 withAlt() {
|
||||
return with(GLFW.GLFW_MOD_ALT);
|
||||
}
|
||||
|
||||
public KeyMatcher withSuper() {
|
||||
return with(GLFW.GLFW_MOD_SUPER);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ public class Keys {
|
||||
}
|
||||
|
||||
public static int getCode(String internalName) {
|
||||
if (NAMES_TO_CODES.containsKey(internalName)) {
|
||||
if (!NAMES_TO_CODES.containsKey(internalName)) {
|
||||
return -1;
|
||||
} else {
|
||||
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
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package ru.windcorp.progressia.client.graphics.input.bus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.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 {
|
||||
|
||||
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 boolean handleConsumed;
|
||||
|
||||
private final boolean dropIfConsumed;
|
||||
private final YesNoDefault dropIfNotHovered;
|
||||
private final YesNoDefault dropIfNotFocused;
|
||||
|
||||
private final InputListener<?> listener;
|
||||
|
||||
public WrappedListener(
|
||||
Class<?> type,
|
||||
boolean handleConsumed,
|
||||
boolean dropIfConsumed,
|
||||
YesNoDefault dropIfNotHovered,
|
||||
YesNoDefault dropIfNotFocused,
|
||||
InputListener<?> listener
|
||||
) {
|
||||
this.type = type;
|
||||
this.handleConsumed = handleConsumed;
|
||||
this.dropIfConsumed = dropIfConsumed;
|
||||
this.dropIfNotHovered = dropIfNotHovered;
|
||||
this.dropIfNotFocused = dropIfNotFocused;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private boolean handles(Input input) {
|
||||
return (!input.isConsumed() || handleConsumed) &&
|
||||
type.isInstance(input.getEvent());
|
||||
private boolean handles(InputEvent input) {
|
||||
if (dropIfConsumed && input.isConsumed())
|
||||
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")
|
||||
public void handle(Input input) {
|
||||
if (handles(input)) {
|
||||
boolean consumed = ((InputListener<InputEvent>) listener)
|
||||
.handle(
|
||||
(InputEvent) type.cast(input.getEvent())
|
||||
);
|
||||
public void handle(InputEvent event) {
|
||||
if (handles(event)) {
|
||||
// A runtime check of types has been performed; this is safe.
|
||||
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
|
||||
|
||||
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);
|
||||
|
||||
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(
|
||||
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,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
register(type, false, listener);
|
||||
/**
|
||||
* Registers a {@link KeyEvent} listener on this bus. An event has to match
|
||||
* the provided {@link KeyMatcher} to be delivered to the listener.
|
||||
* <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} 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) {
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.removeIf(l -> l.listener == listener);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||
@FunctionalInterface
|
||||
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.backend.FaceCulling;
|
||||
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.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder;
|
||||
@ -225,14 +225,14 @@ public class LayerWorld extends Layer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
if (input.isConsumed())
|
||||
public void handleInput(InputEvent event) {
|
||||
if (event.isConsumed())
|
||||
return;
|
||||
|
||||
tmp_testControls.handleInput(input);
|
||||
tmp_testControls.handleInput(event);
|
||||
|
||||
if (!input.isConsumed()) {
|
||||
inputBasedControls.handleInput(input);
|
||||
if (!event.isConsumed()) {
|
||||
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.flat.AssembledFlatLayer;
|
||||
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.bus.Input;
|
||||
|
||||
public class LayerTestUI extends AssembledFlatLayer {
|
||||
|
||||
@ -91,7 +91,7 @@ public class LayerTestUI extends AssembledFlatLayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInput(Input input) {
|
||||
public void handleInput(InputEvent event) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.ClientState;
|
||||
@ -298,7 +297,7 @@ public class TestContent {
|
||||
"Test:BreakBlock",
|
||||
KeyEvent.class,
|
||||
TestContent::onBlockBreakTrigger,
|
||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_LEFT).matcher(),
|
||||
KeyMatcher.LMB::matches,
|
||||
i -> isAnythingSelected()
|
||||
)
|
||||
);
|
||||
@ -310,7 +309,7 @@ public class TestContent {
|
||||
"Test:PlaceBlock",
|
||||
KeyEvent.class,
|
||||
TestContent::onBlockPlaceTrigger,
|
||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
||||
KeyMatcher.RMB::matches,
|
||||
i -> isAnythingSelected() && TestPlayerControls.getInstance().isBlockSelected()
|
||||
)
|
||||
);
|
||||
@ -323,7 +322,7 @@ public class TestContent {
|
||||
"Test:PlaceTile",
|
||||
KeyEvent.class,
|
||||
TestContent::onTilePlaceTrigger,
|
||||
KeyMatcher.of(GLFW.GLFW_MOUSE_BUTTON_RIGHT).matcher(),
|
||||
KeyMatcher.RMB::matches,
|
||||
i -> isAnythingSelected() && !TestPlayerControls.getInstance().isBlockSelected()
|
||||
)
|
||||
);
|
||||
@ -334,7 +333,7 @@ public class TestContent {
|
||||
"Test:StartNextMusic",
|
||||
KeyEvent.class,
|
||||
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.KeyEvent;
|
||||
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.localization.Localizer;
|
||||
import ru.windcorp.progressia.common.Units;
|
||||
@ -162,19 +161,17 @@ public class TestPlayerControls {
|
||||
);
|
||||
}
|
||||
|
||||
public void handleInput(Input input) {
|
||||
InputEvent event = input.getEvent();
|
||||
|
||||
public void handleInput(InputEvent event) {
|
||||
if (event instanceof KeyEvent) {
|
||||
if (onKeyEvent((KeyEvent) event)) {
|
||||
input.consume();
|
||||
event.consume();
|
||||
}
|
||||
} else if (event instanceof CursorMoveEvent) {
|
||||
onMouseMoved((CursorMoveEvent) event);
|
||||
input.consume();
|
||||
event.consume();
|
||||
} else if (event instanceof WheelScrollEvent) {
|
||||
onWheelScroll((WheelScrollEvent) event);
|
||||
input.consume();
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user