hadean/src/main/java/xyz/valnet/engine/graphics/ImmediateUI.java

412 lines
9.5 KiB
Java
Raw Normal View History

2023-01-15 04:11:17 -05:00
package xyz.valnet.engine.graphics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
2023-01-27 09:56:56 -05:00
import xyz.valnet.engine.math.Box;
2023-01-15 04:11:17 -05:00
import xyz.valnet.engine.math.Vector4f;
import xyz.valnet.engine.math.Vector4i;
import xyz.valnet.engine.scenegraph.GameObject;
import xyz.valnet.engine.scenegraph.IMouseCaptureArea;
import xyz.valnet.hadean.input.Button;
import xyz.valnet.hadean.util.Assets;
import xyz.valnet.hadean.util.Layers;
public abstract class ImmediateUI extends GameObject implements IMouseCaptureArea {
@SuppressWarnings("unused")
2023-01-15 04:11:17 -05:00
private boolean active;
@SuppressWarnings("unused")
2023-01-15 04:11:17 -05:00
private boolean mouseDown;
public void render() {
begin();
gui();
end();
}
@Override
public void mouseEnter() {
active = true;
}
@Override
public void mouseLeave() {
active = false;
}
@Override
public void mouseDown(int button) {
if(button == 0) mouseDown = true;
}
@Override
public void mouseUp(int button) {
if(button == 0) mouseDown = false;
}
@Override
public float getLayer() {
return Layers.GENERAL_UI_INTERACTABLE;
}
protected abstract void gui();
private record StackingContext(
boolean fixedSize,
2023-01-27 09:56:56 -05:00
Box box,
Box occlusionBox,
2023-01-18 08:20:27 -05:00
boolean hasRegisteredGuiArea,
boolean horizontal
2023-01-15 04:11:17 -05:00
// layout manager?
2023-01-18 08:20:27 -05:00
) {
public boolean vertical() {
return !horizontal;
}
}
2023-01-15 04:11:17 -05:00
private transient StackingContext context;
private transient Stack<StackingContext> contextStack = new Stack<StackingContext>();;
2023-01-15 04:11:17 -05:00
private transient Map<String, Button> buttons = new HashMap<String, Button>();
private transient Set<String> usedButtonId = new HashSet<String>();
2023-01-15 04:11:17 -05:00
private transient Map<Button, Integer> clicks = new HashMap<Button, Integer>();
private transient int buttonCount;
2023-01-15 04:11:17 -05:00
private String genButtonId() {
buttonCount ++;
return "DefaultButton-" + buttonCount;
}
private boolean hasButton(String id) {
usedButtonId.add(id);
return buttons.containsKey(id);
}
private Button getButton(String id) {
if(hasButton(id)) return buttons.get(id);
System.out.println("Created new Button");
Button btn = new Button(Assets.uiFrame, "", 0, 0, 0, 0, 0);
btn.registerClickListener((target) -> {
if(!clicks.containsKey(target)) clicks.put(target, 0);
clicks.put(target, clicks.get(target) + 1);
});
buttons.put(id, add(btn));
return btn;
}
private boolean getClick(Button btn) {
if(clicks.containsKey(btn)) {
int clickCount = clicks.get(btn);
if(clickCount > 0) {
clicks.put(btn, clickCount - 1);
return true;
}
}
return false;
}
2023-01-27 09:56:56 -05:00
private void modifyBox(float x, float y, float w, float h) {
context = new StackingContext(
context.fixedSize,
new Box(context.box.x + x, context.box.y + y, context.box.w + w, context.box.h + h),
context.occlusionBox,
context.hasRegisteredGuiArea,
context.horizontal
);
}
2023-01-18 08:20:27 -05:00
private void adjustBox(float w, float h) {
if(context.vertical()) {
if(context.fixedSize) {
2023-01-27 09:56:56 -05:00
modifyBox(0, h, 0, -h);
2023-01-18 08:20:27 -05:00
} else {
2023-01-27 09:56:56 -05:00
modifyBox(0, 0, 0, h);
2023-01-18 08:20:27 -05:00
}
2023-01-15 04:11:17 -05:00
} else {
2023-01-18 08:20:27 -05:00
if(context.fixedSize) {
2023-01-27 09:56:56 -05:00
modifyBox(w, 0, -w, 0);
2023-01-18 08:20:27 -05:00
} else {
2023-01-27 09:56:56 -05:00
modifyBox(0, 0, w, 0);
if (h - context.box.h > 0)
modifyBox(0, 0, 0, h - context.box.h);
2023-01-18 08:20:27 -05:00
}
2023-01-15 04:11:17 -05:00
}
}
@Override
2023-01-27 09:56:56 -05:00
public final List<Box> getGuiBoxes() {
return guiAreas;
}
2023-01-27 09:56:56 -05:00
private transient List<Box> guiAreas = new ArrayList<Box>();
2023-01-18 08:20:27 -05:00
@FunctionalInterface
public interface RenderCallback {
public void apply();
}
2023-01-21 17:09:55 -05:00
private float getCurrentLayer() {
return getLayer() + contextStack.size() * 0.001f;
}
private float getPreviousLayer() {
return getLayer() + contextStack.size() * 0.001f;
}
2023-01-15 04:11:17 -05:00
// === ELEMENTS ===
//
2023-01-15 04:11:17 -05:00
protected void begin() {
buttonCount = 0;
usedButtonId.clear();
contextStack.clear();
guiAreas.clear();
}
2023-01-18 08:20:27 -05:00
protected void root(int x, int y, int w, int h, RenderCallback cb) {
root(x, y, w, h);
cb.apply();
rootEnd();
}
protected void window(int x, int y, int w, int h, RenderCallback cb) {
root(x, y, w, h);
fixedFrame(w, h);
pad();
cb.apply();
padEnd();
frameEnd();
rootEnd();
}
protected void root(int x, int y, int w, int h) {
assert context == null : "root can only be a root element";
2023-01-27 09:56:56 -05:00
Box box = new Box(x, y, w, h);
2023-01-18 08:20:27 -05:00
context = new StackingContext(true, box, box, false, false);
}
protected void rootEnd() {
assert contextStack.size() == 0 : "cannot end fixedFrame with un-ended elements inside";
context = null;
}
protected void fixedFrame(int w, int h) {
contextStack.push(context);
context = new StackingContext(
true,
2023-01-27 09:56:56 -05:00
new Box(context.box.x, context.box.y, w, h),
context.occlusionBox.copy(),
2023-01-18 08:20:27 -05:00
true,
context.horizontal
);
}
protected void dynamicFrame() {
contextStack.push(context);
context = new StackingContext(
false,
2023-01-27 09:56:56 -05:00
new Box(context.box.x, context.box.y, context.box.w, 0),
context.occlusionBox.copy(),
2023-01-18 08:20:27 -05:00
true,
context.horizontal
);
}
// TODO this will add _all_ frames, not just root frames to guiareas.
// not a problem, but not efficient. revisit.
protected void frameEnd() {
2023-01-21 17:09:55 -05:00
Drawing.setLayer(getPreviousLayer());
if(!context.fixedSize) {
Assets.uiFrame.draw(context.box);
guiAreas.add(context.box);
} else {
Assets.uiFrame.draw(context.occlusionBox);
guiAreas.add(context.occlusionBox);
}
context = contextStack.pop();
2023-01-15 04:11:17 -05:00
}
protected boolean button(String text) {
return button(genButtonId(), text, false);
}
protected boolean button(String text, boolean expand) {
return button(genButtonId(), text, expand);
2023-01-15 04:11:17 -05:00
}
protected boolean button(String id, String text) {
return button(id, text, false);
}
protected boolean button(String id, String text, boolean expand) {
2023-01-27 09:56:56 -05:00
int h = 32;
if(expand && context.fixedSize) {
2023-01-27 09:56:56 -05:00
h = (int) context.box.h;
}
2023-01-27 09:56:56 -05:00
int w = (int) context.box.w;
2023-01-18 08:20:27 -05:00
if(context.horizontal && !context.fixedSize) {
w = 100;
}
int x = (int) context.box.x;
int y = (int) context.box.y;
if(!context.fixedSize) {
if(context.vertical()) {
2023-01-27 09:56:56 -05:00
y += (int) context.box.h;
2023-01-18 08:20:27 -05:00
} else {
2023-01-27 09:56:56 -05:00
x += (int) context.box.w;
2023-01-18 08:20:27 -05:00
}
}
2023-01-27 09:56:56 -05:00
Box buttonBox = new Box(x, y, w, h);
2023-01-15 04:11:17 -05:00
Button btn = getButton(id);
if(!context.hasRegisteredGuiArea) {
guiAreas.add(buttonBox);
}
2023-01-15 04:11:17 -05:00
btn.setText(text);
2023-01-18 08:20:27 -05:00
btn.setPosition(x, y);
2023-01-27 09:56:56 -05:00
btn.setSize(w, h);
2023-01-21 17:09:55 -05:00
btn.setLayer(getCurrentLayer());
2023-01-15 04:11:17 -05:00
2023-01-27 09:56:56 -05:00
adjustBox(buttonBox.w, buttonBox.h);
2023-01-15 04:11:17 -05:00
return getClick(btn);
}
2023-01-18 08:20:27 -05:00
protected void group(RenderCallback cb) {
group();
cb.apply();
groupEnd();
2023-01-15 04:11:17 -05:00
}
protected void group() {
contextStack.push(context);
context = new StackingContext(false,
2023-01-27 09:56:56 -05:00
new Box(
context.box.x, context.box.y, context.box.w, 0
2023-01-15 04:11:17 -05:00
),
context.occlusionBox.copy(),
2023-01-18 08:20:27 -05:00
context.hasRegisteredGuiArea,
context.horizontal
2023-01-15 04:11:17 -05:00
);
pad();
}
protected void groupEnd() {
padEnd();
2023-01-21 17:09:55 -05:00
Drawing.setLayer(getPreviousLayer());
2023-01-27 09:56:56 -05:00
float h = context.box.h;
2023-01-15 04:11:17 -05:00
Assets.uiFrame.draw(context.box);
context = contextStack.pop();
2023-01-27 09:56:56 -05:00
adjustBox(context.box.w, h);
2023-01-15 04:11:17 -05:00
}
protected void pad() {
contextStack.push(context);
if(context.fixedSize) {
context = new StackingContext(true,
2023-01-27 09:56:56 -05:00
new Box(
context.box.x + 8, context.box.y + 8, context.box.w - 16, context.box.h - 16
2023-01-15 04:11:17 -05:00
),
context.occlusionBox.copy(),
2023-01-18 08:20:27 -05:00
context.hasRegisteredGuiArea,
context.horizontal
2023-01-15 04:11:17 -05:00
);
} else {
context = new StackingContext(false,
2023-01-27 09:56:56 -05:00
new Box(
context.box.x + 8, context.box.y + 8, context.box.w - 16, 0
2023-01-15 04:11:17 -05:00
),
context.occlusionBox.copy(),
2023-01-18 08:20:27 -05:00
context.hasRegisteredGuiArea,
context.horizontal
2023-01-15 04:11:17 -05:00
);
}
}
protected void padEnd() {
2023-01-27 09:56:56 -05:00
float h = context.box.h + 16;
2023-01-15 04:11:17 -05:00
context = contextStack.pop();
2023-01-27 09:56:56 -05:00
adjustBox(context.box.w + 16, h);
2023-01-18 08:20:27 -05:00
}
protected void horizontal(RenderCallback cb) {
contextStack.push(context);
context = new StackingContext(
false,
2023-01-27 09:56:56 -05:00
new Box(context.box.x, context.box.y, 0, 0),
2023-01-18 08:20:27 -05:00
context.occlusionBox,
context.hasRegisteredGuiArea,
true
);
cb.apply();
2023-01-27 09:56:56 -05:00
float w = context.box.w;
float h = context.box.h;
2023-01-18 08:20:27 -05:00
context = contextStack.pop();
adjustBox(w, h);
}
protected void space(int space) {
if(context.horizontal)
adjustBox(space, 1);
else
adjustBox(1, space);
2023-01-15 04:11:17 -05:00
}
protected void end() {
List<String> buttonIdsToRemove = new ArrayList<String>();
for(String id : this.buttons.keySet()) {
if(usedButtonId.contains(id)) continue;
buttonIdsToRemove.add(id);
}
for(String id : buttonIdsToRemove) {
Button btn = buttons.get(id);
remove(btn);
buttons.remove(id);
if(clicks.containsKey(btn)) {
clicks.remove(btn);
}
}
}
protected void text(String text) {
text(text, Assets.font);
}
protected void header(String text) {
text(text, Assets.bigFont);
}
protected void text(String text, Font font) {
Vector4i measured = font.measure(text);
2023-01-21 17:09:55 -05:00
Drawing.setLayer(getCurrentLayer());
2023-01-15 04:11:17 -05:00
2023-01-18 08:20:27 -05:00
int x = (int) context.box.x;
int y = (int) context.box.y;
if(!context.fixedSize) {
if(context.vertical()) {
2023-01-27 09:56:56 -05:00
y += (int) context.box.h;
2023-01-18 08:20:27 -05:00
} else {
2023-01-27 09:56:56 -05:00
x += (int) context.box.w;
2023-01-18 08:20:27 -05:00
}
2023-01-15 04:11:17 -05:00
}
2023-01-18 08:20:27 -05:00
font.drawString(text, x, y);
adjustBox(measured.x, measured.y);
2023-01-15 04:11:17 -05:00
}
}