From 03f37eda81c75fb8dc032819da3f85c7524f6163 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Mon, 12 Oct 2015 22:04:21 +0200 Subject: [PATCH] Add support for popups to the GUI API --- .../minecraft/gui/popup/AbstractGuiPopup.java | 184 ++++++++++++++++++ .../minecraft/gui/popup/GuiYesNoPopup.java | 118 +++++++++++ .../johni0702/minecraft/gui/utils/Colors.java | 2 + src/main/resources/assets/guiapi/gui.png | Bin 4056 -> 4230 bytes 4 files changed, 304 insertions(+) create mode 100644 src/main/java/de/johni0702/minecraft/gui/popup/AbstractGuiPopup.java create mode 100644 src/main/java/de/johni0702/minecraft/gui/popup/GuiYesNoPopup.java diff --git a/src/main/java/de/johni0702/minecraft/gui/popup/AbstractGuiPopup.java b/src/main/java/de/johni0702/minecraft/gui/popup/AbstractGuiPopup.java new file mode 100644 index 00000000..b3e60b82 --- /dev/null +++ b/src/main/java/de/johni0702/minecraft/gui/popup/AbstractGuiPopup.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2015 johni0702 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package de.johni0702.minecraft.gui.popup; + +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.RenderInfo; +import de.johni0702.minecraft.gui.container.AbstractGuiContainer; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.Layout; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractGuiPopup> extends AbstractGuiContainer { + private final GuiPanel popupContainer = new GuiPanel(this){ + private final int u0 = 0; + private final int v0 = 39; + + @Override + public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { + renderer.bindTexture(TEXTURE); + int w = size.getWidth(); + int h = size.getHeight(); + + // Corners + renderer.drawTexturedRect(0, 0, u0, v0, 5, 5); // Top left + renderer.drawTexturedRect(w - 5, 0, u0 + 12, v0, 5, 5); // Top right + renderer.drawTexturedRect(0, h - 5, u0, v0 + 12, 5, 5); // Bottom left + renderer.drawTexturedRect(w - 5, h - 5, u0 + 12, v0 + 12, 5, 5); // Bottom right + + // Top and bottom edge + for (int x = 5; x < w - 5; x += 5) { + int rx = Math.min(5, w - 5 - x); + renderer.drawTexturedRect(x, 0, u0 + 6, v0, rx, 5); // Top + renderer.drawTexturedRect(x, h - 5, u0 + 6, v0 + 12, rx, 5); // Bottom + } + + // Left and right edge + for (int y = 5; y < w - 5; y += 5) { + int ry = Math.min(5, h - 5 - y); + renderer.drawTexturedRect(0, y, u0, v0 + 6, 5, ry); // Left + renderer.drawTexturedRect(w - 5, y, u0 + 12, v0 + 6, 5, ry); // Right + } + + // Center + for (int x = 5; x < w - 5; x += 5) { + for (int y = 5; y < w - 5; y += 5) { + int rx = Math.min(5, w - 5 - x); + int ry = Math.min(5, h - 5 - y); + renderer.drawTexturedRect(x, y, u0 + 6, v0 + 6, rx, ry); + } + } + + super.draw(renderer, size, renderInfo); + } + }.setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + pos(popup, 10, 10); + } + + @Override + public ReadableDimension calcMinSize(GuiContainer container) { + ReadableDimension size = popup.calcMinSize(); + return new Dimension(size.getWidth() + 20, size.getHeight() + 20); + } + }); + protected final GuiPanel popup = new GuiPanel(popupContainer); + + private int layer; + + { + setLayout(new CustomLayout() { + @Override + protected void layout(T container, int width, int height) { + pos(popupContainer, width / 2 - width(popupContainer) / 2, height / 2 - height(popupContainer) / 2); + } + }); + } + + private final Layout originalLayout; + + public AbstractGuiPopup(GuiContainer container) { + super(container); + container.setLayout(new CustomLayout(originalLayout = container.getLayout()) { + @Override + protected void layout(GuiContainer container, int width, int height) { + pos(AbstractGuiPopup.this, 0, 0); + size(AbstractGuiPopup.this, width, height); + } + }); + } + + protected void close() { + getContainer().setLayout(originalLayout); + getContainer().removeElement(this); + } + + public T setLayer(int layer) { + this.layer = layer; + return getThis(); + } + + @Override + public int getLayer() { + return layer; + } + + @Override + @SuppressWarnings("unchecked") + public C forEach(int layer, final Class ofType) { + final C realProxy = super.forEach(layer, ofType); + if (layer >= 0) { + return (C) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ofType}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getReturnType().equals(boolean.class)) { + method.invoke(forEachChild(ofType), args); + return true; + } + return method.invoke(realProxy, args); + } + }); + } else { + return realProxy; + } + } + + @SuppressWarnings("unchecked") + private C forEachChild(final Class ofType) { + int maxLayer = getMaxLayer(); + final List layers = new ArrayList(maxLayer + 1); + for (int i = maxLayer; i >= 0; i--) { + layers.add(super.forEach(i, ofType)); + } + return (C) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ofType}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + boolean isGetter = method.getName().startsWith("get"); + Object handled = method.getReturnType().equals(boolean.class) ? false : null; + for (final C layer : layers) { + handled = method.invoke(layer, args); + if (handled != null) { + if (handled instanceof Boolean) { + if (Boolean.TRUE.equals(handled)) { + break; + } + } else if (isGetter) { + return handled; + } + } + } + return handled; + } + }); + } +} diff --git a/src/main/java/de/johni0702/minecraft/gui/popup/GuiYesNoPopup.java b/src/main/java/de/johni0702/minecraft/gui/popup/GuiYesNoPopup.java new file mode 100644 index 00000000..e2af46fc --- /dev/null +++ b/src/main/java/de/johni0702/minecraft/gui/popup/GuiYesNoPopup.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015 johni0702 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package de.johni0702.minecraft.gui.popup; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiElement; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.utils.Colors; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.lwjgl.util.Dimension; + +public class GuiYesNoPopup extends AbstractGuiPopup { + public static GuiYesNoPopup open(GuiContainer container, GuiElement... info) { + GuiYesNoPopup popup = new GuiYesNoPopup(container).setBackgroundColor(Colors.DARK_TRANSPARENT) + .setLayer(container.getMaxLayer() + 1); + popup.getInfo().addElements(new VerticalLayout.Data(0.5), info); + return popup; + } + + private final SettableFuture future = SettableFuture.create(); + + @Getter + private final GuiButton yesButton = new GuiButton().setSize(150, 20).onClick(new Runnable() { + @Override + public void run() { + close(); + future.set(true); + } + }); + + @Getter + private final GuiButton noButton = new GuiButton().setSize(150, 20).onClick(new Runnable() { + @Override + public void run() { + close(); + future.set(false); + } + }); + + @Getter + private final GuiPanel info = new GuiPanel().setMinSize(new Dimension(320, 50)) + .setLayout(new VerticalLayout(VerticalLayout.Alignment.TOP).setSpacing(2)); + + @Getter + private final GuiPanel buttons = new GuiPanel() + .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)) + .addElements(new HorizontalLayout.Data(0.5), yesButton, noButton); + + { + popup.setLayout(new VerticalLayout().setSpacing(10)) + .addElements(new VerticalLayout.Data(0.5), info, buttons); + } + + @Getter + @Setter + @Accessors(chain = true) + private int layer; + + public GuiYesNoPopup(GuiContainer container) { + super(container); + } + + public GuiYesNoPopup setYesLabel(String label) { + yesButton.setLabel(label); + return this; + } + + public GuiYesNoPopup setNoLabel(String label) { + noButton.setLabel(label); + return this; + } + + public GuiYesNoPopup setYesI18nLabel(String label, Object...args) { + yesButton.setI18nLabel(label, args); + return this; + } + + public GuiYesNoPopup setNoI18nLabel(String label, Object...args) { + noButton.setI18nLabel(label, args); + return this; + } + + public ListenableFuture getFuture() { + return future; + } + + @Override + protected GuiYesNoPopup getThis() { + return this; + } +} diff --git a/src/main/java/de/johni0702/minecraft/gui/utils/Colors.java b/src/main/java/de/johni0702/minecraft/gui/utils/Colors.java index 4fdd19e8..4f22aa26 100644 --- a/src/main/java/de/johni0702/minecraft/gui/utils/Colors.java +++ b/src/main/java/de/johni0702/minecraft/gui/utils/Colors.java @@ -27,7 +27,9 @@ import org.lwjgl.util.ReadableColor; public interface Colors extends ReadableColor { ReadableColor TRANSPARENT = new Color(0, 0, 0, 0); + ReadableColor LIGHT_TRANSPARENT = new Color(0, 0, 0, 64); ReadableColor HALF_TRANSPARENT = new Color(0, 0, 0, 128); + ReadableColor DARK_TRANSPARENT = new Color(0, 0, 0, 192); ReadableColor LIGHT_GRAY = new Color(192, 192, 192); ReadableColor DARK_RED = new Color(170, 0, 0); } diff --git a/src/main/resources/assets/guiapi/gui.png b/src/main/resources/assets/guiapi/gui.png index 20df55ca5765ada5fd5226f84ae2f22177e450df..64138c69d6850910dac05b338722ac172c582dab 100644 GIT binary patch delta 3716 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 i4vYbl@D3&yXa56No4I%XXUrA=0000