webdisplays/src/main/java/net/montoyo/wd/client/gui/WDScreen.java
2023-11-11 00:06:56 -05:00

411 lines
12 KiB
Java

/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack;
import net.montoyo.wd.client.gui.controls.Container;
import net.montoyo.wd.client.gui.controls.Control;
import net.montoyo.wd.client.gui.controls.Event;
import net.montoyo.wd.client.gui.loading.FillControl;
import net.montoyo.wd.client.gui.loading.GuiLoader;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.server_bound.C2SMessageACQuery;
import net.montoyo.wd.utilities.*;
import net.montoyo.wd.utilities.data.Bounds;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public abstract class WDScreen extends Screen {
public static WDScreen CURRENT_SCREEN = null;
protected final ArrayList<Control> controls = new ArrayList<>();
protected final ArrayList<Control> postDrawList = new ArrayList<>();
private final HashMap<Class<? extends Event>, Method> eventMap = new HashMap<>();
protected boolean quitOnEscape = true;
protected boolean defaultBackground = true;
protected int syncTicks = 40;
private int syncTicksLeft = -1;
public WDScreen(Component component) {
super(component);
Method[] methods = getClass().getMethods();
for(Method m : methods) {
if(m.getAnnotation(GuiSubscribe.class) != null) {
if(!Modifier.isPublic(m.getModifiers()))
throw new RuntimeException("Found non public @GuiSubscribe");
Class<?> params[] = m.getParameterTypes();
if(params.length != 1 || !Event.class.isAssignableFrom(params[0]))
throw new RuntimeException("Invalid parameters for @GuiSubscribe");
eventMap.put((Class<? extends Event>) params[0], m);
}
}
}
protected <T extends Control> T addControl(T ctrl) {
controls.add(ctrl);
return ctrl;
}
public int screen2DisplayX(int x) {
double ret = ((double) x) / ((double) width) * ((double) minecraft.getWindow().getWidth());
return (int) ret;
}
public int screen2DisplayY(int y) {
double ret = ((double) y) / ((double) height) * ((double) minecraft.getWindow().getHeight());
return (int) ret;
}
public int display2ScreenX(int x) {
double ret = ((double) x) / ((double) minecraft.getWindow().getWidth()) * ((double) width);
return (int) ret;
}
public int display2ScreenY(int y) {
double ret = ((double) y) / ((double) minecraft.getWindow().getHeight()) * ((double) height);
return (int) ret;
}
protected void centerControls() {
//Determine bounding box
Bounds bounds = Control.findBounds(controls);
//Translation vector
int diffX = (width - bounds.maxX - bounds.minX) / 2;
int diffY = (height - bounds.maxY - bounds.minY) / 2;
//Translate controls
for(Control ctrl : controls) {
int x = ctrl.getX();
int y = ctrl.getY();
ctrl.setPos(x + diffX, y + diffY);
}
}
@Override
public void render(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(defaultBackground)
renderBackground(poseStack);
RenderSystem.setShaderColor(1.f, 1.f, 1.f, 1.f);
for(Control ctrl: controls)
ctrl.draw(poseStack, mouseX, mouseY, ptt);
for(Control ctrl: postDrawList)
ctrl.postDraw(poseStack, mouseX, mouseY, ptt);
}
@Override
public boolean charTyped(char codePoint, int modifiers) {
boolean typed = false;
for(Control ctrl: controls)
typed = typed || ctrl.keyTyped(codePoint, modifiers);
return typed;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
boolean clicked = false;
Control clickedEl = null;
for(Control ctrl: controls) {
clicked = ctrl.mouseClicked(mouseX, mouseY, button);
if (clicked) {
clickedEl = ctrl;
break; // don't assume the compiler will optimize stuff
}
}
if (clicked) {
for (Control control : controls) {
if (control != clickedEl)
control.unfocus();
}
}
return clicked;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
boolean mouseReleased = false;
for(Control ctrl: controls)
mouseReleased = mouseReleased || ctrl.mouseReleased(mouseX, mouseY, button);
return mouseReleased;
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
boolean dragged = false;
for(Control ctrl: controls)
dragged = dragged || ctrl.mouseClickMove(mouseX, mouseY, button, dragX, dragX);
return dragged;
}
@Override
protected void init() {
CURRENT_SCREEN = this;
// minecraft.keyboardHandler.setSendRepeatsToGui(true);
}
@Override
public void onClose() {
if(syncTicksLeft >= 0) {
sync();
syncTicksLeft = -1;
}
for(Control ctrl : controls)
ctrl.destroy();
// Minecraft.getInstance().keyboardHandler.setSendRepeatsToGui(false);
CURRENT_SCREEN = null;
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
boolean scrolled = false;
for(Control ctrl : controls)
scrolled = scrolled || ctrl.mouseScroll(mouseX, mouseY, delta);
return scrolled;
}
@Override
public void mouseMoved(double mouseX, double mouseY) {
boolean moved = false;
for(Control ctrl : controls)
moved = moved || ctrl.mouseMove(mouseX, mouseY);
super.mouseMoved(mouseX, mouseY);
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
boolean down = false;
for (Control ctrl : controls)
down = down || ctrl.keyDown(keyCode, scanCode, modifiers);
if (this instanceof GuiKeyboard) {
return down;
} else {
return new GuiServer(new Vector3i(), new NameUUIDPair()).keyPressed(keyCode, scanCode, modifiers);
}
}
@Override
public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
boolean up = false;
for(Control ctrl : controls)
up = up || ctrl.keyUp(keyCode, scanCode, modifiers);
return up || super.keyReleased(keyCode, scanCode, modifiers);
}
public Object actionPerformed(Event ev) {
Method m = eventMap.get(ev.getClass());
if(m != null) {
try {
return m.invoke(this, ev);
} catch(IllegalAccessException e) {
Log.errorEx("Access to event %s of screen %s is denied", e, ev.getClass().getSimpleName(), getClass().getSimpleName());
} catch(InvocationTargetException e) {
Log.errorEx("Event %s of screen %s failed", e, ev.getClass().getSimpleName(), getClass().getSimpleName());
}
}
return null;
}
public <T extends Control> T getControlByName(String name) {
for(Control ctrl : controls) {
if(name.equals(ctrl.getName()))
return (T) ctrl;
if(ctrl instanceof Container) {
Control ret = ((Container) ctrl).getByName(name);
if(ret != null)
return (T) ret;
}
}
return null;
}
protected void addLoadCustomVariables(Map<String, Double> vars) {
}
public void loadFrom(ResourceLocation resLoc) {
try {
JsonObject root = GuiLoader.getJson(resLoc);
if(root == null)
throw new RuntimeException("Could not load GUI file " + resLoc.toString());
if(!root.has("controls") || !root.get("controls").isJsonArray())
throw new RuntimeException("In GUI file " + resLoc.toString() + ": missing root 'controls' object.");
HashMap<String, Double> vars = new HashMap<>();
vars.put("width", (double) width);
vars.put("height", (double) height);
vars.put("displayWidth", (double) minecraft.getWindow().getWidth());
vars.put("displayHeight", (double) minecraft.getWindow().getHeight());
addLoadCustomVariables(vars);
JsonArray content = root.get("controls").getAsJsonArray();
for(JsonElement elem: content)
controls.add(GuiLoader.create(new JsonOWrapper(elem.getAsJsonObject(), vars)));
Field[] fields = getClass().getDeclaredFields();
for(Field f: fields) {
f.setAccessible(true);
FillControl fc = f.getAnnotation(FillControl.class);
if(fc != null) {
String name = fc.name().isEmpty() ? f.getName() : fc.name();
Control ctrl = getControlByName(name);
if(ctrl == null) {
if(fc.required())
throw new RuntimeException("In GUI file " + resLoc.toString() + ": missing required control " + name);
continue;
}
if(!f.getType().isAssignableFrom(ctrl.getClass()))
throw new RuntimeException("In GUI file " + resLoc.toString() + ": invalid type for control " + name);
try {
f.set(this, ctrl);
} catch(IllegalAccessException e) {
if(fc.required())
throw new RuntimeException(e);
}
}
}
if(root.has("center") && root.get("center").getAsBoolean())
centerControls();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void resize(Minecraft minecraft, int width, int height) {
for(Control ctrl : controls)
ctrl.destroy();
controls.clear();
super.resize(minecraft, width, height);
}
protected void requestAutocomplete(String beginning, boolean matchExact) {
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageACQuery(beginning, matchExact));
}
public void onAutocompleteResult(NameUUIDPair pairs[]) {
}
public void onAutocompleteFailure() {
}
protected void requestSync() {
syncTicksLeft = syncTicks - 1;
}
protected boolean syncRequested() {
return syncTicksLeft >= 0;
}
protected void abortSync() {
syncTicksLeft = -1;
}
protected void sync() {
}
@Override
public void tick() {
if(syncTicksLeft >= 0) {
if(--syncTicksLeft < 0)
sync();
}
}
public void drawItemStackTooltip(GuiGraphics poseStack, ItemStack is, int x, int y) {
poseStack.renderTooltip(Minecraft.getInstance().font, is, x, y); //Since it's protected...
}
public void drawTooltip(GuiGraphics poseStack, List<String> lines, int x, int y) {
poseStack.renderTooltip(Minecraft.getInstance().font, lines.stream().map(a -> FormattedCharSequence.forward(a, Style.EMPTY)).collect(Collectors.toList()), x, y); //This is also protected...
}
public void requirePostDraw(Control ctrl) {
if(!postDrawList.contains(ctrl))
postDrawList.add(ctrl);
}
@Override
public boolean isPauseScreen() {
return false;
}
public abstract boolean isForBlock(BlockPos bp, BlockSide side);
@Nullable
public String getWikiPageName() {
return null;
}
//Bypass for needing to use Components
}