From 2abffff84309137541e9742d3a38e32c07a3f085 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Wed, 5 Aug 2020 00:16:44 +0300 Subject: [PATCH] Ported Component system from https://github.com/OLEGSHA/crystal-farm - Added Components - Also added component-related events and several Layouts - Added GUILayer to integrate Components into Layers - Added InputBuses to handle consumable events - Layers now have a separate input handling pipeline - Mouse button clicks are now handled together with keyboard keys - KeyEvent, Keys, InputTracker consider MBs to be keys - Cursor position and pressed keys are now tracked by InputTracker - GLFW key codes can now be mapped to names with Keys - Added KeyMatcher for later - Fixed world clipping into UI - Cursor info is no longer available thru GraphicsInterface, use InputTracker instead --- .../windcorp/optica/client/ClientProxy.java | 2 + .../windcorp/optica/client/graphics/GUI.java | 42 ++ .../optica/client/graphics/Layer.java | 5 + .../graphics/backend/GraphicsBackend.java | 4 + .../graphics/backend/GraphicsInterface.java | 13 +- .../client/graphics/backend/InputHandler.java | 43 +- .../client/graphics/backend/InputTracker.java | 72 ++ .../graphics/backend/LWJGLInitializer.java | 3 + .../client/graphics/flat/LayerTestUI.java | 7 + .../optica/client/graphics/gui/Component.java | 660 ++++++++++++++++++ .../optica/client/graphics/gui/GUILayer.java | 53 ++ .../client/graphics/gui/LayerTestGUI.java | 74 ++ .../optica/client/graphics/gui/Layout.java | 26 + .../optica/client/graphics/gui/Size.java | 29 + .../graphics/gui/event/ChildAddedEvent.java | 28 + .../client/graphics/gui/event/ChildEvent.java | 35 + .../graphics/gui/event/ChildRemovedEvent.java | 28 + .../graphics/gui/event/ComponentEvent.java | 34 + .../client/graphics/gui/event/FocusEvent.java | 35 + .../graphics/gui/event/HierarchyEvent.java | 28 + .../client/graphics/gui/event/HoverEvent.java | 39 ++ .../gui/event/ParentChangedEvent.java | 41 ++ .../graphics/gui/layout/LayoutAlign.java | 91 +++ .../gui/layout/LayoutBorderHorizontal.java | 120 ++++ .../gui/layout/LayoutBorderVertical.java | 120 ++++ .../graphics/gui/layout/LayoutGrid.java | 159 +++++ .../graphics/gui/layout/LayoutHorizontal.java | 90 +++ .../graphics/gui/layout/LayoutVertical.java | 90 +++ .../client/graphics/input/CursorEvent.java | 8 +- .../client/graphics/input/KeyEvent.java | 32 + .../client/graphics/input/KeyMatcher.java | 76 ++ .../optica/client/graphics/input/Keys.java | 148 ++++ .../client/graphics/input/bus/Input.java | 61 ++ .../client/graphics/input/bus/InputBus.java | 88 +++ .../graphics/input/bus/InputListener.java | 27 + .../client/graphics/world/LayerWorld.java | 27 +- 36 files changed, 2395 insertions(+), 43 deletions(-) create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/InputTracker.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/Component.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/GUILayer.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/LayerTestGUI.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/Layout.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/Size.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildAddedEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildRemovedEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/ComponentEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/FocusEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/HierarchyEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/HoverEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/event/ParentChangedEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutAlign.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderHorizontal.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderVertical.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutGrid.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutHorizontal.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutVertical.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/KeyMatcher.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/Keys.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/bus/Input.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputBus.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputListener.java diff --git a/src/main/java/ru/windcorp/optica/client/ClientProxy.java b/src/main/java/ru/windcorp/optica/client/ClientProxy.java index 3cd9a2c..0a1d3cc 100644 --- a/src/main/java/ru/windcorp/optica/client/ClientProxy.java +++ b/src/main/java/ru/windcorp/optica/client/ClientProxy.java @@ -23,6 +23,7 @@ import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue; import ru.windcorp.optica.client.graphics.flat.FlatRenderProgram; import ru.windcorp.optica.client.graphics.flat.LayerTestUI; +import ru.windcorp.optica.client.graphics.gui.LayerTestGUI; import ru.windcorp.optica.client.graphics.world.LayerWorld; import ru.windcorp.optica.client.graphics.world.WorldRenderProgram; @@ -41,6 +42,7 @@ public class ClientProxy implements Proxy { GUI.addBottomLayer(new LayerWorld()); GUI.addTopLayer(new LayerTestUI()); + GUI.addTopLayer(new LayerTestGUI()); } } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/GUI.java b/src/main/java/ru/windcorp/optica/client/graphics/GUI.java index 75b6a2d..73b1507 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/GUI.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/GUI.java @@ -22,12 +22,26 @@ import java.util.List; import com.google.common.eventbus.Subscribe; +import ru.windcorp.optica.client.graphics.input.CursorEvent; import ru.windcorp.optica.client.graphics.input.FrameResizeEvent; +import ru.windcorp.optica.client.graphics.input.InputEvent; +import ru.windcorp.optica.client.graphics.input.KeyEvent; +import ru.windcorp.optica.client.graphics.input.WheelEvent; +import ru.windcorp.optica.client.graphics.input.bus.Input; public class GUI { private static final List LAYERS = 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() {} public synchronized static void addBottomLayer(Layer layer) { @@ -51,6 +65,29 @@ public class GUI { public static void invalidateEverything() { 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; + } + } 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() { @@ -60,6 +97,11 @@ public class GUI { GUI.invalidateEverything(); } + @Subscribe + public void onInput(InputEvent event) { + dispatchInputEvent(event); + } + }; } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/Layer.java b/src/main/java/ru/windcorp/optica/client/graphics/Layer.java index 7d017e1..000fa37 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/Layer.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/Layer.java @@ -20,6 +20,7 @@ package ru.windcorp.optica.client.graphics; import java.util.concurrent.atomic.AtomicBoolean; import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; +import ru.windcorp.optica.client.graphics.input.bus.Input; public abstract class Layer { @@ -39,6 +40,8 @@ public abstract class Layer { } void render() { + GraphicsInterface.startNextLayer(); + validate(); if (!hasInitialized) { @@ -65,6 +68,8 @@ public abstract class Layer { protected abstract void doRender(); + protected abstract void handleInput(Input input); + protected int getWidth() { return GraphicsInterface.getFrameWidth(); } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java index 5aa8aab..14868f5 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java @@ -106,4 +106,8 @@ public class GraphicsBackend { return framesRendered; } + public static void startNextLayer() { + glClear(GL_DEPTH_BUFFER_BIT); + } + } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java index 94a69f7..d578a9c 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java @@ -17,7 +17,6 @@ *******************************************************************************/ package ru.windcorp.optica.client.graphics.backend; -import glm.vec._2.d.Vec2d; import glm.vec._2.i.Vec2i; public class GraphicsInterface { @@ -64,16 +63,8 @@ public class GraphicsInterface { InputHandler.register(listener); } - public static double getCursorX() { - return InputHandler.getCursorX(); - } - - public static double getCursorY() { - return InputHandler.getCursorY(); - } - - public static Vec2d getCursorPosition() { - return InputHandler.getCursorPosition(); + public static void startNextLayer() { + GraphicsBackend.startNextLayer(); } } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java index 87912a6..7850bba 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java @@ -17,9 +17,10 @@ *******************************************************************************/ package ru.windcorp.optica.client.graphics.backend; +import org.lwjgl.glfw.GLFW; + import com.google.common.eventbus.EventBus; -import glm.vec._2.d.Vec2d; import ru.windcorp.optica.client.graphics.input.*; public class InputHandler { @@ -57,6 +58,24 @@ public class InputHandler { if (GraphicsBackend.getWindowHandle() != window) return; THE_KEY_EVENT.initialize(key, scancode, action, mods); dispatch(THE_KEY_EVENT); + + switch (action) { + case GLFW.GLFW_PRESS: + InputTracker.setKeyState(key, true); + break; + case GLFW.GLFW_RELEASE: + InputTracker.setKeyState(key, false); + break; + } + } + + static void handleMouseButtonInput( + long window, + int key, + int action, + int mods + ) { + handleKeyInput(window, key, Integer.MAX_VALUE - key, action, mods); } // CursorMoveEvent @@ -74,10 +93,6 @@ public class InputHandler { } - private static final Vec2d CURSOR_POSITION = new Vec2d( - Double.NaN, Double.NaN - ); - private static final ModifiableCursorMoveEvent THE_CURSOR_MOVE_EVENT = new ModifiableCursorMoveEvent(); @@ -87,26 +102,12 @@ public class InputHandler { ) { if (GraphicsBackend.getWindowHandle() != window) return; - if (Double.isNaN(CURSOR_POSITION.x)) { - CURSOR_POSITION.set(x, y); - } + InputTracker.initializeCursorPosition(x, y); THE_CURSOR_MOVE_EVENT.initialize(x, y); dispatch(THE_CURSOR_MOVE_EVENT); - CURSOR_POSITION.set(x, y); - } - - public static double getCursorX() { - return CURSOR_POSITION.x; - } - - public static double getCursorY() { - return CURSOR_POSITION.y; - } - - public static Vec2d getCursorPosition() { - return CURSOR_POSITION; + InputTracker.getCursorPosition().set(x, y); } // ScrollEvent diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/InputTracker.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/InputTracker.java new file mode 100644 index 0000000..060a526 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/InputTracker.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import glm.vec._2.d.Vec2d; +import gnu.trove.set.TIntSet; +import gnu.trove.set.hash.TIntHashSet; + +public class InputTracker { + + private static final Vec2d CURSOR_POSITION = new Vec2d( + Double.NaN, Double.NaN + ); + + private static final TIntSet PRESSED_KEYS = new TIntHashSet(256); + + private InputTracker() {} + + public static double getCursorX() { + return CURSOR_POSITION.x; + } + + public static double getCursorY() { + return CURSOR_POSITION.y; + } + + public static Vec2d getCursorPosition() { + return CURSOR_POSITION; + } + + static void initializeCursorPosition(double x, double y) { + if (Double.isNaN(CURSOR_POSITION.x)) { + CURSOR_POSITION.set(x, y); + } + } + + public static boolean isKeyPressed(int glfwCode) { + return PRESSED_KEYS.contains(glfwCode); + } + + static void setKeyState(int glfwCode, boolean isPressed) { + if (isPressed) { + PRESSED_KEYS.add(glfwCode); + } else { + PRESSED_KEYS.remove(glfwCode); + } + } + + public static TIntSet getPressedKeys() { + return PRESSED_KEYS; + } + + static void releaseEverything() { + PRESSED_KEYS.clear(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java index 8ecede8..fdbd9c6 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java @@ -93,6 +93,9 @@ class LWJGLInitializer { GraphicsBackend::onFrameResized); glfwSetKeyCallback(handle, InputHandler::handleKeyInput); + glfwSetMouseButtonCallback(handle, + InputHandler::handleMouseButtonInput); + glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput); glfwSetScrollCallback(handle, InputHandler::handleWheelScroll); diff --git a/src/main/java/ru/windcorp/optica/client/graphics/flat/LayerTestUI.java b/src/main/java/ru/windcorp/optica/client/graphics/flat/LayerTestUI.java index cb08fab..bd9541e 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/flat/LayerTestUI.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/flat/LayerTestUI.java @@ -25,6 +25,7 @@ import glm.mat._4.Mat4; import ru.windcorp.optica.client.graphics.Colors; import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; import ru.windcorp.optica.client.graphics.input.KeyEvent; +import ru.windcorp.optica.client.graphics.input.bus.Input; import ru.windcorp.optica.client.graphics.model.LambdaModel; import ru.windcorp.optica.client.graphics.texture.SimpleTexture; import ru.windcorp.optica.client.graphics.texture.Sprite; @@ -125,6 +126,12 @@ public class LayerTestUI extends AssembledFlatLayer { fillColor ); } + + @Override + protected void handleInput(Input input) { + // TODO Auto-generated method stub + + } @Subscribe public void onKeyEvent(KeyEvent event) { diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/Component.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/Component.java new file mode 100644 index 0000000..3ee94db --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/Component.java @@ -0,0 +1,660 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.lwjgl.glfw.GLFW; + +import com.google.common.eventbus.EventBus; + +import ru.windcorp.optica.client.graphics.backend.InputTracker; +import ru.windcorp.optica.client.graphics.flat.RenderTarget; +import ru.windcorp.optica.client.graphics.gui.event.ChildAddedEvent; +import ru.windcorp.optica.client.graphics.gui.event.ChildRemovedEvent; +import ru.windcorp.optica.client.graphics.gui.event.FocusEvent; +import ru.windcorp.optica.client.graphics.gui.event.HoverEvent; +import ru.windcorp.optica.client.graphics.gui.event.ParentChangedEvent; +import ru.windcorp.optica.client.graphics.input.InputEvent; +import ru.windcorp.optica.client.graphics.input.KeyEvent; +import ru.windcorp.optica.client.graphics.input.bus.Input; +import ru.windcorp.optica.client.graphics.input.bus.InputBus; +import ru.windcorp.optica.client.graphics.input.bus.InputListener; +import ru.windcorp.optica.common.util.Named; + +public class Component extends Named { + + private final List children = + Collections.synchronizedList(new CopyOnWriteArrayList<>()); + + private Component parent = null; + + private EventBus eventBus = null; + private InputBus inputBus = null; + + private int x, y; + private int width, height; + + private boolean valid = false; + + private Size preferredSize = null; + + private Object layoutHint = null; + private Layout layout = null; + + private boolean isFocusable = false; + private boolean isFocused = false; + + private boolean isHovered = false; + + public Component(String name) { + super(name); + } + + public Component getParent() { + return parent; + } + + protected void setParent(Component parent) { + if (this.parent != parent) { + Component previousParent = this.parent; + this.parent = parent; + + dispatchEvent(new ParentChangedEvent(this, previousParent, parent)); + } + } + + public List getChildren() { + return children; + } + + public Component getChild(int index) { + synchronized (getChildren()) { + if (index < 0 || index >= getChildren().size()) return null; + return getChildren().get(index); + } + } + + public int getChildIndex(Component child) { + return getChildren().indexOf(child); + } + + public int getOwnIndex() { + Component parent = getParent(); + if (parent != null) { + return parent.getChildIndex(this); + } + + return -1; + } + + public void moveChild(Component child, int newIndex) { + if (newIndex == -1) newIndex = getChildren().size() - 1; + + if (getChildren().remove(child)) { + getChildren().add(newIndex, child); + invalidate(); + } + } + + public void moveSelf(int newIndex) { + Component parent = getParent(); + if (parent != null) { + parent.moveChild(this, newIndex); + } + } + + public Component addChild(Component child, int index) { + if (index == -1) index = getChildren().size(); + + invalidate(); + getChildren().add(index, child); + child.setParent(this); + + dispatchEvent(new ChildAddedEvent(this, child)); + + return this; + } + + public Component addChild(Component child) { + return addChild(child, -1); + } + + public Component removeChild(Component child) { + if (!getChildren().contains(child)) { + return this; + } + + if (child.isFocused()) { + child.focusNext(); + } + + invalidate(); + getChildren().remove(child); + child.setParent(null); + + dispatchEvent(new ChildRemovedEvent(this, child)); + + return this; + } + + public synchronized int getX() { + return x; + } + + public synchronized int getY() { + return y; + } + + public synchronized Component setPosition(int x, int y) { + invalidate(); + this.x = x; + this.y = y; + return this; + } + + public synchronized int getWidth() { + return width; + } + + public synchronized int getHeight() { + return height; + } + + public synchronized Component setSize(int width, int height) { + invalidate(); + this.width = width; + this.height = height; + return this; + } + + public Component setSize(Size size) { + return setSize(size.width, size.height); + } + + public synchronized Component setBounds(int x, int y, int width, int height) { + setPosition(x, y); + setSize(width, height); + return this; + } + + public Component setBounds(int x, int y, Size size) { + return setBounds(x, y, size.width, size.height); + } + + public boolean isValid() { + return valid; + } + + public synchronized void invalidate() { + valid = false; + getChildren().forEach(child -> child.invalidate()); + } + + public synchronized void validate() { + Component parent = getParent(); + invalidate(); + + if (parent == null) { + layoutSelf(); + } else { + parent.validate(); + } + } + + protected synchronized void layoutSelf() { + try { + if (getLayout() != null) { + getLayout().layout(this); + } + + getChildren().forEach(child -> { + child.layoutSelf(); + }); + + valid = true; + } catch (Exception e) { + throw new RuntimeException(); + } + } + + public synchronized Size getPreferredSize() { + if (preferredSize != null) { + return preferredSize; + } + + if (getLayout() != null) { + try { + return getLayout().calculatePreferredSize(this); + } catch (Exception e) { + throw new RuntimeException(); + } + } + + return new Size(0, 0); + } + + public synchronized Component setPreferredSize(Size preferredSize) { + this.preferredSize = preferredSize; + return this; + } + + public Component setPreferredSize(int width, int height) { + return setPreferredSize(new Size(width, height)); + } + + public Layout getLayout() { + return layout; + } + + public synchronized Component setLayout(Layout layout) { + invalidate(); + this.layout = layout; + return this; + } + + public Object getLayoutHint() { + return layoutHint; + } + + public Component setLayoutHint(Object hint) { + this.layoutHint = hint; + return this; + } + + public boolean isFocusable() { + return isFocusable; + } + + public Component setFocusable(boolean focusable) { + this.isFocusable = focusable; + return this; + } + + public boolean isFocused() { + return isFocused; + } + + protected synchronized void setFocused(boolean focus) { + if (focus != this.isFocused) { + dispatchEvent(new FocusEvent(this, focus)); + this.isFocused = focus; + } + } + + public Component takeFocus() { + if (isFocused()) { + return this; + } + + Component comp = this; + Component focused = null; + + while (comp != null) { + if ((focused = comp.findFocused()) != null) { + focused.setFocused(false); + setFocused(true); + return this; + } + + comp = comp.getParent(); + } + + setFocused(true); + return this; + } + + public void focusNext() { + Component component = this; + + while (true) { + + component = component.getNextFocusCandidate(true); + if (component == this) { + return; + } + + if (component.isFocusable()) { + setFocused(false); + component.setFocused(true); + return; + } + + } + } + + private Component getNextFocusCandidate(boolean canUseChildren) { + if (canUseChildren) synchronized (getChildren()) { + if (!getChildren().isEmpty()) { + return getChild(0); + } + } + + Component parent = getParent(); + if (parent != null) { + synchronized (parent.getChildren()) { + int ownIndex = parent.getChildIndex(this); + if (ownIndex != parent.getChildren().size() - 1) { + return parent.getChild(ownIndex + 1); + } + } + + return parent.getNextFocusCandidate(false); + } + + return this; + } + + public void focusPrevious() { + Component component = this; + + while (true) { + + component = component.getPreviousFocusCandidate(); + if (component == this) { + return; + } + + if (component.isFocusable()) { + setFocused(false); + component.setFocused(true); + return; + } + + } + } + + private Component getPreviousFocusCandidate() { + Component parent = getParent(); + if (parent != null) { + synchronized (parent.getChildren()) { + int ownIndex = parent.getChildIndex(this); + if (ownIndex != 0) { + return parent.getChild(ownIndex - 1).getLastDeepChild(); + } + } + + return parent; + } + + return getLastDeepChild(); + } + + private Component getLastDeepChild() { + synchronized (getChildren()) { + if (!getChildren().isEmpty()) { + return getChild(getChildren().size() - 1).getLastDeepChild(); + } + + return this; + } + } + + public synchronized Component findFocused() { + if (isFocused()) { + return this; + } + + Component result; + + synchronized (getChildren()) { + for (Component c : getChildren()) { + result = c.findFocused(); + if (result != null) { + return result; + } + } + } + + return null; + } + + public boolean isHovered() { + return isHovered; + } + + protected void setHovered(boolean isHovered) { + if (this.isHovered != isHovered) { + this.isHovered = isHovered; + + if (!isHovered && !getChildren().isEmpty()) { + + getChildren().forEach(child -> { + if (child.isHovered()) { + child.setHovered(false); + return; + } + }); + } + + dispatchEvent(new HoverEvent(this, isHovered)); + } + } + + public void addListener(Object listener) { + if (eventBus == null) { + eventBus = new EventBus(getName()); + } + + eventBus.register(listener); + } + + public void removeListener(Object listener) { + if (eventBus == null) return; + eventBus.unregister(listener); + } + + public void dispatchEvent(Object event) { + if (eventBus == null) return; + eventBus.post(event); + } + + public void addListener( + Class type, + boolean handlesConsumed, + InputListener listener + ) { + if (inputBus == null) { + inputBus = new InputBus(); + } + + inputBus.register(type, handlesConsumed, listener); + } + + public void addListener( + Class type, + InputListener listener + ) { + if (inputBus == null) { + inputBus = new InputBus(); + } + + inputBus.register(type, listener); + } + + public void removeListener(InputListener listener) { + if (inputBus != null) { + inputBus.unregister(listener); + } + } + + protected void handleInput(Input input) { + if (inputBus != null) { + 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 new RuntimeException(); + } + } + + 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(); + } else { + handleReassemblyRequest(); + } + } + + protected void handleReassemblyRequest() { + // To be overridden + } + + protected synchronized final void assemble(RenderTarget target) { + if (width == 0 || height == 0) { + return; + } + + if (!isValid()) { + validate(); + } + + try { + assembleSelf(target); + } catch (Exception e) { + throw new RuntimeException(); + } + + assembleChildren(target); + + try { + postAssembleSelf(target); + } catch (Exception e) { + throw new RuntimeException(); + } + } + + protected void assembleSelf(RenderTarget target) { + // To be overridden + } + + protected void postAssembleSelf(RenderTarget target) { + // To be overridden + } + + protected void assembleChildren(RenderTarget target) { + getChildren().forEach(child -> child.assemble(target)); + } + +// /** +// * Returns a component that displays this component in its center. +// * @return a {@link Aligner} initialized to center this component +// */ +// public Component center() { +// return new Aligner(this); +// } +// +// /** +// * Returns a component that aligns this component. +// * @return a {@link Aligner} initialized with this component +// */ +// public Component align(double x, double y) { +// return new Aligner(this, x, y); +// } +// +// /** +// * Returns a component that allows scrolling this component +// * @return a {@link Scroller} initialized with this component +// */ +// public Component scroller() { +// return new Scroller(this); +// } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/GUILayer.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/GUILayer.java new file mode 100644 index 0000000..60b3504 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/GUILayer.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui; + +import ru.windcorp.optica.client.graphics.flat.AssembledFlatLayer; +import ru.windcorp.optica.client.graphics.flat.RenderTarget; +import ru.windcorp.optica.client.graphics.input.bus.Input; + +public abstract class GUILayer extends AssembledFlatLayer { + + private final Component root = new Component("Root") { + protected void handleReassemblyRequest() { + GUILayer.this.invalidate(); + } + }; + + public GUILayer(String name, Layout layout) { + super(name); + getRoot().setLayout(layout); + } + + public Component getRoot() { + return root; + } + + @Override + protected void assemble(RenderTarget target) { + getRoot().setBounds(0, 0, getWidth(), getHeight()); + getRoot().invalidate(); + getRoot().assemble(target); + } + + @Override + protected void handleInput(Input input) { + getRoot().dispatchInput(input); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/LayerTestGUI.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/LayerTestGUI.java new file mode 100644 index 0000000..dc6714e --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/LayerTestGUI.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui; + +import com.google.common.eventbus.Subscribe; + +import ru.windcorp.optica.client.graphics.Colors; +import ru.windcorp.optica.client.graphics.flat.RenderTarget; +import ru.windcorp.optica.client.graphics.gui.event.HoverEvent; +import ru.windcorp.optica.client.graphics.gui.layout.LayoutAlign; +import ru.windcorp.optica.client.graphics.input.KeyEvent; + +public class LayerTestGUI extends GUILayer { + + private static class DebugComponent extends Component { + private final int color; + + public DebugComponent(String name, Size size, int color) { + super(name); + this.color = color; + + setPreferredSize(size); + + addListener(new Object() { + @Subscribe + public void onHoverChanged(HoverEvent e) { + requestReassembly(); + } + }); + + addListener(KeyEvent.class, this::onClicked); + } + + private boolean onClicked(KeyEvent event) { + if (event.isPress() && event.isLeftMouseButton()) { + System.out.println("You pressed a Component!"); + } + return true; + } + + @Override + protected void assembleSelf(RenderTarget target) { + target.fill(getX(), getY(), getWidth(), getHeight(), Colors.BLACK); + + target.fill( + getX() + 2, getY() + 2, + getWidth() - 4, getHeight() - 4, + isHovered() ? Colors.DEBUG_YELLOW : color + ); + } + } + + public LayerTestGUI() { + super("LayerTestGui", new LayoutAlign(1, 0.75, 5)); + + getRoot().addChild(new DebugComponent("Alex", new Size(200, 100), 0x44FF44)); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/Layout.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/Layout.java new file mode 100644 index 0000000..d7ad7d1 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/Layout.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui; + +public interface Layout { + + public void layout(Component c); + + public Size calculatePreferredSize(Component c); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/Size.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/Size.java new file mode 100644 index 0000000..ce72e4a --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/Size.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui; + +public class Size { + + public int width, height; + + public Size(int width, int height) { + this.width = width; + this.height = height; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildAddedEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildAddedEvent.java new file mode 100644 index 0000000..185cb7e --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildAddedEvent.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public class ChildAddedEvent extends ChildEvent { + + public ChildAddedEvent(Component component, Component child) { + super(component, child); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildEvent.java new file mode 100644 index 0000000..44ed509 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildEvent.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public abstract class ChildEvent extends HierarchyEvent { + + private final Component child; + + public ChildEvent(Component component, Component child) { + super(component); + this.child = child; + } + + public Component getChild() { + return child; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildRemovedEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildRemovedEvent.java new file mode 100644 index 0000000..90dc073 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ChildRemovedEvent.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public class ChildRemovedEvent extends ChildEvent { + + public ChildRemovedEvent(Component component, Component child) { + super(component, child); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ComponentEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ComponentEvent.java new file mode 100644 index 0000000..ce479a1 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ComponentEvent.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public abstract class ComponentEvent { + + private final Component component; + + public ComponentEvent(Component component) { + this.component = component; + } + + public Component getComponent() { + return component; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/FocusEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/FocusEvent.java new file mode 100644 index 0000000..c8cae1c --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/FocusEvent.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public class FocusEvent extends ComponentEvent { + + private final boolean newState; + + public FocusEvent(Component component, boolean newState) { + super(component); + this.newState = newState; + } + + public boolean getNewState() { + return newState; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/HierarchyEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/HierarchyEvent.java new file mode 100644 index 0000000..53d6247 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/HierarchyEvent.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public abstract class HierarchyEvent extends ComponentEvent { + + public HierarchyEvent(Component component) { + super(component); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/HoverEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/HoverEvent.java new file mode 100644 index 0000000..0ad6d50 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/HoverEvent.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public class HoverEvent extends ComponentEvent { + + private final boolean newState; + + public HoverEvent(Component component, boolean newState) { + super(component); + this.newState = newState; + } + + public boolean isNowHovered() { + return newState; + } + + public boolean wasHovered() { + return !isNowHovered(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ParentChangedEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ParentChangedEvent.java new file mode 100644 index 0000000..8c91fa0 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/event/ParentChangedEvent.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.event; + +import ru.windcorp.optica.client.graphics.gui.Component; + +public class ParentChangedEvent extends HierarchyEvent { + + private final Component previousParent; + private final Component newParent; + + public ParentChangedEvent(Component component, Component previousParent, Component newParent) { + super(component); + this.previousParent = previousParent; + this.newParent = newParent; + } + + public Component getPreviousParent() { + return previousParent; + } + + public Component getNewParent() { + return newParent; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutAlign.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutAlign.java new file mode 100644 index 0000000..7718165 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutAlign.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.layout; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +import ru.windcorp.optica.client.graphics.gui.Component; +import ru.windcorp.optica.client.graphics.gui.Layout; +import ru.windcorp.optica.client.graphics.gui.Size; + +public class LayoutAlign implements Layout { + + private final int margin; + private double alignX, alignY; + + public LayoutAlign(double alignX, double alignY, int margin) { + this.alignX = alignX; + this.alignY = alignY; + this.margin = margin; + } + + public LayoutAlign(int margin) { + this(0.5, 0.5, margin); + } + + public LayoutAlign() { + this(1); + } + + @Override + public void layout(Component c) { + c.getChildren().forEach(child -> { + + Size size = child.getPreferredSize(); + + int cWidth = c.getWidth() - 2 * margin; + int cHeight = c.getHeight() - 2 * margin; + + size.width = min(size.width, cWidth); + size.height = min(size.height, cHeight); + + child.setBounds( + c.getX() + + (int) ((cWidth - size.width) * alignX) + margin, + c.getY() + + (int) ((cHeight - size.height) * alignY) + margin, + size + ); + + }); + } + + @Override + public Size calculatePreferredSize(Component c) { + Size result = new Size(0, 0); + + c.getChildren().stream() + .map(child -> child.getPreferredSize()) + .forEach(size -> { + result.width = max(size.width, result.width); + result.height = max(size.height, result.height); + }); + + result.width += 2 * margin; + result.height += 2 * margin; + + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + margin + ")"; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderHorizontal.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderHorizontal.java new file mode 100644 index 0000000..3b53d63 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderHorizontal.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.layout; + +import static java.lang.Math.max; + +import ru.windcorp.optica.client.graphics.gui.Component; +import ru.windcorp.optica.client.graphics.gui.Layout; +import ru.windcorp.optica.client.graphics.gui.Size; + +public class LayoutBorderHorizontal implements Layout { + + public static final String CENTER = "Center", + LEFT = "Left", + RIGHT = "Right"; + + private final int margin; + + public LayoutBorderHorizontal(int margin) { + this.margin = margin; + } + + public LayoutBorderHorizontal() { + this(1); + } + + @Override + public void layout(Component c) { + int left = 0, right = 0; + + Size childSize; + + synchronized (c.getChildren()) { + for (Component child : c.getChildren()) { + if (child.getLayoutHint() == LEFT) { + childSize = child.getPreferredSize(); + left = childSize.width + margin; + child.setBounds( + c.getX(), + c.getY(), + childSize.width, + c.getHeight()); + } else if (child.getLayoutHint() == RIGHT) { + childSize = child.getPreferredSize(); + right = childSize.width + margin; + child.setBounds( + c.getX() + c.getWidth() - childSize.width, + c.getY(), + childSize.width, + c.getHeight()); + } + } + + for (Component child : c.getChildren()) { + if (child.getLayoutHint() == CENTER) { + child.setBounds( + c.getX() + left, + c.getY(), + c.getWidth() - left - right, + c.getHeight()); + + } + } + } + } + + @Override + public Size calculatePreferredSize(Component c) { + Size result = new Size(0, 0); + int left = 0, right = 0; + + Size childSize; + + synchronized (c.getChildren()) { + for (Component child : c.getChildren()) { + childSize = child.getPreferredSize(); + if (child.getLayoutHint() instanceof String) { + + if (child.getLayoutHint() == LEFT) { + left = max(left, childSize.width + margin); + result.height = max(result.height, childSize.height); + continue; + } else if (child.getLayoutHint() == RIGHT) { + right = max(right, childSize.width + margin); + result.height = max(result.height, childSize.height); + continue; + } + + } + + result.width = max(result.width, childSize.width); + result.height = max(result.height, childSize.height); + } + } + result.width += left + right; + + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + margin + ")"; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderVertical.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderVertical.java new file mode 100644 index 0000000..a19698a --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutBorderVertical.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.layout; + +import static java.lang.Math.max; + +import ru.windcorp.optica.client.graphics.gui.Component; +import ru.windcorp.optica.client.graphics.gui.Layout; +import ru.windcorp.optica.client.graphics.gui.Size; + +public class LayoutBorderVertical implements Layout { + + public static final String + CENTER = "Center", + UP = "Up", + DOWN = "Down"; + + private final int margin; + + public LayoutBorderVertical(int margin) { + this.margin = margin; + } + + public LayoutBorderVertical() { + this(1); + } + + @Override + public void layout(Component c) { + int top = 0, bottom = 0; + + Size childSize; + + synchronized (c.getChildren()) { + for (Component child : c.getChildren()) { + if (child.getLayoutHint() == UP) { + childSize = child.getPreferredSize(); + top = childSize.height + margin; + child.setBounds( + c.getX(), + c.getY(), + c.getWidth(), + childSize.height); + } else if (child.getLayoutHint() == DOWN) { + childSize = child.getPreferredSize(); + bottom = childSize.height + margin; + child.setBounds( + c.getX(), + c.getY() + c.getHeight() - childSize.height, + c.getWidth(), childSize.height); + } + } + + for (Component child : c.getChildren()) { + if (child.getLayoutHint() == CENTER) { + child.setBounds( + c.getX(), + c.getY() + top, + c.getWidth(), + c.getHeight() - top - bottom); + + } + } + } + } + + @Override + public Size calculatePreferredSize(Component c) { + Size result = new Size(0, 0); + int up = 0, down = 0; + + Size childSize; + + synchronized (c.getChildren()) { + for (Component child : c.getChildren()) { + childSize = child.getPreferredSize(); + if (child.getLayoutHint() instanceof String) { + + if (child.getLayoutHint() == UP) { + up = max(up, childSize.height + margin); + result.width = max(result.width, childSize.width); + continue; + } else if (child.getLayoutHint() == DOWN) { + down = max(down, childSize.height + margin); + result.width = max(result.width, childSize.width); + continue; + } + + } + + result.width = max(result.width, childSize.width); + result.height = max(result.height, childSize.height); + } + } + result.height += up + down; + + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + margin + ")"; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutGrid.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutGrid.java new file mode 100644 index 0000000..2b7d799 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutGrid.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.layout; + +import java.util.Arrays; + +import ru.windcorp.optica.client.graphics.gui.Component; +import ru.windcorp.optica.client.graphics.gui.Layout; +import ru.windcorp.optica.client.graphics.gui.Size; + +public class LayoutGrid implements Layout { + + private class GridDimensions { + int[] columns = new int[4]; + int[] rows = new int[10]; + boolean isSummed = false; + + void add(int column, int row, Size size) { + if (isSummed) throw new IllegalStateException("Already summed"); + columns = update(columns, column, size.width); + rows = update(rows, row, size.height); + } + + private int[] update(int[] array, int index, int value) { + if (array.length <= index) { + array = Arrays.copyOf(array, ((index / 10) + 1) * 10); + } + + if (array[index] < value) { + array[index] = value; + } + + return array; + } + + Size getBounds() { + if (isSummed) throw new IllegalStateException("Already summed"); + Size result = new Size(2*margin - gap, 2*margin - gap); + + for (int i = 0; i < columns.length; ++i) { + if (columns[i] != 0) { + result.width += columns[i] + gap; + } + } + + for (int i = 0; i < rows.length; ++i) { + if (rows[i] != 0) { + result.height += rows[i] + gap; + } + } + + return result; + } + + void sum() { + if (isSummed) throw new IllegalStateException("Already summed"); + + int accumulator = margin; + int buffer; + + for (int i = 0; i < columns.length; ++i) { + buffer = columns[i]; + columns[i] = accumulator; + accumulator += buffer + gap; + } + + accumulator = margin; + + for (int i = 0; i < rows.length; ++i) { + buffer = rows[i]; + rows[i] = accumulator; + accumulator += buffer + gap; + } + + isSummed = true; + } + + void setBounds(int column, int row, Component child, Component parent) { + if (!isSummed) throw new IllegalStateException("Not summed yet"); + + child.setBounds( + parent.getX() + columns[column], + parent.getY() + rows[row], + + (column != (columns.length-1) ? + (columns[column + 1] - columns[column] - gap) : + (parent.getWidth() - margin - columns[column])), + + (row != (rows.length-1) ? + (rows[row + 1] - rows[row] - gap) : + (parent.getHeight() - margin - rows[row])) + ); + } + } + + private int gap, margin; + + public LayoutGrid(int margin, int gap) { + this.margin = margin; + this.gap = gap; + } + + public LayoutGrid(int gap) { + this(gap, gap); + } + + public LayoutGrid() { + this(1); + } + + @Override + public void layout(Component c) { + synchronized (c.getChildren()) { + GridDimensions grid = calculateGrid(c); + grid.sum(); + + int[] coords; + for (Component child : c.getChildren()) { + coords = (int[]) child.getLayoutHint(); + grid.setBounds(coords[0], coords[1], child, c); + } + } + } + + @Override + public Size calculatePreferredSize(Component c) { + synchronized (c.getChildren()) { + return calculateGrid(c).getBounds(); + } + } + + private GridDimensions calculateGrid(Component parent) { + GridDimensions result = new GridDimensions(); + int[] coords; + + for (Component child : parent.getChildren()) { + coords = (int[]) child.getLayoutHint(); + result.add(coords[0], coords[1], child.getPreferredSize()); + } + + return result; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutHorizontal.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutHorizontal.java new file mode 100644 index 0000000..d397f24 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutHorizontal.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.layout; + +import static java.lang.Math.max; + +import ru.windcorp.optica.client.graphics.gui.Component; +import ru.windcorp.optica.client.graphics.gui.Layout; +import ru.windcorp.optica.client.graphics.gui.Size; + +public class LayoutHorizontal implements Layout { + + private final int margin, gap; + + public LayoutHorizontal(int margin, int gap) { + this.margin = margin; + this.gap = gap; + } + + public LayoutHorizontal(int gap) { + this(gap, gap); + } + + public LayoutHorizontal() { + this(1); + } + + @Override + public void layout(Component c) { + int x = c.getX() + margin, + y = c.getY() + margin; + + int width; + + synchronized (c.getChildren()) { + for (Component child : c.getChildren()) { + + width = child.getPreferredSize().width; + child.setBounds(x, y, width, c.getHeight() - 2 * margin); + x += gap + width; + + } + } + } + + @Override + public Size calculatePreferredSize(Component c) { + Size size = new Size(0, 0); + Size childPreferredSize; + + synchronized (c.getChildren()) { + for (int i = 0; i < c.getChildren().size(); ++i) { + childPreferredSize = c.getChild(i).getPreferredSize(); + + if (i > 0) { + size.width += gap; + } + + size.height = max(size.height, childPreferredSize.height); + size.width += childPreferredSize.width; + } + } + + size.width += 2 * margin; + size.height += 2 * margin; + + return size; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + gap + ", " + margin + ")"; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutVertical.java b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutVertical.java new file mode 100644 index 0000000..a72fbc7 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/gui/layout/LayoutVertical.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.gui.layout; + +import static java.lang.Math.max; + +import ru.windcorp.optica.client.graphics.gui.Component; +import ru.windcorp.optica.client.graphics.gui.Layout; +import ru.windcorp.optica.client.graphics.gui.Size; + +public class LayoutVertical implements Layout { + + private final int margin, gap; + + public LayoutVertical(int margin, int gap) { + this.margin = margin; + this.gap = gap; + } + + public LayoutVertical(int gap) { + this(gap, gap); + } + + public LayoutVertical() { + this(1); + } + + @Override + public void layout(Component c) { + int x = c.getX() + margin, + y = c.getY() + margin; + + int height; + + synchronized (c.getChildren()) { + for (Component child : c.getChildren()) { + + height = child.getPreferredSize().height; + child.setBounds(x, y, c.getWidth() - 2 * margin, height); + y += gap + height; + + } + } + } + + @Override + public Size calculatePreferredSize(Component c) { + Size size = new Size(0, 0); + Size childPreferredSize; + + synchronized (c.getChildren()) { + for (int i = 0; i < c.getChildren().size(); ++i) { + childPreferredSize = c.getChild(i).getPreferredSize(); + + if (i > 0) { + size.height += gap; + } + + size.width = max(size.width, childPreferredSize.width); + size.height += childPreferredSize.height; + } + } + + size.width += 2 * margin; + size.height += 2 * margin; + + return size; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + gap + ", " + margin + ")"; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java index 62f058c..85db36f 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java @@ -18,7 +18,7 @@ package ru.windcorp.optica.client.graphics.input; import glm.vec._2.d.Vec2d; -import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; +import ru.windcorp.optica.client.graphics.backend.InputTracker; public abstract class CursorEvent extends InputEvent { @@ -27,15 +27,15 @@ public abstract class CursorEvent extends InputEvent { } public double getCursorX() { - return GraphicsInterface.getCursorX(); + return InputTracker.getCursorX(); } public double getCursorY() { - return GraphicsInterface.getCursorY(); + return InputTracker.getCursorY(); } public Vec2d getCursorPosition() { - return GraphicsInterface.getCursorPosition(); + return InputTracker.getCursorPosition(); } @Override diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java index 275e1f9..bce6a30 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java @@ -59,11 +59,43 @@ public class KeyEvent extends InputEvent { public boolean isRepeat() { return action == GLFW.GLFW_REPEAT; } + + public boolean isLeftMouseButton() { + return key == GLFW.GLFW_MOUSE_BUTTON_LEFT; + } + + public boolean isRightMouseButton() { + return key == GLFW.GLFW_MOUSE_BUTTON_RIGHT; + } + + public boolean isMiddleMouseButton() { + return key == GLFW.GLFW_MOUSE_BUTTON_MIDDLE; + } + + public boolean isMouse() { + return Keys.isMouse(getKey()); + } public int getMods() { return mods; } + public boolean hasShift() { + return (getMods() & GLFW.GLFW_MOD_SHIFT) != 0; + } + + public boolean hasControl() { + return (getMods() & GLFW.GLFW_MOD_CONTROL) != 0; + } + + public boolean hasAlt() { + return (getMods() & GLFW.GLFW_MOD_ALT) != 0; + } + + public boolean hasSuper() { + return (getMods() & GLFW.GLFW_MOD_SUPER) != 0; + } + @Override public KeyEvent snapshot() { return new KeyEvent(key, scancode, action, mods, getTime()); diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/KeyMatcher.java b/src/main/java/ru/windcorp/optica/client/graphics/input/KeyMatcher.java new file mode 100644 index 0000000..2a387cb --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/KeyMatcher.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input; + +import gnu.trove.set.TIntSet; +import ru.windcorp.optica.client.graphics.backend.InputTracker; + +public class KeyMatcher { + + private final int key; + private final int[] additionalKeys; + private final int mods; + + public KeyMatcher(int key, int[] additionalKeys, int mods) { + this.key = key; + this.additionalKeys = additionalKeys; + this.mods = mods; + } + + public KeyMatcher(KeyEvent template, int... additionalKeys) { + this(template.getKey(), additionalKeys, template.getMods()); + } + + public static KeyMatcher createKeyMatcher(KeyEvent template) { + return new KeyMatcher( + template, + InputTracker.getPressedKeys().toArray() + ); + } + + public boolean matches(KeyEvent event) { + if (!event.isPress()) return false; + if (event.getKey() != getKey()) return false; + if (event.getMods() != getMods()) return false; + + TIntSet pressedKeys = InputTracker.getPressedKeys(); + + if (pressedKeys.size() != additionalKeys.length) return false; + + for (int additionalKey : additionalKeys) { + if (!pressedKeys.contains(additionalKey)) { + return false; + } + } + + return true; + } + + public int getKey() { + return key; + } + + public int[] getAdditionalKeys() { + return additionalKeys; + } + + public int getMods() { + return mods; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/Keys.java b/src/main/java/ru/windcorp/optica/client/graphics/input/Keys.java new file mode 100644 index 0000000..26ea944 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/Keys.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.lwjgl.glfw.GLFW; + +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import gnu.trove.set.TIntSet; +import gnu.trove.set.hash.TIntHashSet; + +public class Keys { + + private static final TIntObjectMap CODES_TO_NAMES = + new TIntObjectHashMap<>(); + + private static final TObjectIntMap NAMES_TO_CODES = + new TObjectIntHashMap<>(); + + private static final TIntSet MOUSE_BUTTONS = new TIntHashSet(); + + private static final String KEY_PREFIX = "GLFW_KEY_"; + private static final String MOUSE_BUTTON_PREFIX = "GLFW_MOUSE_BUTTON_"; + + private static final Set IGNORE_FIELDS = + new HashSet<>(Arrays.asList( + "GLFW_KEY_UNKNOWN", + "GLFW_KEY_LAST", + "GLFW_MOUSE_BUTTON_LAST", + "GLFW_MOUSE_BUTTON_1", // Alias for LEFT + "GLFW_MOUSE_BUTTON_2", // Alias for RIGHT + "GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE + )); + + static { + initializeDictionary(); + } + + private static void initializeDictionary() { + try { + + for (Field field : GLFW.class.getFields()) { + if (!Modifier.isStatic(field.getModifiers())) continue; + if (!Modifier.isFinal(field.getModifiers())) continue; + + String name = field.getName(); + + if ( + !name.startsWith(KEY_PREFIX) && + !name.startsWith(MOUSE_BUTTON_PREFIX) + ) continue; + + if (IGNORE_FIELDS.contains(name)) continue; + + addToDictionary(field); + } + + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static void addToDictionary(Field field) + throws IllegalAccessException { + + int value = field.getInt(null); + String name = field.getName(); + + if (name.startsWith(KEY_PREFIX)) { + name = name.substring(KEY_PREFIX.length()); + } else if (name.startsWith(MOUSE_BUTTON_PREFIX)) { + name = "MOUSE_" + name.substring(MOUSE_BUTTON_PREFIX.length()); + MOUSE_BUTTONS.add(value); + } + + if (CODES_TO_NAMES.containsKey(value)) { + throw new RuntimeException( + "Duplicate keys: " + CODES_TO_NAMES.get(value) + + " and " + name + " both map to " + value + + "(0x" + Integer.toHexString(value) + ")" + ); + } + + CODES_TO_NAMES.put(value, name); + NAMES_TO_CODES.put(name, value); + } + + public static String getInternalName(int code) { + String result = CODES_TO_NAMES.get(code); + + if (result == null) { + return "UNKNOWN"; + } + + return result; + } + + public static String getDisplayName(int code) { + String name = getInternalName(code); + + if (name.startsWith("KP_")) { + name = "KEYPAD_" + name.substring("KP_".length()); + } + + name = Character.toTitleCase(name.charAt(0)) + + name.substring(1).toLowerCase(); + + name = name.replace('_', ' '); + + return name; + } + + public static int getCode(String internalName) { + if (NAMES_TO_CODES.containsKey(internalName)) { + return -1; + } else { + return NAMES_TO_CODES.get(internalName); + } + } + + public static boolean isMouse(int code) { + return MOUSE_BUTTONS.contains(code); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/bus/Input.java b/src/main/java/ru/windcorp/optica/client/graphics/input/bus/Input.java new file mode 100644 index 0000000..55c7118 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/bus/Input.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input.bus; + +import ru.windcorp.optica.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; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputBus.java b/src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputBus.java new file mode 100644 index 0000000..b3446e1 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputBus.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input.bus; + +import java.util.ArrayList; +import java.util.Collection; + +import ru.windcorp.optica.client.graphics.input.InputEvent; + +public class InputBus { + + private static class WrappedListener { + + private final Class type; + private final boolean handleConsumed; + private final InputListener listener; + + public WrappedListener( + Class type, + boolean handleConsumed, + InputListener listener + ) { + this.type = type; + this.handleConsumed = handleConsumed; + this.listener = listener; + } + + private boolean handles(Input input) { + return + (!input.isConsumed() || handleConsumed) && + type.isInstance(input.getEvent()); + } + + @SuppressWarnings("unchecked") + public void handle(Input input) { + if (handles(input)) { + boolean consumed = ((InputListener) listener) + .handle( + (InputEvent) type.cast(input.getEvent()) + ); + + input.setConsumed(consumed); + } + } + + } + + private final Collection listeners = new ArrayList<>(4); + + public void dispatch(Input input) { + listeners.forEach(l -> l.handle(input)); + } + + public void register( + Class type, + boolean handlesConsumed, + InputListener listener + ) { + listeners.add(new WrappedListener(type, handlesConsumed, listener)); + } + + public void register( + Class type, + InputListener listener + ) { + register(type, false, listener); + } + + public void unregister(InputListener listener) { + listeners.removeIf(l -> l.listener == listener); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputListener.java b/src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputListener.java new file mode 100644 index 0000000..c14c2cd --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/bus/InputListener.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * 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 . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input.bus; + +import ru.windcorp.optica.client.graphics.input.InputEvent; + +@FunctionalInterface +public interface InputListener { + + boolean handle(T event); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java index 79575f9..fecbe81 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java @@ -19,15 +19,15 @@ package ru.windcorp.optica.client.graphics.world; import org.lwjgl.glfw.GLFW; -import com.google.common.eventbus.Subscribe; - import glm.mat._3.Mat3; import glm.vec._3.Vec3; import ru.windcorp.optica.client.graphics.Layer; import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; import ru.windcorp.optica.client.graphics.input.CursorMoveEvent; +import ru.windcorp.optica.client.graphics.input.InputEvent; import ru.windcorp.optica.client.graphics.input.KeyEvent; +import ru.windcorp.optica.client.graphics.input.bus.Input; import ru.windcorp.optica.client.world.WorldRender; import ru.windcorp.optica.common.block.BlockData; import ru.windcorp.optica.common.block.BlockDataRegistry; @@ -67,7 +67,7 @@ public class LayerWorld extends Layer { @Override protected void initialize() { // TODO Auto-generated method stub - GraphicsInterface.subscribeToInputEvents(this); + } @Override @@ -113,14 +113,28 @@ public class LayerWorld extends Layer { world.render(helper); } + @Override + protected void handleInput(Input input) { + if (input.isConsumed()) return; + + InputEvent event = input.getEvent(); + + if (event instanceof KeyEvent) { + onKeyEvent((KeyEvent) event); + input.consume(); + } else if (event instanceof CursorMoveEvent) { + onMouseMoved((CursorMoveEvent) event); + input.consume(); + } + } + public Camera getCamera() { return camera; } private boolean flag = true; - @Subscribe - public void onKeyEvent(KeyEvent event) { + private void onKeyEvent(KeyEvent event) { if (event.isRepeat()) return; int multiplier = event.isPress() ? 1 : -1; @@ -172,8 +186,7 @@ public class LayerWorld extends Layer { } } - @Subscribe - public void onMouseMoved(CursorMoveEvent event) { + private void onMouseMoved(CursorMoveEvent event) { if (!flag) return; final float yawScale = 0.002f;