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
This commit is contained in:
parent
4bd5411abb
commit
2abffff843
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Layer> 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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -106,4 +106,8 @@ public class GraphicsBackend {
|
||||
return framesRendered;
|
||||
}
|
||||
|
||||
public static void startNextLayer() {
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -93,6 +93,9 @@ class LWJGLInitializer {
|
||||
GraphicsBackend::onFrameResized);
|
||||
|
||||
glfwSetKeyCallback(handle, InputHandler::handleKeyInput);
|
||||
glfwSetMouseButtonCallback(handle,
|
||||
InputHandler::handleMouseButtonInput);
|
||||
|
||||
glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput);
|
||||
|
||||
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
|
||||
|
@ -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) {
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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<Component> 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<Component> 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 <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 addListener(
|
||||
Class<? extends T> type,
|
||||
InputListener<T> 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);
|
||||
// }
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
package ru.windcorp.optica.client.graphics.gui;
|
||||
|
||||
public interface Layout {
|
||||
|
||||
public void layout(Component c);
|
||||
|
||||
public Size calculatePreferredSize(Component c);
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
148
src/main/java/ru/windcorp/optica/client/graphics/input/Keys.java
Normal file
148
src/main/java/ru/windcorp/optica/client/graphics/input/Keys.java
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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<String> CODES_TO_NAMES =
|
||||
new TIntObjectHashMap<>();
|
||||
|
||||
private static final TObjectIntMap<String> 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<String> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
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<InputEvent>) listener)
|
||||
.handle(
|
||||
(InputEvent) type.cast(input.getEvent())
|
||||
);
|
||||
|
||||
input.setConsumed(consumed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
|
||||
|
||||
public void dispatch(Input input) {
|
||||
listeners.forEach(l -> l.handle(input));
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void register(
|
||||
Class<? extends T> type,
|
||||
boolean handlesConsumed,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
listeners.add(new WrappedListener(type, handlesConsumed, listener));
|
||||
}
|
||||
|
||||
public <T extends InputEvent> void register(
|
||||
Class<? extends T> type,
|
||||
InputListener<T> listener
|
||||
) {
|
||||
register(type, false, listener);
|
||||
}
|
||||
|
||||
public void unregister(InputListener<?> listener) {
|
||||
listeners.removeIf(l -> l.listener == listener);
|
||||
}
|
||||
|
||||
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
package ru.windcorp.optica.client.graphics.input.bus;
|
||||
|
||||
import ru.windcorp.optica.client.graphics.input.InputEvent;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface InputListener<T extends InputEvent> {
|
||||
|
||||
boolean handle(T event);
|
||||
|
||||
}
|
@ -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;
|
||||
|
Reference in New Issue
Block a user