Ported GUI improvements from opfromthestart/Progressia
These changes were originally implemented by opfromthestart. OLEGSHA then patched a whole bunch of stuff - Components can now be disabled - Added BasicButton - Added Button, Checkbox and RadioButton (with RadioButtonGroup) - Added some new colors to Colors - Pressing Esc in game pops up a menu (WIP) - Fixed text z-ordering - Fixed LayoutGrid Y-direction
This commit is contained in:
parent
531a8c99c3
commit
737b495fc4
@ -34,7 +34,13 @@ public class Colors {
|
|||||||
DEBUG_BLUE = toVector(0xFF0000FF),
|
DEBUG_BLUE = toVector(0xFF0000FF),
|
||||||
DEBUG_CYAN = toVector(0xFF00FFFF),
|
DEBUG_CYAN = toVector(0xFF00FFFF),
|
||||||
DEBUG_MAGENTA = toVector(0xFFFF00FF),
|
DEBUG_MAGENTA = toVector(0xFFFF00FF),
|
||||||
DEBUG_YELLOW = toVector(0xFFFFFF00);
|
DEBUG_YELLOW = toVector(0xFFFFFF00),
|
||||||
|
|
||||||
|
LIGHT_GRAY = toVector(0xFFCBCBD0),
|
||||||
|
BLUE = toVector(0xFF37A2E6),
|
||||||
|
HOVER_BLUE = toVector(0xFFC3E4F7),
|
||||||
|
DISABLED_GRAY = toVector(0xFFE5E5E5),
|
||||||
|
DISABLED_BLUE = toVector(0xFFB2D8ED);
|
||||||
|
|
||||||
public static Vec4 toVector(int argb) {
|
public static Vec4 toVector(int argb) {
|
||||||
return toVector(argb, new Vec4());
|
return toVector(argb, new Vec4());
|
||||||
|
@ -189,13 +189,10 @@ public class RenderTarget {
|
|||||||
|
|
||||||
public void addCustomRenderer(Renderable renderable) {
|
public void addCustomRenderer(Renderable renderable) {
|
||||||
assembleCurrentClipFromFaces();
|
assembleCurrentClipFromFaces();
|
||||||
assembled.add(
|
|
||||||
new Clip(
|
float depth = this.depth--;
|
||||||
maskStack,
|
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
|
||||||
getTransform(),
|
assembled.add(new Clip(maskStack, transform, renderable));
|
||||||
renderable
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addFaceToCurrentClip(Face face) {
|
protected void addFaceToCurrentClip(Face face) {
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
|
||||||
|
public abstract class BasicButton extends Component {
|
||||||
|
|
||||||
|
private final Label label;
|
||||||
|
|
||||||
|
private boolean isPressed = false;
|
||||||
|
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
||||||
|
|
||||||
|
public BasicButton(String name, String label, Font labelFont) {
|
||||||
|
super(name);
|
||||||
|
this.label = new Label(name + ".Label", labelFont, label);
|
||||||
|
|
||||||
|
setLayout(new LayoutAlign(10));
|
||||||
|
addChild(this.label);
|
||||||
|
|
||||||
|
setFocusable(true);
|
||||||
|
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
|
||||||
|
|
||||||
|
// Click triggers
|
||||||
|
addListener(KeyEvent.class, e -> {
|
||||||
|
if (e.isRepeat()) {
|
||||||
|
return false;
|
||||||
|
} else if (
|
||||||
|
e.isLeftMouseButton() ||
|
||||||
|
e.getKey() == GLFW.GLFW_KEY_SPACE ||
|
||||||
|
e.getKey() == GLFW.GLFW_KEY_ENTER
|
||||||
|
) {
|
||||||
|
setPressed(e.isPress());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addListener(new Object() {
|
||||||
|
|
||||||
|
// Release when losing focus
|
||||||
|
@Subscribe
|
||||||
|
public void onFocusChange(FocusEvent e) {
|
||||||
|
if (!e.getNewState()) {
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release when hover ends
|
||||||
|
@Subscribe
|
||||||
|
public void onHoverEnded(HoverEvent e) {
|
||||||
|
if (!e.isNowHovered()) {
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release when disabled
|
||||||
|
@Subscribe
|
||||||
|
public void onDisabled(EnableEvent e) {
|
||||||
|
if (!e.getComponent().isEnabled()) {
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger virtualClick when button is released
|
||||||
|
@Subscribe
|
||||||
|
public void onRelease(ButtonEvent.Release e) {
|
||||||
|
virtualClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicButton(String name, String label) {
|
||||||
|
this(name, label, new Font());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPressed() {
|
||||||
|
return isPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void click() {
|
||||||
|
setPressed(true);
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPressed(boolean isPressed) {
|
||||||
|
if (this.isPressed != isPressed) {
|
||||||
|
this.isPressed = isPressed;
|
||||||
|
|
||||||
|
if (isPressed) {
|
||||||
|
takeFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(ButtonEvent.create(this, this.isPressed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicButton addAction(Consumer<BasicButton> action) {
|
||||||
|
this.actions.add(Objects.requireNonNull(action, "action"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAction(Consumer<BasicButton> action) {
|
||||||
|
return this.actions.remove(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void virtualClick() {
|
||||||
|
this.actions.forEach(action -> {
|
||||||
|
action.accept(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
|
||||||
|
public class Button extends BasicButton {
|
||||||
|
|
||||||
|
public Button(String name, String label, Font labelFont) {
|
||||||
|
super(name, label, labelFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button(String name, String label) {
|
||||||
|
this(name, label, new Font());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
// Border
|
||||||
|
|
||||||
|
Vec4 borderColor;
|
||||||
|
if (isPressed() || isHovered() || isFocused()) {
|
||||||
|
borderColor = Colors.BLUE;
|
||||||
|
} else {
|
||||||
|
borderColor = Colors.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
|
||||||
|
|
||||||
|
// Inside area
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
Vec4 backgroundColor;
|
||||||
|
if (isHovered() && isEnabled()) {
|
||||||
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Colors.WHITE;
|
||||||
|
}
|
||||||
|
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change label font color
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
|
||||||
|
} else {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postAssembleSelf(RenderTarget target) {
|
||||||
|
// Apply disable tint
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||||
|
|
||||||
|
public class Checkbox extends BasicButton {
|
||||||
|
|
||||||
|
private class Tick extends Component {
|
||||||
|
|
||||||
|
public Tick() {
|
||||||
|
super(Checkbox.this.getName() + ".Tick");
|
||||||
|
|
||||||
|
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
|
||||||
|
int size = getPreferredSize().x;
|
||||||
|
int x = getX();
|
||||||
|
int y = getY() + (getHeight() - size) / 2;
|
||||||
|
|
||||||
|
// Border
|
||||||
|
|
||||||
|
Vec4 borderColor;
|
||||||
|
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
|
||||||
|
borderColor = Colors.BLUE;
|
||||||
|
} else {
|
||||||
|
borderColor = Colors.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
target.fill(x, y, size, size, borderColor);
|
||||||
|
|
||||||
|
// Inside area
|
||||||
|
|
||||||
|
if (Checkbox.this.isPressed()) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
Vec4 backgroundColor;
|
||||||
|
if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
|
||||||
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Colors.WHITE;
|
||||||
|
}
|
||||||
|
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Tick"
|
||||||
|
|
||||||
|
if (Checkbox.this.isChecked()) {
|
||||||
|
target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checked;
|
||||||
|
|
||||||
|
public Checkbox(String name, String label, Font labelFont, boolean check) {
|
||||||
|
super(name, label, labelFont);
|
||||||
|
this.checked = check;
|
||||||
|
|
||||||
|
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
|
||||||
|
Component basicChild = getChild(0);
|
||||||
|
|
||||||
|
Panel panel = new Panel(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
||||||
|
removeChild(basicChild);
|
||||||
|
setLayout(new LayoutAlign(0, 0.5f, 10));
|
||||||
|
panel.setLayoutHint(basicChild.getLayoutHint());
|
||||||
|
panel.addChild(new Tick());
|
||||||
|
panel.addChild(basicChild);
|
||||||
|
addChild(panel);
|
||||||
|
|
||||||
|
addAction(b -> switchState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Checkbox(String name, String label, Font labelFont) {
|
||||||
|
this(name, label, labelFont, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Checkbox(String name, String label, boolean check) {
|
||||||
|
this(name, label, new Font(), check);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Checkbox(String name, String label) {
|
||||||
|
this(name, label, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchState() {
|
||||||
|
setChecked(!isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the checked
|
||||||
|
*/
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checked the checked to set
|
||||||
|
*/
|
||||||
|
public void setChecked(boolean checked) {
|
||||||
|
this.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
// Change label font color
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
|
||||||
|
} else {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postAssembleSelf(RenderTarget target) {
|
||||||
|
// Apply disable tint
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,18 +19,23 @@
|
|||||||
package ru.windcorp.progressia.client.graphics.gui;
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
import glm.vec._2.i.Vec2i;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
|
||||||
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
|
||||||
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
|
||||||
@ -62,6 +67,8 @@ public class Component extends Named {
|
|||||||
private Object layoutHint = null;
|
private Object layoutHint = null;
|
||||||
private Layout layout = null;
|
private Layout layout = null;
|
||||||
|
|
||||||
|
private boolean isEnabled = true;
|
||||||
|
|
||||||
private boolean isFocusable = false;
|
private boolean isFocusable = false;
|
||||||
private boolean isFocused = false;
|
private boolean isFocused = false;
|
||||||
|
|
||||||
@ -285,10 +292,31 @@ public class Component extends Named {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this component is focusable. A component needs to be
|
||||||
|
* focusable to become focused. A component that is focusable may not
|
||||||
|
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
|
||||||
|
*
|
||||||
|
* @return {@code true} iff the component is focusable
|
||||||
|
* @see #canGainFocusNow()
|
||||||
|
*/
|
||||||
public boolean isFocusable() {
|
public boolean isFocusable() {
|
||||||
return isFocusable;
|
return isFocusable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this component can become focused at this moment.
|
||||||
|
* <p>
|
||||||
|
* The implementation of this method in {@link Component} considers the
|
||||||
|
* component a focus candidate if it is both focusable and enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} iff the component can receive focus
|
||||||
|
* @see #isFocusable()
|
||||||
|
*/
|
||||||
|
public boolean canGainFocusNow() {
|
||||||
|
return isFocusable() && isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
public Component setFocusable(boolean focusable) {
|
public Component setFocusable(boolean focusable) {
|
||||||
this.isFocusable = focusable;
|
this.isFocusable = focusable;
|
||||||
return this;
|
return this;
|
||||||
@ -337,7 +365,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.isFocusable()) {
|
if (component.canGainFocusNow()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -379,7 +407,7 @@ public class Component extends Named {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.isFocusable()) {
|
if (component.canGainFocusNow()) {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
component.setFocused(true);
|
component.setFocused(true);
|
||||||
return;
|
return;
|
||||||
@ -433,12 +461,51 @@ public class Component extends Named {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables this component. An {@link EnableEvent} is dispatched
|
||||||
|
* if the state changes.
|
||||||
|
*
|
||||||
|
* @param enabled {@code true} to enable the component, {@code false} to
|
||||||
|
* disable the component
|
||||||
|
* @see #setEnabledRecursively(boolean)
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
if (this.isEnabled != enabled) {
|
||||||
|
if (isFocused() && isEnabled()) {
|
||||||
|
focusNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEnabled()) {
|
||||||
|
setHovered(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isEnabled = enabled;
|
||||||
|
dispatchEvent(new EnableEvent(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables this component and all of its children recursively.
|
||||||
|
*
|
||||||
|
* @param enabled {@code true} to enable the components, {@code false} to
|
||||||
|
* disable the components
|
||||||
|
* @see #setEnabled(boolean)
|
||||||
|
*/
|
||||||
|
public void setEnabledRecursively(boolean enabled) {
|
||||||
|
setEnabled(enabled);
|
||||||
|
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isHovered() {
|
public boolean isHovered() {
|
||||||
return isHovered;
|
return isHovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setHovered(boolean isHovered) {
|
protected void setHovered(boolean isHovered) {
|
||||||
if (this.isHovered != isHovered) {
|
if (this.isHovered != isHovered && isEnabled()) {
|
||||||
this.isHovered = isHovered;
|
this.isHovered = isHovered;
|
||||||
|
|
||||||
if (!isHovered && !getChildren().isEmpty()) {
|
if (!isHovered && !getChildren().isEmpty()) {
|
||||||
@ -502,7 +569,7 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handleInput(Input input) {
|
protected void handleInput(Input input) {
|
||||||
if (inputBus != null) {
|
if (inputBus != null && isEnabled()) {
|
||||||
inputBus.dispatch(input);
|
inputBus.dispatch(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,6 +665,17 @@ public class Component extends Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules the reassembly to occur.
|
||||||
|
* <p>
|
||||||
|
* This method is invoked in root components whenever a
|
||||||
|
* {@linkplain #requestReassembly() reassembly request} is made by one of
|
||||||
|
* its children. When creating the dedicated root component, override this
|
||||||
|
* method to perform any implementation-specific actions that will cause a
|
||||||
|
* reassembly as soon as possible.
|
||||||
|
* <p>
|
||||||
|
* The default implementation of this method does nothing.
|
||||||
|
*/
|
||||||
protected void handleReassemblyRequest() {
|
protected void handleReassemblyRequest() {
|
||||||
// To be overridden
|
// To be overridden
|
||||||
}
|
}
|
||||||
@ -638,6 +716,135 @@ public class Component extends Named {
|
|||||||
getChildren().forEach(child -> child.assemble(target));
|
getChildren().forEach(child -> child.assemble(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Automatic Reassembly
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The various kinds of changes that may be used with
|
||||||
|
* {@link Component#reassembleAt(ARTrigger...)}.
|
||||||
|
*/
|
||||||
|
protected static enum ARTrigger {
|
||||||
|
/**
|
||||||
|
* Reassemble the component whenever its hover status changes, e.g.
|
||||||
|
* whenever the pointer enters or leaves its bounds.
|
||||||
|
*/
|
||||||
|
HOVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reassemble the component whenever it gains or loses focus.
|
||||||
|
* <p>
|
||||||
|
* <em>Component must be focusable to be able to gain focus.</em> The
|
||||||
|
* component will not be reassembled unless
|
||||||
|
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
|
||||||
|
* invoked.
|
||||||
|
*/
|
||||||
|
FOCUS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reassemble the component whenever it is enabled or disabled.
|
||||||
|
*/
|
||||||
|
ENABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All trigger objects (event listeners) that are currently registered with
|
||||||
|
* {@link #eventBus}. The field is {@code null} until the first trigger is
|
||||||
|
* installed.
|
||||||
|
*/
|
||||||
|
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
|
||||||
|
|
||||||
|
private Object createTriggerObject(ARTrigger type) {
|
||||||
|
switch (type) {
|
||||||
|
case HOVER:
|
||||||
|
return new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void onHoverChanged(HoverEvent e) {
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case FOCUS:
|
||||||
|
return new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void onFocusChanged(FocusEvent e) {
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case ENABLE:
|
||||||
|
return new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void onEnabled(EnableEvent e) {
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new NullPointerException("type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that {@link #requestReassembly()} is invoked on this component
|
||||||
|
* whenever any of the specified changes occur. Duplicate attempts to
|
||||||
|
* register the same trigger are silently ignored.
|
||||||
|
* <p>
|
||||||
|
* {@code triggers} may be empty, which results in a no-op. It must not be
|
||||||
|
* {@code null}.
|
||||||
|
*
|
||||||
|
* @param triggers the {@linkplain ARTrigger triggers} to
|
||||||
|
* request reassembly with.
|
||||||
|
* @see #disableAutoReassemblyAt(ARTrigger...)
|
||||||
|
*/
|
||||||
|
protected synchronized void reassembleAt(ARTrigger... triggers) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(triggers, "triggers");
|
||||||
|
if (triggers.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (autoReassemblyTriggerObjects == null) {
|
||||||
|
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ARTrigger trigger : triggers) {
|
||||||
|
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
|
||||||
|
Object triggerObject = createTriggerObject(trigger);
|
||||||
|
addListener(trigger);
|
||||||
|
autoReassemblyTriggerObjects.put(trigger, triggerObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that {@link #requestReassembly()} is no longer invoked on this
|
||||||
|
* component whenever any of the specified changes occur. After a trigger is
|
||||||
|
* removed, it may be reinstalled with
|
||||||
|
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
|
||||||
|
* nonexistant trigger are silently ignored.
|
||||||
|
* <p>
|
||||||
|
* {@code triggers} may be empty, which results in a no-op. It must not be
|
||||||
|
* {@code null}.
|
||||||
|
*
|
||||||
|
* @param triggers the {@linkplain ARTrigger triggers} to remove
|
||||||
|
* @see #reassemblyAt(ARTrigger...)
|
||||||
|
*/
|
||||||
|
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(triggers, "triggers");
|
||||||
|
if (triggers.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (autoReassemblyTriggerObjects == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (ARTrigger trigger : triggers) {
|
||||||
|
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
|
||||||
|
if (triggerObject != null) {
|
||||||
|
removeListener(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Returns a component that displays this component in its center.
|
// * Returns a component that displays this component in its center.
|
||||||
// * @return a {@link Aligner} initialized to center this component
|
// * @return a {@link Aligner} initialized to center this component
|
||||||
|
@ -83,6 +83,11 @@ public class Label extends Component {
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFont(Font font) {
|
||||||
|
this.font = font;
|
||||||
|
requestReassembly();
|
||||||
|
}
|
||||||
|
|
||||||
public String getCurrentText() {
|
public String getCurrentText() {
|
||||||
return currentText;
|
return currentText;
|
||||||
}
|
}
|
||||||
@ -96,11 +101,7 @@ public class Label extends Component {
|
|||||||
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
|
||||||
|
|
||||||
target.pushTransform(
|
target.pushTransform(
|
||||||
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf
|
new Mat4().identity().translate(startX, getY(), 0).scale(2)
|
||||||
// is this
|
|
||||||
// magic
|
|
||||||
// <---
|
|
||||||
.scale(2)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
|
import glm.vec._4.Vec4;
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Font;
|
||||||
|
import ru.windcorp.progressia.client.graphics.font.Typefaces;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
|
||||||
|
public class RadioButton extends BasicButton {
|
||||||
|
|
||||||
|
private class Tick extends Component {
|
||||||
|
|
||||||
|
public Tick() {
|
||||||
|
super(RadioButton.this.getName() + ".Tick");
|
||||||
|
|
||||||
|
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
|
||||||
|
target.fill(x + 4, y, size - 8, size, color);
|
||||||
|
target.fill(x + 2, y + 2, size - 4, size - 4, color);
|
||||||
|
target.fill(x, y + 4, size, size - 8, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
|
||||||
|
int size = getPreferredSize().x;
|
||||||
|
int x = getX();
|
||||||
|
int y = getY() + (getHeight() - size) / 2;
|
||||||
|
|
||||||
|
// Border
|
||||||
|
|
||||||
|
Vec4 borderColor;
|
||||||
|
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
|
||||||
|
borderColor = Colors.BLUE;
|
||||||
|
} else {
|
||||||
|
borderColor = Colors.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
cross(target, x, y, size, borderColor);
|
||||||
|
|
||||||
|
// Inside area
|
||||||
|
|
||||||
|
if (RadioButton.this.isPressed()) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
Vec4 backgroundColor;
|
||||||
|
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
|
||||||
|
backgroundColor = Colors.HOVER_BLUE;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Colors.WHITE;
|
||||||
|
}
|
||||||
|
cross(target, x + 2, y + 2, size - 4, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Tick"
|
||||||
|
|
||||||
|
if (RadioButton.this.isChecked()) {
|
||||||
|
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checked;
|
||||||
|
|
||||||
|
private RadioButtonGroup group = null;
|
||||||
|
|
||||||
|
public RadioButton(String name, String label, Font labelFont, boolean check) {
|
||||||
|
super(name, label, labelFont);
|
||||||
|
this.checked = check;
|
||||||
|
|
||||||
|
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
|
||||||
|
Component basicChild = getChild(0);
|
||||||
|
|
||||||
|
Panel panel = new Panel(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
|
||||||
|
removeChild(basicChild);
|
||||||
|
setLayout(new LayoutAlign(0, 0.5f, 10));
|
||||||
|
panel.setLayoutHint(basicChild.getLayoutHint());
|
||||||
|
panel.addChild(new Tick());
|
||||||
|
panel.addChild(basicChild);
|
||||||
|
addChild(panel);
|
||||||
|
|
||||||
|
addListener(KeyEvent.class, e -> {
|
||||||
|
if (e.isRelease()) return false;
|
||||||
|
|
||||||
|
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
|
||||||
|
if (group != null) {
|
||||||
|
group.selectPrevious();
|
||||||
|
group.getSelected().takeFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
|
||||||
|
if (group != null) {
|
||||||
|
group.selectNext();
|
||||||
|
group.getSelected().takeFocus();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
addAction(b -> setChecked(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(String name, String label, Font labelFont) {
|
||||||
|
this(name, label, labelFont, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(String name, String label, boolean check) {
|
||||||
|
this(name, label, new Font(), check);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(String name, String label) {
|
||||||
|
this(name, label, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param group the group to set
|
||||||
|
*/
|
||||||
|
public RadioButton setGroup(RadioButtonGroup group) {
|
||||||
|
|
||||||
|
if (this.group != null) {
|
||||||
|
group.selectNext();
|
||||||
|
removeAction(group.listener);
|
||||||
|
group.buttons.remove(this);
|
||||||
|
group.getSelected(); // Clear reference if this was the only button in the group
|
||||||
|
}
|
||||||
|
|
||||||
|
this.group = group;
|
||||||
|
|
||||||
|
if (this.group != null) {
|
||||||
|
group.buttons.add(this);
|
||||||
|
addAction(group.listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
setChecked(false);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the checked
|
||||||
|
*/
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checked the checked to set
|
||||||
|
*/
|
||||||
|
public void setChecked(boolean checked) {
|
||||||
|
this.checked = checked;
|
||||||
|
|
||||||
|
if (group != null) {
|
||||||
|
group.listener.accept(this); // Failsafe for manual invocations of setChecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
// Change label font color
|
||||||
|
|
||||||
|
if (isPressed()) {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
|
||||||
|
} else {
|
||||||
|
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postAssembleSelf(RenderTarget target) {
|
||||||
|
// Apply disable tint
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class RadioButtonGroup {
|
||||||
|
|
||||||
|
private final Collection<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
|
||||||
|
final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
private RadioButton selected = null;
|
||||||
|
|
||||||
|
Consumer<BasicButton> listener = b -> {
|
||||||
|
if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
|
||||||
|
select((RadioButton) b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
|
||||||
|
this.actions.add(Objects.requireNonNull(action, "action"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAction(Consumer<BasicButton> action) {
|
||||||
|
return this.actions.remove(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RadioButton> getButtons() {
|
||||||
|
return Collections.unmodifiableList(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized RadioButton getSelected() {
|
||||||
|
if (!buttons.contains(selected)) {
|
||||||
|
selected = null;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void select(RadioButton button) {
|
||||||
|
if (button != null && !buttons.contains(button)) {
|
||||||
|
throw new IllegalArgumentException("Button " + button + " is not in the group");
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelected(); // Clear if invalid
|
||||||
|
|
||||||
|
if (selected == button) {
|
||||||
|
return; // Terminate listener-setter recursion
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
selected.setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
selected = button;
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
selected.setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.forEach(action -> action.accept(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectNext() {
|
||||||
|
selectNeighbour(+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectPrevious() {
|
||||||
|
selectNeighbour(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void selectNeighbour(int direction) {
|
||||||
|
if (getSelected() == null) {
|
||||||
|
if (buttons.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Cannot select neighbour button: group empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
select(buttons.get(0));
|
||||||
|
} else {
|
||||||
|
RadioButton button;
|
||||||
|
int index = buttons.indexOf(selected);
|
||||||
|
|
||||||
|
do {
|
||||||
|
index += direction;
|
||||||
|
|
||||||
|
if (index >= buttons.size()) {
|
||||||
|
index = 0;
|
||||||
|
} else if (index < 0) {
|
||||||
|
index = buttons.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button = buttons.get(index);
|
||||||
|
} while (button != getSelected() && !button.isEnabled());
|
||||||
|
|
||||||
|
select(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.BasicButton;
|
||||||
|
|
||||||
|
public class ButtonEvent extends ComponentEvent {
|
||||||
|
|
||||||
|
public static class Press extends ButtonEvent {
|
||||||
|
public Press(BasicButton button) {
|
||||||
|
super(button, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Release extends ButtonEvent {
|
||||||
|
public Release(BasicButton button) {
|
||||||
|
super(button, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean isPress;
|
||||||
|
|
||||||
|
protected ButtonEvent(BasicButton button, boolean isPress) {
|
||||||
|
super(button);
|
||||||
|
this.isPress = isPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ButtonEvent create(BasicButton button, boolean isPress) {
|
||||||
|
if (isPress) {
|
||||||
|
return new Press(button);
|
||||||
|
} else {
|
||||||
|
return new Release(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPress() {
|
||||||
|
return isPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRelease() {
|
||||||
|
return !isPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.windcorp.progressia.client.graphics.gui.event;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Component;
|
||||||
|
|
||||||
|
public class EnableEvent extends ComponentEvent {
|
||||||
|
|
||||||
|
public EnableEvent(Component component) {
|
||||||
|
super(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -98,15 +98,26 @@ public class LayoutGrid implements Layout {
|
|||||||
if (!isSummed)
|
if (!isSummed)
|
||||||
throw new IllegalStateException("Not summed yet");
|
throw new IllegalStateException("Not summed yet");
|
||||||
|
|
||||||
|
int width, height;
|
||||||
|
|
||||||
|
if (column == columns.length - 1) {
|
||||||
|
width = parent.getWidth() - margin - columns[column];
|
||||||
|
} else {
|
||||||
|
width = columns[column + 1] - columns[column] - gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row == rows.length - 1) {
|
||||||
|
height = parent.getHeight() - margin - rows[row];
|
||||||
|
} else {
|
||||||
|
height = rows[row + 1] - rows[row] - gap;
|
||||||
|
}
|
||||||
|
|
||||||
child.setBounds(
|
child.setBounds(
|
||||||
parent.getX() + columns[column],
|
parent.getX() + columns[column],
|
||||||
parent.getY() + rows[row],
|
parent.getY() + parent.getHeight() - (rows[row] + height),
|
||||||
|
|
||||||
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap)
|
width,
|
||||||
: (parent.getWidth() - margin - columns[column])),
|
height
|
||||||
|
|
||||||
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
|
|
||||||
: (parent.getHeight() - margin - rows[row]))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,10 +143,9 @@ public class LayoutGrid implements Layout {
|
|||||||
GridDimensions grid = calculateGrid(c);
|
GridDimensions grid = calculateGrid(c);
|
||||||
grid.sum();
|
grid.sum();
|
||||||
|
|
||||||
int[] coords;
|
|
||||||
for (Component child : c.getChildren()) {
|
for (Component child : c.getChildren()) {
|
||||||
coords = (int[]) child.getLayoutHint();
|
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||||
grid.setBounds(coords[0], coords[1], child, c);
|
grid.setBounds(coords.x, coords.y, child, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,11 +159,10 @@ public class LayoutGrid implements Layout {
|
|||||||
|
|
||||||
private GridDimensions calculateGrid(Component parent) {
|
private GridDimensions calculateGrid(Component parent) {
|
||||||
GridDimensions result = new GridDimensions();
|
GridDimensions result = new GridDimensions();
|
||||||
int[] coords;
|
|
||||||
|
|
||||||
for (Component child : parent.getChildren()) {
|
for (Component child : parent.getChildren()) {
|
||||||
coords = (int[]) child.getLayoutHint();
|
Vec2i coords = (Vec2i) child.getLayoutHint();
|
||||||
result.add(coords[0], coords[1], child.getPreferredSize());
|
result.add(coords.x, coords.y, child.getPreferredSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
104
src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java
Normal file
104
src/main/java/ru/windcorp/progressia/test/LayerButtonTest.java
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Progressia
|
||||||
|
* Copyright (C) 2020-2021 Wind Corporation and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package ru.windcorp.progressia.test;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import ru.windcorp.progressia.client.graphics.Colors;
|
||||||
|
import ru.windcorp.progressia.client.graphics.GUI;
|
||||||
|
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
|
||||||
|
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Button;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Checkbox;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Panel;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.RadioButton;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.RadioButtonGroup;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutBorderHorizontal;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.InputEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||||
|
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||||
|
|
||||||
|
public class LayerButtonTest extends GUILayer {
|
||||||
|
|
||||||
|
public LayerButtonTest() {
|
||||||
|
super("LayerButtonTest", new LayoutBorderHorizontal(0));
|
||||||
|
|
||||||
|
Panel background = new Panel("Background", new LayoutAlign(10)) {
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
target.fill(Colors.toVector(0x88FFFFFF));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Panel panel = new Panel("Panel", new LayoutVertical(10)) {
|
||||||
|
@Override
|
||||||
|
protected void assembleSelf(RenderTarget target) {
|
||||||
|
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.LIGHT_GRAY);
|
||||||
|
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, Colors.WHITE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Button blockableButton;
|
||||||
|
panel.addChild((blockableButton = new Button("BlockableButton", "Blockable")).addAction(b -> {
|
||||||
|
System.out.println("Button Blockable!");
|
||||||
|
}));
|
||||||
|
blockableButton.setEnabled(false);
|
||||||
|
|
||||||
|
panel.addChild(new Checkbox("EnableButton", "Enable").addAction(b -> {
|
||||||
|
blockableButton.setEnabled(((Checkbox) b).isChecked());
|
||||||
|
}));
|
||||||
|
|
||||||
|
RadioButtonGroup group = new RadioButtonGroup().addAction(g -> {
|
||||||
|
System.out.println("RBG! " + g.getSelected().getLabel().getCurrentText());
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.addChild(new RadioButton("RB1", "Moon").setGroup(group));
|
||||||
|
panel.addChild(new RadioButton("RB2", "Type").setGroup(group));
|
||||||
|
panel.addChild(new RadioButton("RB3", "Ice").setGroup(group));
|
||||||
|
panel.addChild(new RadioButton("RB4", "Cream").setGroup(group));
|
||||||
|
|
||||||
|
panel.getChild(panel.getChildren().size() - 1).setEnabled(false);
|
||||||
|
|
||||||
|
panel.getChild(1).takeFocus();
|
||||||
|
|
||||||
|
background.addChild(panel);
|
||||||
|
getRoot().addChild(background.setLayoutHint(LayoutBorderHorizontal.CENTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleInput(Input input) {
|
||||||
|
|
||||||
|
if (!input.isConsumed()) {
|
||||||
|
|
||||||
|
InputEvent e = input.getEvent();
|
||||||
|
|
||||||
|
if ((e instanceof KeyEvent) && ((KeyEvent) e).isPress() && ((KeyEvent) e).getKey() == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
|
GUI.removeLayer(this);
|
||||||
|
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
super.handleInput(input);
|
||||||
|
input.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -184,6 +184,7 @@ public class TestPlayerControls {
|
|||||||
case GLFW.GLFW_KEY_ESCAPE:
|
case GLFW.GLFW_KEY_ESCAPE:
|
||||||
if (!event.isPress())
|
if (!event.isPress())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
handleEscape();
|
handleEscape();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -293,13 +294,20 @@ public class TestPlayerControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleEscape() {
|
private void handleEscape() {
|
||||||
if (captureMouse) {
|
// if (captureMouse) {
|
||||||
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
// GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
||||||
} else {
|
// } else {
|
||||||
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
// GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// captureMouse = !captureMouse;
|
||||||
|
|
||||||
|
movementForward = 0;
|
||||||
|
movementRight = 0;
|
||||||
|
movementUp = 0;
|
||||||
|
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
||||||
|
GUI.addTopLayer(new LayerButtonTest());
|
||||||
|
|
||||||
captureMouse = !captureMouse;
|
|
||||||
updateGUI();
|
updateGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user