- 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:
OLEGSHA 2020-08-05 00:16:44 +03:00
parent 4bd5411abb
commit 2abffff843
36 changed files with 2395 additions and 43 deletions

View File

@ -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());
}
}

View File

@ -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) {
@ -52,6 +66,29 @@ public class GUI {
LAYERS.forEach(Layer::invalidate);
}
private static void dispatchInputEvent(InputEvent event) {
Input.Target target;
if (event instanceof KeyEvent) {
if (((KeyEvent) event).isMouse()) {
target = Input.Target.HOVERED;
} else {
target = Input.Target.FOCUSED;
}
} 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);
}
};
}

View File

@ -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();
}

View File

@ -106,4 +106,8 @@ public class GraphicsBackend {
return framesRendered;
}
public static void startNextLayer() {
glClear(GL_DEPTH_BUFFER_BIT);
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -93,6 +93,9 @@ class LWJGLInitializer {
GraphicsBackend::onFrameResized);
glfwSetKeyCallback(handle, InputHandler::handleKeyInput);
glfwSetMouseButtonCallback(handle,
InputHandler::handleMouseButtonInput);
glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput);
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);

View File

@ -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;
@ -126,6 +127,12 @@ public class LayerTestUI extends AssembledFlatLayer {
);
}
@Override
protected void handleInput(Input input) {
// TODO Auto-generated method stub
}
@Subscribe
public void onKeyEvent(KeyEvent event) {
if (event.isRepeat() || event.getKey() != GLFW.GLFW_KEY_LEFT_CONTROL) {

View File

@ -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);
// }
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 + ")";
}
}

View File

@ -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 + ")";
}
}

View File

@ -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 + ")";
}
}

View File

@ -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;
}
}

View File

@ -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 + ")";
}
}

View File

@ -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 + ")";
}
}

View File

@ -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

View File

@ -60,10 +60,42 @@ public class KeyEvent extends InputEvent {
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());

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;