aboutsummaryrefslogtreecommitdiff
path: root/loader/src/main/java/kr/syeyoung
diff options
context:
space:
mode:
Diffstat (limited to 'loader/src/main/java/kr/syeyoung')
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/BindableAttribute.java101
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Context.java31
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/DomElement.java255
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/ElementTreeWalkIterator.java48
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/GuiScreenAdapter.java303
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/RootDom.java71
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Widget.java93
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AbsXY.java80
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Align.java77
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AspectRatioFitter.java102
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Background.java63
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Border.java172
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Button.java128
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CircularRect.java75
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Clip.java72
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Column.java255
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CompatLayer.java169
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ConstrainedBox.java104
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Flexible.java92
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/GlobalHUDScale.java119
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicHeight.java65
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicWidth.java65
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Line.java139
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Measure.java69
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Navigator.java79
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/NegativeStencil.java112
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Padding.java97
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Placeholder.java66
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/RoundRect.java73
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Row.java263
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scaler.java113
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ScrollablePanel.java136
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scrollbar.java165
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/SelectiveContainer.java72
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Slot.java60
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stack.java113
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stencil.java112
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Text.java205
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/TextField.java445
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ToggleButton.java104
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/UnconstrainedBox.java71
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Wrap.java130
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/Image.java60
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ImageTexture.java171
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ResourceImage.java54
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/URLImage.java87
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/AbsLocationPopup.java98
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MinecraftTooltip.java58
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MouseTooltip.java56
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/PopupMgr.java92
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BreakWord.java23
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BrokenWordData.java31
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/FlatTextSpan.java202
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichLine.java33
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichText.java224
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/TextSpan.java48
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/DefaultFontRenderer.java310
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/FontRenderer.java30
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/ChromaShader.java41
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/Shader.java24
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/SingleColorShader.java40
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/CompiledTextStyle.java146
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ITextStyle.java52
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ParentDelegatingTextStyle.java158
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/Layouter.java43
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/NullLayouter.java33
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/SingleChildPassingLayouter.java53
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Animation.java38
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ConstraintBox.java40
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IPosition.java25
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IRect.java34
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ISize.java29
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Position.java28
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Rect.java32
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Size.java28
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/DrawNothingRenderer.java29
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/OnlyChildrenRenderer.java52
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/Renderer.java36
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/RenderingContext.java142
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/SingleChildRenderer.java52
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestPopup.java35
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestView.java50
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedExportOnlyWidget.java88
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedImportOnlyWidget.java125
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedWidget.java151
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidget.java99
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidgetConverter.java40
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DomElementRegistry.java107
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidget.java25
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidgetConverter.java42
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ImportingWidget.java29
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ParsedWidgetConverter.java43
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/PropByPropParsedWidgetConverter.java140
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/StringConversions.java49
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Bind.java30
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Export.java30
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/On.java30
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthrough.java32
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthroughs.java30
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/Parser.java25
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElement.java34
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElementList.java24
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserException.java38
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParser.java70
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParserElement.java74
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/WidgetList.java26
106 files changed, 9362 insertions, 0 deletions
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/BindableAttribute.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/BindableAttribute.java
new file mode 100644
index 00000000..8c09f2cf
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/BindableAttribute.java
@@ -0,0 +1,101 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import lombok.Getter;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+
+public class BindableAttribute<T> {
+ public BindableAttribute(Class<T> type) {
+ this.type = type;
+ initialized = false;
+ }
+ public BindableAttribute(Class<T> type, T defaultValue) {
+ this.type = type;
+ value = defaultValue;
+ initialized = true;
+ }
+
+ private boolean initialized = false;
+ @Getter
+ private final Class<T> type;
+ private T value;
+ private List<BiConsumer<T,T>> onUpdates = new ArrayList<>();
+
+ private boolean updating = false;
+ public void setValue(T t) {
+ if (updating) return;
+ updating = true;
+ T old = this.value;
+ this.value = t;
+ try {
+ if (!Objects.equals(t, old))
+ for (BiConsumer<T, T> onUpdate : onUpdates) {
+ onUpdate.accept(old, value);
+ }
+ } finally {
+ updating = false;
+ initialized = true;
+ }
+ }
+ public T getValue() {
+ return value;
+ }
+
+ public void addOnUpdate(BiConsumer<T,T> onUpdate) {
+ onUpdates.add(onUpdate);
+ }
+ public void removeOnUpdate(BiConsumer<T,T> onUpdate) {
+ onUpdates.remove(onUpdate);
+ }
+
+ private Set<BindableAttribute<T>> linkedWith = new HashSet<>();
+
+ private void boundSet(T old, T neu) {
+ setValue(neu);
+ }
+
+ public void exportTo(BindableAttribute<T> bindableAttribute) { // This method has to be called by exporting bindable attribute
+ if (bindableAttribute.type != type) throw new IllegalArgumentException("Different type!!");
+
+ this.addOnUpdate(bindableAttribute::boundSet);
+ bindableAttribute.addOnUpdate(this::boundSet);
+ linkedWith.add(bindableAttribute);
+
+ if (bindableAttribute.initialized)
+ setValue(bindableAttribute.getValue());
+ else
+ bindableAttribute.setValue(getValue());
+ }
+
+ public void unexport(BindableAttribute<T> bindableAttribute) {
+ bindableAttribute.removeOnUpdate(this::boundSet);
+ removeOnUpdate(bindableAttribute::boundSet);
+ linkedWith.remove(bindableAttribute);
+ }
+
+ public void unexportAll() {
+ Set<BindableAttribute<T>> copy = new HashSet<>(linkedWith);
+ for (BindableAttribute<T> tBindableAttribute : copy) {
+ unexport(tBindableAttribute);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Context.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Context.java
new file mode 100644
index 00000000..b59c2c2b
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Context.java
@@ -0,0 +1,31 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// TODO: get rid of this and change it with tree walking.
+public class Context {
+ public Map<String, Object> CONTEXT = new HashMap<>();
+
+ public <T> T getValue(Class<T> clazz, String key) {
+ return (T) CONTEXT.get(key);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/DomElement.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/DomElement.java
new file mode 100644
index 00000000..9487b21c
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/DomElement.java
@@ -0,0 +1,255 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.Stack;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.NullLayouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Position;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.DrawNothingRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class DomElement {
+ @Getter
+ @Setter(AccessLevel.PACKAGE)
+ private Widget widget;
+ @Getter
+ @Setter(AccessLevel.PACKAGE)
+ private Renderer renderer = DrawNothingRenderer.INSTANCE; // renders element itself.
+ @Getter
+ @Setter(AccessLevel.PACKAGE)
+ private Layouter layouter = NullLayouter.INSTANCE; // layouts subelements
+
+ @Getter
+ @Setter
+ private DomElement parent;
+ @Getter
+ private List<DomElement> children = new CopyOnWriteArrayList<>();
+
+ @Getter
+ Context context;
+
+ public DomElement() {
+ }
+
+ @Getter
+ private boolean isMounted;
+
+
+ public void setMounted(boolean mounted) {
+ if (isMounted == mounted) {
+ return;
+ }
+ isMounted = mounted;
+
+ for (DomElement child : children) {
+ child.context = context;
+ child.setMounted(mounted);
+ }
+
+ if (mounted) widget.onMount();
+ else {
+ if (isFocused())
+ context.CONTEXT.put("focus", null);
+ widget.onUnmount();
+ };
+ }
+
+ @Getter
+ private Rect relativeBound; // relative bound from parent, unapplied transformation
+
+ public void setRelativeBound(Rect relativeBound) {
+ this.relativeBound = relativeBound;
+ this.size = new Size(relativeBound.getWidth(), relativeBound.getHeight());
+ }
+
+ @Getter @Setter
+ private Size size;
+
+ @Getter @Setter
+ private Rect absBounds; // absolute bound from screen top left
+
+
+
+ // event propagation
+
+
+ public void requestRelayoutParent() {
+ if (parent != null)
+ parent.requestRelayout();
+ }
+ public void requestRelayout() {
+ if (layouter.canCutRequest() && size != null) {
+ layouter.layout(this, new ConstraintBox(
+ size.getWidth(), size.getWidth(), size.getHeight(), size.getHeight()
+ ));
+ return;
+ }
+ if (parent != null)
+ parent.requestRelayout();
+ }
+
+ public void addElementFirst(DomElement element) {
+ element.setParent(this);
+ children.add(0, element);
+ element.context = context;
+ element.setMounted(isMounted);
+ requestRelayout();
+ }
+ public void addElement(DomElement element) {
+ element.setParent(this);
+ children.add(element);
+ element.context = context;
+ element.setMounted(isMounted);
+ requestRelayout();
+ }
+ public void removeElement(DomElement element) {
+ element.setParent(null);
+ children.remove(element);
+ element.setMounted(false);
+ requestRelayout();
+ }
+
+ public void keyPressed0(char typedChar, int keyCode) {
+ for (DomElement childComponent : children) {
+ childComponent.keyPressed0(typedChar, keyCode);
+ if (widget instanceof Stack) break;
+ }
+
+ if (isFocused())
+ widget.keyPressed(typedChar, keyCode);
+ }
+ public void keyHeld0(char typedChar, int keyCode) {
+ for (DomElement childComponent : children) {
+ childComponent.keyHeld0(typedChar, keyCode);
+ if (widget instanceof Stack) break;
+ }
+
+ if (isFocused())
+ widget.keyHeld(typedChar, keyCode);
+ }
+ public void keyReleased0(char typedChar, int keyCode) {
+ for (DomElement childComponent : children) {
+ childComponent.keyReleased0(typedChar, keyCode);
+ if (widget instanceof Stack) break;
+ }
+ if (isFocused())
+ widget.keyReleased(typedChar, keyCode);
+ }
+
+ public void obtainFocus() {
+ context.CONTEXT.put("focus", this);
+ }
+
+ public boolean isFocused() {
+ return context.getValue(DomElement.class, "focus") == this;
+ }
+
+ public boolean mouseClicked0(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int mouseButton) {
+ if (absBounds == null) return false;
+ if (!absBounds.contains(absMouseX, absMouseY)) {
+ return false;
+ }
+
+ for (DomElement childComponent : children) {
+ Position transformed = renderer.transformPoint(childComponent, new Position(relMouseX0, relMouseY0));
+
+ if (childComponent.mouseClicked0(absMouseX, absMouseY, transformed.x, transformed.y, mouseButton)) {
+ return true;
+ }
+ }
+
+ return widget.mouseClicked(absMouseX, absMouseY, relMouseX0, relMouseY0, mouseButton);
+ }
+
+
+ public void mouseReleased0(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int state) {
+ if (absBounds == null) return;
+
+ for (DomElement childComponent : children) {
+ Position transformed = renderer.transformPoint(childComponent, new Position(relMouseX0, relMouseY0));
+
+ childComponent.mouseReleased0(absMouseX, absMouseY, transformed.x, transformed.y, state);
+ }
+ widget.mouseReleased(absMouseX, absMouseY, relMouseX0, relMouseY0, state);
+ }
+
+ public void mouseClickMove0(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int clickedMouseButton, long timeSinceLastClick) {
+ if (absBounds == null) return;
+
+ for (DomElement childComponent : children) {
+ Position transformed = renderer.transformPoint(childComponent, new Position(relMouseX0, relMouseY0));
+
+ childComponent.mouseClickMove0(absMouseX, absMouseY, transformed.x, transformed.y, clickedMouseButton, timeSinceLastClick);
+ }
+ if (isFocused())
+ widget.mouseClickMove(absMouseX, absMouseY, relMouseX0, relMouseY0, clickedMouseButton, timeSinceLastClick);
+ }
+ public boolean mouseScrolled0(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int scrollAmount) {
+ if (absBounds == null) return false;
+
+ for (DomElement childComponent : children) {
+ Position transformed = renderer.transformPoint(childComponent, new Position(relMouseX0, relMouseY0));
+
+ if (childComponent.mouseScrolled0(absMouseX, absMouseY, transformed.x, transformed.y, scrollAmount)) {
+ return true;
+ }
+ }
+ if (!absBounds.contains(absMouseX, absMouseY) && !isFocused()) return false;
+ return widget.mouseScrolled(absMouseX, absMouseY, relMouseX0, relMouseY0, scrollAmount);
+ }
+
+
+ private boolean wasMouseIn = false;
+ public boolean mouseMoved0(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, boolean withinbound) {
+ if (absBounds == null) return false;
+ boolean isIn = absBounds.contains(absMouseX, absMouseY) && withinbound;
+ if (!isIn) {
+ if (wasMouseIn) widget.mouseExited(absMouseX, absMouseY, relMouseX0, relMouseY0);
+ } else {
+ if (!wasMouseIn) widget.mouseEntered(absMouseX, absMouseY, relMouseX0, relMouseY0);
+ }
+ wasMouseIn = isIn;
+
+ for (DomElement childComponent : children) {
+ Position transformed = renderer.transformPoint(childComponent, new Position(relMouseX0, relMouseY0));
+
+ if (childComponent.mouseMoved0(absMouseX, absMouseY, transformed.x, transformed.getY(), isIn)) {
+ return true;
+ }
+ }
+ if (isIn)
+ return widget.mouseMoved(absMouseX, absMouseY, relMouseX0, relMouseY0);
+ return false;
+ }
+
+ public void setCursor(EnumCursor enumCursor) {
+ parent.setCursor(enumCursor);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/ElementTreeWalkIterator.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/ElementTreeWalkIterator.java
new file mode 100644
index 00000000..65352a14
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/ElementTreeWalkIterator.java
@@ -0,0 +1,48 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Iterator;
+
+public class ElementTreeWalkIterator implements Iterator<DomElement>, Iterable<DomElement> {
+ private DomElement current;
+ public ElementTreeWalkIterator(DomElement child) {
+ this.current = child;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return current != null;
+ }
+
+ @Override
+ public DomElement next() {
+ DomElement c1 = current;
+ current = current.getParent();
+ return c1;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<DomElement> iterator() {
+ return this;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/GuiScreenAdapter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/GuiScreenAdapter.java
new file mode 100644
index 00000000..03c60397
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/GuiScreenAdapter.java
@@ -0,0 +1,303 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.GLCursors;
+import lombok.Getter;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.io.IOException;
+import java.util.Stack;
+
+import static org.lwjgl.opengl.GL11.GL_GREATER;
+
+public class GuiScreenAdapter extends GuiScreen {
+
+ @Getter
+ private RootDom view;
+ private boolean isOpen = false;
+
+ private Stack<RootDom> domStack = new Stack<>();
+
+ public GuiScreenAdapter(Widget widget) {
+ view = new RootDom(widget);
+ view.getContext().CONTEXT.put("screenAdapter", this);
+
+ try {
+ Mouse.setNativeCursor(GLCursors.getCursor(EnumCursor.DEFAULT));
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void open(Widget newRoot) {
+ domStack.push(view);
+ view = new RootDom(newRoot);
+ view.getContext().CONTEXT.put("screenAdapter", this);
+ initGui();
+ }
+ public void goBack() {
+ view = domStack.pop();
+ view.getContext().CONTEXT.put("screenAdapter", this);
+ initGui();
+ }
+
+ public static GuiScreenAdapter getAdapter(DomElement domElement) {
+ return domElement.getContext().getValue(GuiScreenAdapter.class, "screenAdapter");
+ }
+
+ @Override
+ public void initGui() {
+ super.initGui();
+ Keyboard.enableRepeatEvents(true);
+ isOpen = true;
+ try {
+ view.setRelativeBound(new Rect(0, 0, Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight));
+ view.setAbsBounds(new Rect(0, 0, Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight));
+ view.setSize(new Size(Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight));
+ view.getLayouter().layout(view, new ConstraintBox(
+ Minecraft.getMinecraft().displayWidth,
+ Minecraft.getMinecraft().displayWidth,
+ Minecraft.getMinecraft().displayHeight,
+ Minecraft.getMinecraft().displayHeight
+ ));
+ view.setMounted(true);
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ int i = Mouse.getEventX();
+ int j = this.mc.displayHeight - Mouse.getEventY();
+
+ try {
+ if (view.isRelayoutRequested()) {
+
+ view.setRelayoutRequested(false);
+ view.getLayouter().layout(view, new ConstraintBox(
+ Minecraft.getMinecraft().displayWidth,
+ Minecraft.getMinecraft().displayWidth,
+ Minecraft.getMinecraft().displayHeight,
+ Minecraft.getMinecraft().displayHeight
+ ));
+ }
+
+
+ ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
+ GlStateManager.pushMatrix();
+ GlStateManager.disableDepth();
+ GlStateManager.enableBlend();
+ GlStateManager.enableAlpha();
+ GlStateManager.alphaFunc(GL_GREATER, 0);
+ GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
+ GlStateManager.color(1, 1, 1, 1);
+ GlStateManager.scale(1.0 / scaledResolution.getScaleFactor(), 1.0 / scaledResolution.getScaleFactor(), 1.0d);
+ view.getRenderer().doRender(i, j, i, j, partialTicks, new RenderingContext(), view);
+ GlStateManager.alphaFunc(GL_GREATER, 0.1f);
+ GlStateManager.popMatrix();
+ GlStateManager.enableDepth();
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void keyTyped(char typedChar, int keyCode) throws IOException {
+ try {
+ view.keyPressed0(typedChar, keyCode);
+ super.keyTyped(typedChar, keyCode);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+ public void keyHeld(int keyCode, char typedChar) throws IOException {
+ try {
+ view.keyHeld0(typedChar, keyCode);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+ public void keyReleased(int keyCode, char typedChar) throws IOException {
+ try {
+ view.keyReleased0(typedChar, keyCode);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
+ try {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ view.mouseClicked0(mouseX, mouseY
+ , mouseX, mouseY, mouseButton);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onGuiClosed() {
+ super.onGuiClosed();
+ Keyboard.enableRepeatEvents(false);
+ isOpen = false;
+
+ try {
+ Mouse.setNativeCursor(null);
+ view.setCursor(EnumCursor.DEFAULT);
+ } catch (LWJGLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void mouseReleased(int mouseX, int mouseY, int state) {
+ try {
+ view.mouseReleased0(mouseX, mouseY
+ , mouseX, mouseY, state);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
+ try {
+ view.mouseClickMove0(mouseX, mouseY
+ , mouseX, mouseY, clickedMouseButton, timeSinceLastClick);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+ public void mouseMove(int mouseX, int mouseY) {
+ try {
+ view.mouseMoved0(mouseX, mouseY
+ , mouseX, mouseY, true);
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+
+ private int touchValue;
+ private int eventButton;
+ private long lastMouseEvent;
+
+
+ private int lastX, lastY;
+
+
+ @Override
+ public void handleMouseInput() throws IOException {
+ if (!isOpen) return;
+ try {
+ int i = Mouse.getEventX();
+ int j = this.mc.displayHeight - Mouse.getEventY();
+ int k = Mouse.getEventButton();
+
+ if (Mouse.getEventButtonState()) {
+ if (this.mc.gameSettings.touchscreen && this.touchValue++ > 0) {
+ return;
+ }
+
+ this.eventButton = k;
+ this.lastMouseEvent = Minecraft.getSystemTime();
+ this.mouseClicked(i, j, this.eventButton);
+ } else if (k != -1) {
+ if (this.mc.gameSettings.touchscreen && --this.touchValue > 0) {
+ return;
+ }
+
+ this.eventButton = -1;
+ this.mouseReleased(i, j, k);
+ } else if (this.eventButton != -1 && this.lastMouseEvent > 0L) {
+ long l = Minecraft.getSystemTime() - this.lastMouseEvent;
+ this.mouseClickMove(i, j, this.eventButton, l);
+ }
+ if (lastX != i || lastY != j) {
+ try {
+ EnumCursor prevCursor = view.getCurrentCursor();
+ view.setCursor(EnumCursor.DEFAULT);
+ this.mouseMove(i, j);
+ EnumCursor newCursor = view.getCurrentCursor();
+ if (prevCursor != newCursor) Mouse.setNativeCursor(GLCursors.getCursor(newCursor));
+ } catch (Throwable e) {
+ if (e.getMessage() == null || !e.getMessage().contains("hack to stop"))
+ e.printStackTrace();
+ }
+ }
+
+
+ int wheel = Mouse.getEventDWheel();
+ if (wheel != 0) {
+ view.mouseScrolled0(i, j, i, j, wheel);
+ }
+ lastX = i;
+ lastY = j;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void handleKeyboardInput() throws IOException {
+ if (!isOpen) return;
+
+ if (Keyboard.getEventKeyState()) {
+ if (Keyboard.isRepeatEvent())
+ this.keyHeld(Keyboard.getEventKey(), Keyboard.getEventCharacter());
+ else
+ this.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey());
+ } else {
+ this.keyReleased(Keyboard.getEventKey(), Keyboard.getEventCharacter());
+ }
+
+ this.mc.dispatchKeypresses();
+ }
+
+ @Override
+ public void handleInput() throws IOException {
+ Keyboard.enableRepeatEvents(true); // I hope it's a temporary solution NEU Incompat. ?
+ super.handleInput();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/RootDom.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/RootDom.java
new file mode 100644
index 00000000..d7c732f3
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/RootDom.java
@@ -0,0 +1,71 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.SingleChildPassingLayouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.SingleChildRenderer;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+public class RootDom extends DomElement {
+
+ private static class DummyWidget extends Widget {
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return null;
+ }
+ }
+ public RootDom(Widget widget) {
+ context = new Context();
+ setLayouter(SingleChildPassingLayouter.INSTANCE);
+ setRenderer(SingleChildRenderer.INSTANCE);
+ setWidget(new DummyWidget());
+
+ DomElement element = widget.createDomElement(this);// and it's mounted!
+ addElement(element);
+ }
+
+ @Getter
+ private EnumCursor currentCursor;
+
+ @Override
+ public void setCursor(EnumCursor enumCursor) {
+ currentCursor = enumCursor;
+ }
+
+ @Getter
+ @Setter
+ private boolean relayoutRequested;
+
+ @Override
+ public void requestRelayout() {
+ relayoutRequested = true;
+ }
+
+
+
+ @Override
+ public boolean mouseClicked0(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int mouseButton) {
+ getContext().CONTEXT.put("focus", null);
+ return super.mouseClicked0(absMouseX, absMouseY, relMouseX0, relMouseY0, mouseButton);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Widget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Widget.java
new file mode 100644
index 00000000..3e001f89
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/Widget.java
@@ -0,0 +1,93 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.SingleChildPassingLayouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.SingleChildRenderer;
+import lombok.Getter;
+
+import java.util.List;
+
+public abstract class Widget {
+
+ @Getter
+ private DomElement domElement = new DomElement();
+
+
+ private boolean multiAtFirst = false;
+ public DomElement createDomElement(DomElement parent) {
+ domElement = new DomElement();
+ domElement.setWidget(this);
+ domElement.setParent(parent);
+
+ domElement.setLayouter(createLayouter());
+ domElement.setRenderer(createRenderer());
+
+ // build
+ List<Widget> widgets = build(domElement);
+ for (Widget widget : widgets) {
+ DomElement domElement1 = widget.createDomElement(domElement);
+ domElement.addElement(domElement1);
+ }
+ return domElement;
+ }
+
+ protected Layouter createLayouter() {
+ if (this instanceof Layouter) return (Layouter) this;
+ return SingleChildPassingLayouter.INSTANCE;
+ }
+ protected Renderer createRenderer() {
+ if (this instanceof Renderer) return (Renderer) this;
+ return SingleChildRenderer.INSTANCE;
+ }
+
+ public abstract List<Widget> build(DomElement buildContext);
+
+ public Widget() {
+ // parameters shall be passed through constructor
+ // or maybe a "builder"
+ // set bindable props in the builder and yeah
+ // or maybe set raw props
+ }
+
+
+ public boolean mouseScrolled(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int scrollAmount) {
+ return false;
+ }
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ return false;
+ }
+ public void mouseClickMove(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int clickedMouseButton, long timeSinceLastClick) {}
+ public void mouseReleased(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int state) {}
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ return false;
+ }
+ public void keyReleased(char typedChar, int keyCode) {}
+ public void keyHeld(char typedChar, int keyCode) {}
+ public void keyPressed(char typedChar, int keyCode) {}
+ public void mouseExited(int absMouseX, int absMouseY, double relMouseX, double relMouseY) {}
+ public void mouseEntered(int absMouseX, int absMouseY, double relMouseX, double relMouseY) {}
+
+ public void onMount() {
+ }
+ public void onUnmount() {
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AbsXY.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AbsXY.java
new file mode 100644
index 00000000..50ec1d08
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AbsXY.java
@@ -0,0 +1,80 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class AbsXY extends AnnotatedExportOnlyWidget implements Layouter {
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+ @Export(attributeName = "x")
+ public final BindableAttribute<Double> x = new BindableAttribute<>(Double.class, 0.0);
+
+ @Export(attributeName = "y")
+ public final BindableAttribute<Double> y = new BindableAttribute<>(Double.class, 0.0);
+
+ public AbsXY() {
+ x.addOnUpdate(this::setLocations);
+ y.addOnUpdate(this::setLocations);
+ }
+
+ private void setLocations(double old, double neu) {
+ if (getDomElement().getChildren().size() == 0) return;
+ DomElement child = getDomElement().getChildren().get(0);
+ if (child.getSize() == null) return;
+ child.setRelativeBound(new Rect(x.getValue(), y.getValue(), child.getSize().getWidth(), child.getSize().getHeight()));
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ DomElement child = buildContext.getChildren().get(0);
+ Size size = child.getLayouter().layout(child, new ConstraintBox(
+ 0,constraintBox.getMaxWidth() - x.getValue(), 0, constraintBox.getMaxHeight()-y.getValue()
+ ));
+ child.setRelativeBound(new Rect(x.getValue(), y.getValue(), size.getWidth(), size.getHeight()));
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0), height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0), width);
+ }
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(child.getValue());
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Align.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Align.java
new file mode 100644
index 00000000..b55fe961
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Align.java
@@ -0,0 +1,77 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Align extends AnnotatedExportOnlyWidget implements Layouter {
+ @Export(attributeName = "hAlign")
+ public final BindableAttribute<Alignment> hAlign = new BindableAttribute<>(Alignment.class, Alignment.CENTER);
+ @Export(attributeName = "vAlign")
+ public final BindableAttribute<Alignment> vAlign = new BindableAttribute<>(Alignment.class, Alignment.CENTER);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+ public static enum Alignment {
+ START, CENTER, END
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(child.getValue());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ DomElement theOnly = buildContext.getChildren().get(0);
+ Size size = theOnly.getLayouter().layout(theOnly, new ConstraintBox(
+ 0, constraintBox.getMaxWidth(), 0, constraintBox.getMaxHeight()
+ ));
+
+ double x = hAlign.getValue() == Alignment.START ? 0 : hAlign.getValue() == Alignment.CENTER ? (constraintBox.getMaxWidth() - size.getWidth())/2 : constraintBox.getMaxWidth() - size.getWidth();
+ double y = vAlign.getValue() == Alignment.START ? 0 : vAlign.getValue() == Alignment.CENTER ? (constraintBox.getMaxHeight() - size.getHeight())/2 : constraintBox.getMaxHeight() - size.getHeight();
+
+ theOnly.setRelativeBound(new Rect(x, y,
+ size.getWidth(), size.getHeight()
+ ));
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0), height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0), width);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AspectRatioFitter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AspectRatioFitter.java
new file mode 100644
index 00000000..982c1523
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/AspectRatioFitter.java
@@ -0,0 +1,102 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class AspectRatioFitter extends AnnotatedExportOnlyWidget implements Layouter {
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "width")
+ public final BindableAttribute<Integer> width = new BindableAttribute<>(Integer.class, 1);
+ @Export(attributeName = "height")
+ public final BindableAttribute<Integer> height = new BindableAttribute<>(Integer.class, 1);
+
+ @Export(attributeName = "fit")
+ public final BindableAttribute<Flexible.FlexFit> fit = new BindableAttribute<>(Flexible.FlexFit.class, Flexible.FlexFit.TIGHT);
+
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ // ratio is width/height
+
+
+ double heightIfWidthFit = constraintBox.getMaxWidth() * height.getValue() / width.getValue();
+ double widthIfHeightFit = constraintBox.getMaxHeight() * width.getValue() / height.getValue();
+
+ DomElement target = null;
+ if (!buildContext.getChildren().isEmpty())
+ target = buildContext.getChildren().get(0);
+ if (heightIfWidthFit <= constraintBox.getMaxHeight()) {
+ if (target != null) {
+ Size size = target.getLayouter().layout(target, new ConstraintBox(
+ fit.getValue() == Flexible.FlexFit.LOOSE ? constraintBox.getMinWidth() : constraintBox.getMaxWidth(), constraintBox.getMaxWidth(),
+ fit.getValue() == Flexible.FlexFit.LOOSE ? constraintBox.getMinHeight() : heightIfWidthFit, heightIfWidthFit
+ ));
+ target.setRelativeBound(new Rect(0,0,size.getWidth(), size.getHeight()));
+ return size;
+ }
+ return new Size(constraintBox.getMaxWidth(), heightIfWidthFit);
+ } else if (widthIfHeightFit <= constraintBox.getMaxWidth()){
+ if (target != null) {
+ Size size = target.getLayouter().layout(target, new ConstraintBox(
+ fit.getValue() == Flexible.FlexFit.LOOSE ? constraintBox.getMinWidth() : widthIfHeightFit, widthIfHeightFit,
+ fit.getValue() == Flexible.FlexFit.LOOSE ? constraintBox.getMinHeight() : constraintBox.getMaxHeight(), constraintBox.getMaxHeight()
+ ));
+ target.setRelativeBound(new Rect(0,0,size.getWidth(), size.getHeight()));
+ return size;
+ }
+
+ return new Size(widthIfHeightFit, constraintBox.getMaxHeight());
+ } else {
+ throw new IllegalStateException("How is this possible mathematically?");
+ }
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return height * this.width.getValue() / this.height.getValue();
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return width * this.height.getValue() / this.width.getValue();
+ }
+
+ @Override
+ public boolean canCutRequest() {
+ return Flexible.FlexFit.TIGHT == fit.getValue();
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Background.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Background.java
new file mode 100644
index 00000000..8fadc636
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Background.java
@@ -0,0 +1,63 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.SingleChildRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+// passes down constraints
+// but sets background!! cool!!!
+public class Background extends AnnotatedExportOnlyWidget {
+
+ @Export(attributeName = "backgroundColor")
+ public final BindableAttribute<Integer> color = new BindableAttribute<>(Integer.class, 0xFFFFFFFF);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return child.getValue() == null ? Collections.EMPTY_LIST : Collections.singletonList(child.getValue());
+ }
+
+ @Override
+ protected Renderer createRenderer() {
+ return new BRender();
+ }
+
+ public class BRender extends SingleChildRenderer {
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext renderingContext, DomElement buildContext) {
+ renderingContext.drawRect(0,0,buildContext.getSize().getWidth(),buildContext.getSize().getHeight(),
+ color.getValue()
+ );
+ super.doRender(absMouseX, absMouseY, relMouseX, relMouseY, partialTicks, renderingContext, buildContext);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Border.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Border.java
new file mode 100644
index 00000000..640a54c3
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Border.java
@@ -0,0 +1,172 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.OnlyChildrenRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Border extends AnnotatedExportOnlyWidget implements Layouter {
+ @Export(attributeName = "_left")
+ public final BindableAttribute<Widget> left = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_right")
+ public final BindableAttribute<Widget> right = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_top")
+ public final BindableAttribute<Widget> top = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_bottom")
+ public final BindableAttribute<Widget> bottom = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_content")
+ public final BindableAttribute<Widget> content = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ // layout borders, ask them about their constraints,
+ // then layout content with space less than blahblah
+ // then relayout borders.
+ DomElement top = null, bottom = null, left = null, right = null, content = null;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() == this.top.getValue()) top = child;
+ if (child.getWidget() == this.bottom.getValue()) bottom = child;
+ if (child.getWidget() == this.left.getValue()) left = child;
+ if (child.getWidget() == this.right.getValue()) right = child;
+ if (child.getWidget() == this.content.getValue()) content = child;
+ }
+
+
+
+ double th = 0, bh = 0;
+ {
+ if (top != null)
+ th= top.getLayouter().layout(top, new ConstraintBox(constraintBox.getMaxWidth(), constraintBox.getMaxWidth(), 0, constraintBox.getMaxHeight())).getHeight();
+ if (bottom != null)
+ bh = bottom.getLayouter().layout(bottom, new ConstraintBox(constraintBox.getMaxWidth(), constraintBox.getMaxWidth(), 0, constraintBox.getMaxHeight())).getHeight();
+ }
+
+ double lw = 0, rw = 0;
+ {
+ if (left != null)
+ lw= left.getLayouter().layout(left, new ConstraintBox(0, constraintBox.getMaxWidth(), constraintBox.getMaxHeight(), constraintBox.getMaxHeight())).getWidth();
+ if (right != null)
+ rw= right.getLayouter().layout(right, new ConstraintBox(0, constraintBox.getMaxWidth(), constraintBox.getMaxHeight(), constraintBox.getMaxHeight())).getWidth();
+ }
+
+ Size dimension = content.getLayouter().layout(content, new ConstraintBox(
+ 0, constraintBox.getMaxWidth() - lw - rw,
+ 0, constraintBox.getMaxHeight() - th - bh
+ ));
+
+
+ {
+ if (left != null)
+ lw= left.getLayouter().layout(left, new ConstraintBox(lw, lw, dimension.getHeight(), dimension.getHeight())).getWidth();
+ if (right != null)
+ rw= right.getLayouter().layout(right, new ConstraintBox(rw, rw, dimension.getHeight(), dimension.getHeight())).getWidth();
+ }
+ {
+ if (top != null)
+ th= top.getLayouter().layout(top, new ConstraintBox(dimension.getWidth() + lw + rw, dimension.getWidth() + lw + rw, th, th)).getHeight();
+ if (bottom != null)
+ bh = bottom.getLayouter().layout(bottom, new ConstraintBox(dimension.getWidth() + lw + rw, dimension.getWidth() + lw + rw, bh,bh)).getHeight();
+ }
+
+ if (top != null)
+ top.setRelativeBound(new Rect(0,0, dimension.getWidth() + lw + rw, th));
+ if (bottom != null)
+ bottom.setRelativeBound(new Rect(0, dimension.getHeight() + th, dimension.getWidth() + lw + rw, bh));
+ if (left != null)
+ left.setRelativeBound(new Rect(0, th, lw, dimension.getHeight()));
+ if (right != null)
+ right.setRelativeBound(new Rect(lw + dimension.getWidth(), th, rw, dimension.getHeight()));
+
+ content.setRelativeBound(new Rect(
+ th, lw, dimension.getWidth(), dimension.getHeight()
+ ));
+
+
+
+ return new Size(dimension.getWidth() + lw + rw, dimension.getHeight() + th + bh);
+ }
+
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ DomElement top = null, bottom = null, left = null, right = null, content = null;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() == this.top.getValue()) top = child;
+ if (child.getWidget() == this.bottom.getValue()) bottom = child;
+ if (child.getWidget() == this.left.getValue()) left = child;
+ if (child.getWidget() == this.right.getValue()) right = child;
+ if (child.getWidget() == this.content.getValue()) content = child;
+ }
+ double effHeight = height - top.getLayouter().getMaxIntrinsicHeight(top, 0)
+ - bottom.getLayouter().getMaxIntrinsicHeight(bottom, 0);
+ return content.getLayouter().getMaxIntrinsicWidth(content, effHeight)
+ + left.getLayouter().getMaxIntrinsicWidth(left, effHeight)
+ + right.getLayouter().getMaxIntrinsicWidth(right, effHeight);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ DomElement top = null, bottom = null, left = null, right = null, content = null;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() == this.top.getValue()) top = child;
+ if (child.getWidget() == this.bottom.getValue()) bottom = child;
+ if (child.getWidget() == this.left.getValue()) left = child;
+ if (child.getWidget() == this.right.getValue()) right = child;
+ if (child.getWidget() == this.content.getValue()) content = child;
+ }
+ double effWidth = width - left.getLayouter().getMaxIntrinsicHeight(top, 0)
+ - right.getLayouter().getMaxIntrinsicHeight(bottom, 0);
+ return content.getLayouter().getMaxIntrinsicHeight(content, effWidth)
+ + top.getLayouter().getMaxIntrinsicHeight(top, width)
+ + bottom.getLayouter().getMaxIntrinsicHeight(bottom, width);
+ }
+
+ @Override
+ protected Renderer createRenderer() {
+ return OnlyChildrenRenderer.INSTANCE;
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ List<Widget> widgets = new LinkedList<>();
+ widgets.add(content.getValue());
+ if (top.getValue() != null)
+ widgets.add(top.getValue());
+ if (bottom.getValue() != null)
+ widgets.add(bottom.getValue());
+ if (left.getValue() != null)
+ widgets.add(left.getValue());
+ if (right.getValue() != null)
+ widgets.add(right.getValue());
+ return widgets;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Button.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Button.java
new file mode 100644
index 00000000..73b0e21d
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Button.java
@@ -0,0 +1,128 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Passthrough;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.util.ResourceLocation;
+
+@Passthrough(exportName = "_", bindName = "wgtNormal", type = Widget.class)
+@Passthrough(exportName = "_hovered", bindName = "wgtHover", type = Widget.class)
+@Passthrough(exportName = "_pressed", bindName = "wgtPressed", type = Widget.class)
+@Passthrough(exportName = "_disabled", bindName = "wgtDisabled", type = Widget.class)
+public class Button extends AnnotatedWidget implements Renderer {
+
+ @Bind(variableName = "refDisabled")
+ public final BindableAttribute<DomElement> disabled = new BindableAttribute<DomElement>(DomElement.class);
+ @Bind(variableName = "refPressed")
+ public final BindableAttribute<DomElement> pressed = new BindableAttribute<DomElement>(DomElement.class);
+ @Bind(variableName = "refHover")
+ public final BindableAttribute<DomElement> hover = new BindableAttribute<DomElement>(DomElement.class);
+ @Bind(variableName = "refNormal")
+ public final BindableAttribute<DomElement> normal = new BindableAttribute<DomElement>(DomElement.class);
+
+
+ @Export(attributeName = "click")
+ public final BindableAttribute<Runnable> onClick = new BindableAttribute<>(Runnable.class);
+ @Export(attributeName = "disabled")
+ public final BindableAttribute<Boolean> isDisabled = new BindableAttribute<>(Boolean.class);
+
+ public Button() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/button.gui"));
+ }
+
+ @Override
+ protected Layouter createLayouter() {
+ return Stack.StackingLayouter.INSTANCE;
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ boolean isHover = buildContext.getSize().contains(relMouseX, relMouseY);
+ DomElement value;
+ if (isDisabled.getValue()) {
+ value = disabled.getValue();
+ } else if (isPressed) {
+ value = pressed.getValue();
+ } else if (isHover) {
+ value = hover.getValue();
+ } else {
+ value = normal.getValue();
+ }
+ Rect original = value.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks, context, value);
+ }
+
+ private boolean isPressed;
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ getDomElement().obtainFocus();
+ isPressed = true;
+
+
+ return onClick.getValue() != null;
+ }
+
+ @Override
+ public void mouseReleased(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int state) {
+ isPressed = false;
+
+
+ if (isDisabled.getValue()) return;
+ if (getDomElement().getAbsBounds().contains(absMouseX, absMouseY)) {
+ if (onClick.getValue() != null) onClick.getValue().run();
+ }
+ super.mouseReleased(absMouseX, absMouseY, relMouseX, relMouseY, state);
+ }
+
+ @Override
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ if (isDisabled.getValue())
+ getDomElement().setCursor(EnumCursor.NOT_ALLOWED);
+ else
+ getDomElement().setCursor(EnumCursor.POINTING_HAND);
+ return true;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CircularRect.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CircularRect.java
new file mode 100644
index 00000000..18738d42
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CircularRect.java
@@ -0,0 +1,75 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.SingleChildRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.mod.shader.ShaderManager;
+import kr.syeyoung.dungeonsguide.mod.shader.ShaderProgram;
+import net.minecraft.client.Minecraft;
+import org.lwjgl.opengl.GL20;
+
+import java.util.Collections;
+import java.util.List;
+
+public class CircularRect extends AnnotatedExportOnlyWidget {
+
+ @Export(attributeName = "backgroundColor")
+ public final BindableAttribute<Integer> color = new BindableAttribute<>(Integer.class, 0xFFFFFFFF);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+ @Override
+ protected Renderer createRenderer() {
+ return new BRender();
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return child.getValue() == null ? Collections.EMPTY_LIST : Collections.singletonList(child.getValue());
+ }
+
+ public class BRender extends SingleChildRenderer {
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext renderingContext, DomElement buildContext) {
+ Size size = buildContext.getSize();
+ double radius = Math.min(size.getWidth(), size.getHeight()) / 2;
+
+ ShaderProgram shaderProgram = ShaderManager.getShader("shaders/roundrect");
+ shaderProgram.useShader();
+ shaderProgram.uploadUniform("radius", (float)(radius * buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth()));
+ shaderProgram.uploadUniform("halfSize", (float) buildContext.getAbsBounds().getWidth()/2, (float) buildContext.getAbsBounds().getHeight()/2);
+ shaderProgram.uploadUniform("centerPos",
+ (float) (buildContext.getAbsBounds().getX()+buildContext.getAbsBounds().getWidth()/2),
+ Minecraft.getMinecraft().displayHeight - (float) (buildContext.getAbsBounds().getY() + buildContext.getAbsBounds().getHeight()/2));
+ shaderProgram.uploadUniform("smoothness", 1.0f);
+ renderingContext.drawRect(0,0,buildContext.getSize().getWidth(), buildContext.getSize().getHeight(), color.getValue());
+ GL20.glUseProgram(0);
+ super.doRender(absMouseX, absMouseY, relMouseX, relMouseY, partialTicks, renderingContext, buildContext);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Clip.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Clip.java
new file mode 100644
index 00000000..6656d4df
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Clip.java
@@ -0,0 +1,72 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.client.renderer.GlStateManager;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Clip extends AnnotatedExportOnlyWidget implements Renderer {
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ if (buildContext.getChildren().isEmpty()) return;
+ if (buildContext.getSize().getWidth() <= 0 || buildContext.getSize().getHeight() <= 0)
+ return;
+ context.pushClip(buildContext.getAbsBounds(), buildContext.getSize(), 0,0, buildContext.getSize().getWidth(), buildContext.getSize().getHeight());
+
+ DomElement value = buildContext.getChildren().get(0);
+
+ Rect original = value.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks, context, value);
+ context.popClip();
+ }
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Column.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Column.java
new file mode 100644
index 00000000..6d618886
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Column.java
@@ -0,0 +1,255 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.OnlyChildrenRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.WidgetList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Column extends AnnotatedExportOnlyWidget implements Layouter {
+ public static enum CrossAxisAlignment {
+ START, CENTER, END, STRETCH
+ }
+ public static enum MainAxisAlignment {
+ START, CENTER, END, SPACE_EVENLY, SPACE_AROUND, SPACE_BETWEEN
+ }
+
+ @Export(attributeName = "crossAlign")
+ public final BindableAttribute<CrossAxisAlignment> hAlign = new BindableAttribute<>(CrossAxisAlignment.class, CrossAxisAlignment.CENTER);
+
+ @Export(attributeName = "mainAlign")
+ public final BindableAttribute<MainAxisAlignment> vAlign = new BindableAttribute<>(MainAxisAlignment.class, MainAxisAlignment.START);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<WidgetList> widgets = new BindableAttribute<>(WidgetList.class);
+
+ @Export(attributeName = "api")
+ public final BindableAttribute<Column> api = new BindableAttribute<>(Column.class, this);
+
+ public Column() {
+ hAlign.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ vAlign.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return widgets.getValue();
+ }
+
+ @Override
+ protected Renderer createRenderer() {
+ return OnlyChildrenRenderer.INSTANCE;
+ }
+
+ public void addWidget(Widget widget) {
+ if (getDomElement().getWidget() == null) {
+ widgets.getValue().add(widget);
+ } else {
+ DomElement domElement = widget.createDomElement(getDomElement());
+ getDomElement().addElement(domElement);
+ }
+ }
+
+ public void removeWidget(Widget widget) {
+ getDomElement().removeElement(widget.getDomElement());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraints) {
+ double width = 0;
+ double height = 0;
+ double effheight = constraints.getMaxHeight(); // max does not count for column.
+ CrossAxisAlignment crossAxisAlignment = hAlign.getValue();
+ MainAxisAlignment mainAxisAlignment = vAlign.getValue();
+ Map<DomElement, Size> saved = new HashMap<>();
+ for (DomElement child : buildContext.getChildren()) {
+ if (!(child.getWidget() instanceof Flexible)) {
+ Size requiredSize = child.getLayouter().layout(child, new ConstraintBox(
+ crossAxisAlignment == CrossAxisAlignment.STRETCH
+ ? constraints.getMaxWidth() : 0, constraints.getMaxWidth(), 0, Double.POSITIVE_INFINITY
+ ));
+ saved.put(child, requiredSize);
+ width = Math.max(width, requiredSize.getWidth());
+ height += requiredSize.getHeight();
+ }
+ }
+
+
+
+ boolean flexFound = false;
+ int sumFlex = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ sumFlex += Math.max(1, ((Flexible) child.getWidget()).flex.getValue());
+ flexFound = true;
+ }
+ }
+
+ if (flexFound && effheight == Double.POSITIVE_INFINITY) throw new IllegalStateException("Max height can not be infinite with flex elements");
+ else if (effheight == Double.POSITIVE_INFINITY) effheight = height;
+
+ if (flexFound) {
+ double remainingHeight = effheight - height;
+ if (remainingHeight < 0) remainingHeight = 0;
+ double heightPer = remainingHeight / sumFlex;
+
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ Size requiredSize = child.getLayouter().layout(child, new ConstraintBox(
+ crossAxisAlignment == CrossAxisAlignment.STRETCH
+ ? constraints.getMaxWidth() : 0, constraints.getMaxWidth(), 0, heightPer * ((Flexible) child.getWidget()).flex.getValue()
+ ));
+ saved.put(child, requiredSize);
+ width = Math.max(width, requiredSize.getWidth());
+ height += requiredSize.getHeight();
+ }
+ }
+ }
+ width = constraints.getMaxWidth() == Double.POSITIVE_INFINITY ? width : constraints.getMaxWidth();
+
+
+
+ double starty = 0;
+ double heightDelta = 0;
+
+ if (mainAxisAlignment == MainAxisAlignment.CENTER)
+ starty = (effheight - height) / 2;
+ else if (mainAxisAlignment == MainAxisAlignment.END)
+ starty = effheight - height;
+ else if (mainAxisAlignment == MainAxisAlignment.SPACE_BETWEEN) {
+ double remaining = effheight - height;
+ if (remaining > 0) {
+ starty = 0;
+ heightDelta = remaining / (buildContext.getChildren().size()-1);
+ } else {
+ starty = (effheight - height) / 2;
+ }
+ } else if (mainAxisAlignment == MainAxisAlignment.SPACE_EVENLY) {
+ double remaining = effheight - height;
+ if (remaining > 0) {
+ heightDelta = remaining / (buildContext.getChildren().size()+1);
+ starty = heightDelta;
+ } else {
+ starty= (effheight - height) / 2;
+ }
+ } else if (mainAxisAlignment == MainAxisAlignment.SPACE_AROUND) {
+ double remaining = effheight - height;
+ if (remaining > 0) {
+ heightDelta = 2 * remaining / buildContext.getChildren().size();
+ starty = heightDelta / 2;
+ } else {
+ starty = (effheight - height / 2);
+ }
+ }
+
+ for (DomElement child : buildContext.getChildren()) {
+ Size size = saved.get(child);
+
+ child.setRelativeBound(new Rect(
+ crossAxisAlignment == CrossAxisAlignment.START ? 0 :
+ crossAxisAlignment == CrossAxisAlignment.CENTER ? (width-size.getWidth())/2 :
+ crossAxisAlignment == CrossAxisAlignment.STRETCH ? (width - size.getWidth()) /2 :
+ width - size.getWidth(), starty
+ ,size.getWidth(), size.getHeight()
+ ));
+ starty += size.getHeight();
+ starty += heightDelta;
+ }
+ return new Size(
+ width,
+ effheight
+ );
+ }
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ double height = 0;
+ double flex = 0;
+ double maxPer = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ flex += ((Flexible) child.getWidget()).flex.getValue();
+ maxPer = Double.max(maxPer, child.getLayouter().getMaxIntrinsicHeight(child, width) /
+ ((Flexible) child.getWidget()).flex.getValue());
+ } else {
+ height += child.getLayouter().getMaxIntrinsicHeight(child, width);
+ }
+ }
+ return height + maxPer * flex;
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ double startingWidth = 0;
+ double prevWidth = 0;
+ double maxWidth = 0;
+ int circuitBreaker = 0;
+ do {
+ circuitBreaker++;
+ if (circuitBreaker > 100) {
+ try {
+ throw new RuntimeException("Caught in infinite loop welp");
+ } catch (Exception e) { e.printStackTrace(); }
+ break;
+ }
+ prevWidth = startingWidth;
+ startingWidth = maxWidth;
+ double heightTaken = 0;
+ int sumFlex = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (!(child.getWidget() instanceof Flexible)) {
+ heightTaken += child.getLayouter().getMaxIntrinsicHeight(child, startingWidth);
+ maxWidth = Double.max(maxWidth, child.getLayouter().getMaxIntrinsicWidth(child, 0));
+ } else {
+ sumFlex += ((Flexible) child.getWidget()).flex.getValue();
+ }
+ }
+ double leftOver = height - heightTaken;
+ if (leftOver <= 0) {
+ leftOver = 0;
+ if (prevWidth > 0)
+ maxWidth = prevWidth;
+ break;
+ }
+ if (sumFlex > 0) {
+ double per = leftOver / sumFlex;
+ if (height == 0) per = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ maxWidth = Double.max(maxWidth, child.getLayouter().getMaxIntrinsicWidth(child, per * ((Flexible) child.getWidget()).flex.getValue()));
+ }
+ }
+ }
+ if (leftOver == 0 || sumFlex == 0) break;
+ } while (startingWidth != maxWidth);
+ return maxWidth;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CompatLayer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CompatLayer.java
new file mode 100644
index 00000000..69ad5652
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/CompatLayer.java
@@ -0,0 +1,169 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.mod.gui.MPanel;
+import kr.syeyoung.dungeonsguide.mod.gui.elements.MTooltip;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups.AbsLocationPopup;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups.PopupMgr;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import net.minecraft.client.Minecraft;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CompatLayer extends Widget implements Layouter, Renderer {
+ private MPanel panel;
+ private Rectangle force;
+
+ public CompatLayer(MPanel panel) {
+ this(panel, null);
+ }
+
+ List<AbsLocationPopup> tooltips = new ArrayList<>();
+ public CompatLayer(MPanel panel, Rectangle force) {
+ this.panel = panel;
+ this.force = force;
+ panel.setParent(new MPanel() {
+ @Override
+ public void setCursor(EnumCursor enumCursor) {
+ getDomElement().setCursor(enumCursor);
+ }
+
+ @Override
+ public int getTooltipsOpen() {
+ return 0;
+ }
+
+ @Override
+ public void openTooltip(MTooltip mPanel) {
+ Rectangle bounds = mPanel.getBounds();
+ AbsLocationPopup absLocationPopup = new AbsLocationPopup(bounds.getX(), bounds.getY(), new CompatLayer(mPanel, bounds), true);
+ PopupMgr.getPopupMgr(getDomElement()).openPopup(absLocationPopup, (a) -> {tooltips.remove(absLocationPopup);});
+ tooltips.add(absLocationPopup);
+ }
+ });
+ }
+
+ @Override
+ public void onUnmount() {
+ super.onUnmount();
+ tooltips.forEach(PopupMgr.getPopupMgr(getDomElement())::closePopup);
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.emptyList();
+ }
+
+
+
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ getDomElement().obtainFocus();
+ double scale = getDomElement().getAbsBounds().getWidth() / getDomElement().getSize().getWidth();
+ return panel.mouseClicked0( (int)(absMouseX / scale), (int)(absMouseY / scale), (int)relMouseX,(int) relMouseY, mouseButton);
+ }
+
+ @Override
+ public void mouseClickMove(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int clickedMouseButton, long timeSinceLastClick) {
+ double scale = getDomElement().getAbsBounds().getWidth() / getDomElement().getSize().getWidth();
+ panel.mouseClickMove0( (int)(absMouseX / scale), (int)(absMouseY / scale), (int)relMouseX, (int)relMouseY, clickedMouseButton, timeSinceLastClick);
+ }
+
+ @Override
+ public void mouseReleased(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int state) {
+ double scale = getDomElement().getAbsBounds().getWidth() / getDomElement().getSize().getWidth();
+ panel.mouseReleased0( (int)(absMouseX / scale), (int)(absMouseY / scale), (int)relMouseX,(int) relMouseY, state);
+ }
+
+ @Override
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ double scale = getDomElement().getAbsBounds().getWidth() / getDomElement().getSize().getWidth();
+ panel.mouseMoved0( (int)(absMouseX / scale), (int)(absMouseY / scale), (int)relMouseX0, (int)relMouseY0);
+ return true;
+ }
+
+ @Override
+ public boolean mouseScrolled(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int scrollAmount) {
+ double scale = getDomElement().getAbsBounds().getWidth() / getDomElement().getSize().getWidth();
+ panel.mouseScrolled0( (int)(absMouseX / scale), (int)(absMouseY / scale), (int)relMouseX0, (int)relMouseY0, scrollAmount);
+ return true;
+ }
+
+ @Override
+ public void keyPressed(char typedChar, int keyCode) {
+ panel.keyPressed0(typedChar, keyCode);
+ }
+
+ @Override
+ public void keyReleased(char typedChar, int keyCode) {
+ panel.keyReleased0(typedChar, keyCode);
+ }
+
+ @Override
+ public void keyHeld(char typedChar, int keyCode) {
+ panel.keyHeld0(typedChar, keyCode);
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ Dimension dimension = force == null ? panel.getPreferredSize() : force.getSize();
+ panel.resize((int) constraintBox.getMaxWidth(), (int) constraintBox.getMaxHeight());
+ if (panel.getBounds().getWidth() != 0) dimension = panel.getSize();
+
+ panel.setBounds(new Rectangle(0,0, (int) dimension.getWidth(), (int) dimension.getHeight()));
+ return new Size(dimension.getWidth(), dimension.getHeight());
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ double scale = getDomElement().getAbsBounds().getWidth() / getDomElement().getSize().getWidth();
+
+ Rectangle originalRect = context.currentClip();
+ Rectangle rectangle = originalRect == null ? null : originalRect.getBounds();
+ boolean isNotNull = rectangle != null;
+ if (rectangle == null) rectangle = new Rectangle(0,0,Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight);
+
+ rectangle.y = Minecraft.getMinecraft().displayHeight - rectangle.y - rectangle.height;
+ rectangle.width /= scale;
+ rectangle.height /= scale;
+ rectangle.x /= scale;
+ rectangle.y /= scale;
+
+// System.out.println(rectangle);
+ panel.render0(scale, new Point((int) (getDomElement().getAbsBounds().getX() / scale), (int) (getDomElement().getAbsBounds().getY() /scale)),
+ rectangle, (int)(absMouseX / scale), (int)(absMouseY / scale), (int)relMouseX, (int)relMouseY, partialTicks);
+
+ if (isNotNull) {
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+ GL11.glScissor(originalRect.x, originalRect.y, originalRect.width, originalRect.height);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ConstrainedBox.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ConstrainedBox.java
new file mode 100644
index 00000000..d552c416
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ConstrainedBox.java
@@ -0,0 +1,104 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class ConstrainedBox extends AnnotatedExportOnlyWidget implements Layouter {
+
+ @Export(attributeName = "minWidth")
+ public final BindableAttribute<Double> minWidth = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "minHeight")
+ public final BindableAttribute<Double> minHeight = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "maxWidth")
+ public final BindableAttribute<Double> maxWidth = new BindableAttribute<>(Double.class, Double.POSITIVE_INFINITY);
+ @Export(attributeName = "maxHeight")
+ public final BindableAttribute<Double> maxHeight = new BindableAttribute<>(Double.class, Double.POSITIVE_INFINITY);
+
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return child.getValue() == null ? Collections.EMPTY_LIST : Collections.singletonList(child.getValue());
+ }
+
+ public ConstrainedBox() {
+ minWidth.addOnUpdate((a, b) -> getDomElement().requestRelayoutParent());
+ minHeight.addOnUpdate((a, b) -> getDomElement().requestRelayoutParent());
+ maxWidth.addOnUpdate((a, b) -> getDomElement().requestRelayoutParent());
+ maxHeight.addOnUpdate((a, b) -> getDomElement().requestRelayoutParent());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+
+ double minWidth = Layouter.clamp(this.minWidth.getValue(), constraintBox.getMinWidth(), constraintBox.getMaxWidth());
+ double minHeight = Layouter.clamp(this.minHeight.getValue(), constraintBox.getMinHeight(), constraintBox.getMaxHeight());
+
+ double maxWidth = Layouter.clamp(this.maxWidth.getValue(), constraintBox.getMinWidth(), constraintBox.getMaxWidth());
+ double maxHeight = Layouter.clamp(this.maxHeight.getValue(), constraintBox.getMinHeight(), constraintBox.getMaxHeight());
+
+ if (getDomElement().getChildren().isEmpty()) {
+ return new Size(maxWidth == Double.POSITIVE_INFINITY ? 0 : maxWidth,
+ maxHeight == Double.POSITIVE_INFINITY ? 0 : maxHeight);
+ }
+
+ DomElement child = getDomElement().getChildren().get(0);
+ Size dim = child.getLayouter().layout(child, new ConstraintBox(
+ minWidth, maxWidth, minHeight, maxHeight
+ )); // force size heh.
+ child.setRelativeBound(new Rect(0, 0, dim.getWidth(), dim.getHeight()));
+ return dim;
+ }
+
+ @Override
+ public boolean canCutRequest() {
+ return Objects.equals(minWidth.getValue(), maxWidth.getValue())
+ && Objects.equals(minHeight.getValue(), maxHeight.getValue());
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ DomElement child = buildContext.getChildren().get(0);
+ return Layouter.clamp(child.getLayouter().getMaxIntrinsicHeight(child, width),
+ minHeight.getValue() == Double.POSITIVE_INFINITY ? 0 : minHeight.getValue(), maxHeight.getValue());
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ DomElement child = buildContext.getChildren().get(0);
+ return Layouter.clamp(child.getLayouter().getMaxIntrinsicWidth(child, height),
+ minWidth.getValue() == Double.POSITIVE_INFINITY ? 0 : minWidth.getValue(), maxWidth.getValue());
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Flexible.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Flexible.java
new file mode 100644
index 00000000..27068dfd
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Flexible.java
@@ -0,0 +1,92 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+// yes it's that flexible from flutter
+public class Flexible extends AnnotatedExportOnlyWidget implements Layouter {
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+
+ @Export(attributeName = "flex")
+ public final BindableAttribute<Integer> flex = new BindableAttribute<>(Integer.class, 1);
+
+ @Export(attributeName = "fit")
+ public final BindableAttribute<FlexFit> fit = new BindableAttribute<>(FlexFit.class, FlexFit.TIGHT);
+
+ public static enum FlexFit {
+ TIGHT, LOOSE
+ }
+
+ public Flexible() {
+ flex.addOnUpdate((a,b) -> Optional.ofNullable(getDomElement().getParent()).ifPresent(DomElement::requestRelayout));
+ fit.addOnUpdate((a,b) -> Optional.ofNullable(getDomElement().getParent()).ifPresent(DomElement::requestRelayout));
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ FlexFit fit = this.fit.getValue();
+ ConstraintBox box =
+ fit == FlexFit.TIGHT ?
+ new ConstraintBox(constraintBox.getMaxWidth() == Double.POSITIVE_INFINITY ? 0 : constraintBox.getMaxWidth(), constraintBox.getMaxWidth()
+ , constraintBox.getMaxHeight() == Double.POSITIVE_INFINITY ? 0 : constraintBox.getMaxHeight(), constraintBox.getMaxHeight())
+ : ConstraintBox.loose(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ DomElement child = buildContext.getChildren().get(0);
+
+ Size dim = child.getLayouter().layout(child, box);
+
+ buildContext.getChildren().get(0).setRelativeBound(new Rect(0,0, dim.getWidth(),dim.getHeight()));
+ return dim;
+ }
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0), height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0), width);
+ }
+
+ @Override
+ public boolean canCutRequest() {
+ return FlexFit.TIGHT == fit.getValue();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/GlobalHUDScale.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/GlobalHUDScale.java
new file mode 100644
index 00000000..263d624f
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/GlobalHUDScale.java
@@ -0,0 +1,119 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Position;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+
+import java.util.Collections;
+import java.util.List;
+
+public class GlobalHUDScale extends Widget implements Layouter, Renderer {
+
+ private final Widget widget;
+ public GlobalHUDScale(Widget widget) {
+ this.widget = widget;
+ scale = getScale();
+ }
+
+
+ private double getScale() {
+ return new ScaledResolution(Minecraft.getMinecraft()).getScaleFactor();
+ }
+
+ private double scale;
+
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget);
+ }
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ this.scale = getScale();
+ DomElement child = buildContext.getChildren().get(0);
+ Size dims = child.getLayouter().layout(child, new ConstraintBox(
+ (constraintBox.getMinWidth() / scale),
+ (constraintBox.getMaxWidth() / scale),
+ (constraintBox.getMinHeight() / scale),
+ (constraintBox.getMaxHeight() / scale)
+ ));
+ child.setRelativeBound(new Rect(0,0,
+ (dims.getWidth() * scale),
+ (dims.getHeight() * scale)));
+ child.setSize(new Size(dims.getWidth(), dims.getHeight()));
+
+ return new Size(dims.getWidth() * scale, dims.getHeight() * scale);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ DomElement child = buildContext.getChildren().get(0);
+ return child.getLayouter().getMaxIntrinsicHeight(child, width / scale) * scale;
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ DomElement child = buildContext.getChildren().get(0);
+ return child.getLayouter().getMaxIntrinsicWidth(child, height / scale) * scale;
+ }
+
+ @Override
+ public Position transformPoint(DomElement element, Position pos) {
+ Rect elementRect = element.getRelativeBound();
+ double relX = pos.getX() - elementRect.getX();
+ double relY = pos.getY() - elementRect.getY();
+ return new Position(relX / scale, relY / scale);
+ }
+
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ DomElement value = buildContext.getChildren().get(0);
+
+ Rect original = value.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+ GlStateManager.scale(scale, scale, 1);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ (relMouseX - original.getX()) / scale,
+ (relMouseY - original.getY()) / scale, partialTicks, context, value);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicHeight.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicHeight.java
new file mode 100644
index 00000000..e9a01cf0
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicHeight.java
@@ -0,0 +1,65 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class IntrinsicHeight extends AnnotatedExportOnlyWidget implements Layouter {
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ DomElement elem = buildContext.getChildren().get(0);
+ double height = elem.getLayouter().getMaxIntrinsicHeight(elem, constraintBox.getMaxWidth() == Double.POSITIVE_INFINITY ? 0 : constraintBox.getMaxWidth());
+ Size size = elem.getLayouter().layout(elem, new ConstraintBox(
+ constraintBox.getMinWidth(), constraintBox.getMaxWidth(), height, height
+ ));
+ elem.setRelativeBound(new Rect(0,0,size.getWidth(), size.getHeight()));
+ return size;
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ DomElement elem = buildContext.getChildren().get(0);
+ return elem.getLayouter().getMaxIntrinsicWidth(elem, height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ DomElement elem = buildContext.getChildren().get(0);
+ return elem.getLayouter().getMaxIntrinsicHeight(elem, width);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicWidth.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicWidth.java
new file mode 100644
index 00000000..c731acb8
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/IntrinsicWidth.java
@@ -0,0 +1,65 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class IntrinsicWidth extends AnnotatedExportOnlyWidget implements Layouter {
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ DomElement elem = buildContext.getChildren().get(0);
+ double width = elem.getLayouter().getMaxIntrinsicWidth(elem, constraintBox.getMaxHeight() == Double.POSITIVE_INFINITY ? 0 : constraintBox.getMaxHeight());
+ Size size = elem.getLayouter().layout(elem, new ConstraintBox(
+ width, width, constraintBox.getMinHeight(), constraintBox.getMaxHeight()
+ ));
+ elem.setRelativeBound(new Rect(0,0,size.getWidth(), size.getHeight()));
+ return size;
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ DomElement elem = buildContext.getChildren().get(0);
+ return elem.getLayouter().getMaxIntrinsicWidth(elem, height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ DomElement elem = buildContext.getChildren().get(0);
+ return elem.getLayouter().getMaxIntrinsicHeight(elem, width);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Line.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Line.java
new file mode 100644
index 00000000..b94a22aa
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Line.java
@@ -0,0 +1,139 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.client.renderer.GlStateManager;
+import org.lwjgl.opengl.GL11;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Line extends AnnotatedExportOnlyWidget implements Layouter, Renderer{
+ @Export(attributeName = "thickness")
+ public final BindableAttribute<Float> thickness =new BindableAttribute<>(Float.class, 1.0f);
+
+ @Export(attributeName = "factor")
+ public final BindableAttribute<Integer> factor =new BindableAttribute<>(Integer.class, 1); // normal line
+
+ @Export(attributeName = "pattern")
+ public final BindableAttribute<Short> pattern =new BindableAttribute<>(Short.class, null); // normal line
+
+ @Export(attributeName = "color")
+ public final BindableAttribute<Integer> color = new BindableAttribute<>(Integer.class, 0xFF000000);
+ @Export(attributeName = "dir")
+ public final BindableAttribute<Orientation> direction = new BindableAttribute<>(Orientation.class, Orientation.HORIZONTAL);
+
+
+ public float r = 0;
+ public float g = 0;
+ public float b = 0;
+ public float a = 1;
+
+
+ public static enum Orientation {
+ VERTICAL, HORIZONTAL
+ }
+
+ public Line() {
+ thickness.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ color.addOnUpdate((old, color) -> {
+ a = ((color >> 24) & 0xFF) / 255.0f;
+ r = ((color >> 16) &0xFF) /255.0f;
+ g = ((color >> 8) & 0xFF) / 255.0f;
+ b = (color & 0xFF) / 255.0f;
+ });
+ }
+
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ double thickness = this.thickness.getValue();
+ Orientation orientation = direction.getValue();
+ if (orientation == Orientation.HORIZONTAL) {
+ return new Size(
+ constraintBox.getMaxWidth(),
+ Layouter.clamp( thickness, constraintBox.getMinHeight(), constraintBox.getMaxHeight())
+ );
+ } else {
+ return new Size(
+ Layouter.clamp( thickness, constraintBox.getMinWidth(), constraintBox.getMaxWidth()),
+ constraintBox.getMaxHeight()
+ );
+ }
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return direction.getValue() == Orientation.HORIZONTAL ? 0 : this.thickness.getValue();
+ }
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return direction.getValue() == Orientation.VERTICAL ? 0 : this.thickness.getValue();
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ double w = buildContext.getSize().getWidth(), h = buildContext.getSize().getHeight();
+
+ GlStateManager.color(r,g,b,a);
+ GlStateManager.disableTexture2D();
+ GL11.glLineWidth((float) (thickness.getValue() *
+ Double.max(
+ buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight(),
+ buildContext.getAbsBounds().getWidth()/buildContext.getSize().getWidth()
+ ))
+ );
+
+ Short pattern = this.pattern.getValue();
+ if (pattern != null) {
+ GL11.glLineStipple(factor.getValue(), pattern);
+ GL11.glEnable(GL11.GL_LINE_STIPPLE);
+ }
+
+ GL11.glBegin(GL11.GL_LINES);
+ if (direction.getValue() == Orientation.HORIZONTAL) {
+ GL11.glVertex2d(0,h/2.0f);
+ GL11.glVertex2d(w, h/2.0f);
+ } else {
+ GL11.glVertex2d(w/2.0f,0);
+ GL11.glVertex2d(w/2.0f, h);
+ }
+ GL11.glEnd();
+ GlStateManager.enableTexture2D();
+
+ if (pattern != null) {
+ GL11.glDisable(GL11.GL_LINE_STIPPLE);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Measure.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Measure.java
new file mode 100644
index 00000000..bf7a037b
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Measure.java
@@ -0,0 +1,69 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Measure extends AnnotatedExportOnlyWidget implements Layouter {
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ if (buildContext.getChildren().isEmpty()) {
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+
+ DomElement childCtx = buildContext.getChildren().get(0);
+
+ Size dim = childCtx.getLayouter().layout(childCtx, constraintBox);
+ childCtx.setRelativeBound(new Rect(0,0, dim.getWidth(), dim.getHeight()));
+ size.setValue(dim);
+ return new Size(dim.getWidth(), dim.getHeight());
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0), height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0), width);
+ }
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+
+ @Export(attributeName = "size")
+ public final BindableAttribute<Size> size = new BindableAttribute<>(Size.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Navigator.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Navigator.java
new file mode 100644
index 00000000..04c1c9b4
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Navigator.java
@@ -0,0 +1,79 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Stack;
+
+public class Navigator extends AnnotatedExportOnlyWidget {
+ public Stack<Widget> widgets = new Stack<>();
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+ public Widget current;
+
+ public Navigator() {
+ child.addOnUpdate((old, a) -> {
+ if (current == null) current = a;
+ });
+ }
+
+ public void openPage(Widget widget) {
+ widgets.push(current);
+ getDomElement().removeElement(getDomElement().getChildren().get(0));
+ getDomElement().addElement(widget.createDomElement(getDomElement()));
+ current = widget;
+ }
+
+ public void goBack() {
+ getDomElement().removeElement(getDomElement().getChildren().get(0));
+ Widget page;
+ if (widgets.isEmpty()) page = child.getValue();
+ else page = widgets.pop();
+ getDomElement().addElement(page.createDomElement(getDomElement()));
+ current = page;
+ }
+
+ public static Navigator getNavigator(DomElement element) {
+ return element.getContext().getValue(Navigator.class, "navigator");
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(current);
+ }
+
+ @Override
+ public void onMount() {
+ getDomElement().getContext().CONTEXT.put("navigator", this);
+ }
+
+ @Override
+ public void onUnmount() {
+ getDomElement().getContext().CONTEXT.remove("navigator");
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/NegativeStencil.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/NegativeStencil.java
new file mode 100644
index 00000000..8e2b268c
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/NegativeStencil.java
@@ -0,0 +1,112 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.client.renderer.GlStateManager;
+import org.lwjgl.opengl.GL11;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NegativeStencil extends AnnotatedExportOnlyWidget implements Renderer {
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> children = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_stencil")
+ public final BindableAttribute<Widget> stencil = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Arrays.asList(children.getValue(), stencil.getValue());
+ }
+
+ @Override
+ protected Layouter createLayouter() {
+ return Stack.StackingLayouter.INSTANCE;
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ DomElement theThingToDraw = buildContext.getChildren().get(0);
+ DomElement stencil = buildContext.getChildren().get(1);
+
+ GL11.glEnable(GL11.GL_STENCIL_TEST);
+ GL11.glClearStencil(0);
+ GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
+
+ GL11.glStencilMask(0xFF);
+ GL11.glColorMask(false, false, false, false);
+ GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
+ GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_REPLACE, GL11.GL_REPLACE);
+
+ GlStateManager.pushMatrix();
+ Rect original = stencil.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ stencil.setAbsBounds(elementABSBound);
+
+ stencil.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks,context, stencil);
+ GlStateManager.popMatrix();
+
+
+ GL11.glStencilFunc(GL11.GL_EQUAL, 0, 0xFF);
+ GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
+ GL11.glColorMask(true, true, true, true);
+
+
+ original = theThingToDraw.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ theThingToDraw.setAbsBounds(elementABSBound);
+
+ theThingToDraw.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks,context, theThingToDraw);
+
+ GL11.glDisable(GL11.GL_STENCIL_TEST);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Padding.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Padding.java
new file mode 100644
index 00000000..6e2d2725
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Padding.java
@@ -0,0 +1,97 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Padding extends AnnotatedExportOnlyWidget implements Layouter {
+
+ @Export(attributeName = "left")
+ public final BindableAttribute<Double> left = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "right")
+ public final BindableAttribute<Double> right = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "top")
+ public final BindableAttribute<Double> top = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "bottom")
+ public final BindableAttribute<Double> bottom = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+ public Padding() {
+ left.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ right.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ top.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ bottom.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(child.getValue());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ DomElement domElement = buildContext.getChildren().get(0);
+
+ double width = (left.getValue() + right.getValue());
+ double height = (top.getValue() + bottom.getValue());
+ Size dim = domElement.getLayouter().layout(domElement, new ConstraintBox(
+ constraintBox.getMinWidth() - width,
+ constraintBox.getMaxWidth() - width,
+ constraintBox.getMinHeight() - height,
+ constraintBox.getMaxHeight() - height
+ ));
+
+ domElement.setRelativeBound(new Rect(
+ left.getValue().intValue(),
+ top.getValue().intValue(),
+ dim.getWidth(),
+ dim.getHeight()
+ ));
+
+
+ return new Size(dim.getWidth() + width, dim.getHeight() + height);
+ }
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return left.getValue() + right.getValue() +
+ (buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0),
+ Math.max(0, height - top.getValue() - bottom.getValue())));
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return top.getValue() + bottom.getValue() + (
+ buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0),
+ Math.max(0, width - left.getValue() - right.getValue())));
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Placeholder.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Placeholder.java
new file mode 100644
index 00000000..2670c0db
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Placeholder.java
@@ -0,0 +1,66 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.NullLayouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import net.minecraft.client.renderer.GlStateManager;
+import org.lwjgl.opengl.GL11;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Placeholder extends AnnotatedExportOnlyWidget implements Renderer {
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ double w = buildContext.getSize().getWidth(), h = buildContext.getSize().getHeight();
+ context.drawRect(0,0,w,h, 0xFFFFFFFF);
+ GlStateManager.color(0,0,0,1);
+ GlStateManager.disableTexture2D();
+ GL11.glLineWidth(1.0f);
+ GL11.glBegin(GL11.GL_LINE_LOOP);
+ GL11.glVertex2d(0,0);
+ GL11.glVertex2d(w, 0);
+ GL11.glVertex2d(w, h);
+ GL11.glVertex2d(0, h);
+ GL11.glEnd();
+ GL11.glBegin(GL11.GL_LINES);
+ GL11.glVertex2d(0,0);
+ GL11.glVertex2d(w,h);
+ GL11.glVertex2d(w,0);
+ GL11.glVertex2d(0, h);
+ GL11.glEnd();
+ }
+
+ @Override
+ protected Layouter createLayouter() {
+ return NullLayouter.INSTANCE;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/RoundRect.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/RoundRect.java
new file mode 100644
index 00000000..217972e5
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/RoundRect.java
@@ -0,0 +1,73 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.SingleChildRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.mod.shader.ShaderManager;
+import kr.syeyoung.dungeonsguide.mod.shader.ShaderProgram;
+import net.minecraft.client.Minecraft;
+import org.lwjgl.opengl.GL20;
+
+import java.util.Collections;
+import java.util.List;
+
+public class RoundRect extends AnnotatedExportOnlyWidget {
+ @Export(attributeName = "radius")
+ public final BindableAttribute<Double> radius = new BindableAttribute<>(Double.class, 0.0);
+
+ @Export(attributeName = "backgroundColor")
+ public final BindableAttribute<Integer> color = new BindableAttribute<>(Integer.class, 0xFFFFFFFF);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+ @Override
+ protected Renderer createRenderer() {
+ return new BRender();
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return child.getValue() == null ? Collections.EMPTY_LIST : Collections.singletonList(child.getValue());
+ }
+
+ public class BRender extends SingleChildRenderer {
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext renderingContext, DomElement buildContext) {
+ ShaderProgram shaderProgram = ShaderManager.getShader("shaders/roundrect");
+ shaderProgram.useShader();
+ shaderProgram.uploadUniform("radius", (float)(radius.getValue() * buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth()));
+ shaderProgram.uploadUniform("halfSize", (float) buildContext.getAbsBounds().getWidth()/2, (float) buildContext.getAbsBounds().getHeight()/2);
+ shaderProgram.uploadUniform("centerPos",
+ (float) (buildContext.getAbsBounds().getX()+buildContext.getAbsBounds().getWidth()/2),
+ Minecraft.getMinecraft().displayHeight - (float) (buildContext.getAbsBounds().getY() + buildContext.getAbsBounds().getHeight()/2));
+ shaderProgram.uploadUniform("smoothness", 0.0f);
+ renderingContext.drawRect(0,0,buildContext.getSize().getWidth(), buildContext.getSize().getHeight(), color.getValue());
+ GL20.glUseProgram(0);
+ super.doRender(absMouseX, absMouseY, relMouseX, relMouseY, partialTicks, renderingContext, buildContext);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Row.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Row.java
new file mode 100644
index 00000000..05d89d75
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Row.java
@@ -0,0 +1,263 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.OnlyChildrenRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.WidgetList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Row extends AnnotatedExportOnlyWidget implements Layouter {
+ public static enum CrossAxisAlignment {
+ START, CENTER, END, STRETCH
+ }
+ public static enum MainAxisAlignment {
+ START, CENTER, END, SPACE_EVENLY, SPACE_AROUND, SPACE_BETWEEN
+ }
+
+ @Export(attributeName = "crossAlign")
+ public final BindableAttribute<CrossAxisAlignment> vAlign = new BindableAttribute<>(CrossAxisAlignment.class, CrossAxisAlignment.CENTER);
+
+ @Export(attributeName = "mainAlign")
+ public final BindableAttribute<MainAxisAlignment> hAlign = new BindableAttribute<>(MainAxisAlignment.class, MainAxisAlignment.START);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<WidgetList> children = new BindableAttribute<>(WidgetList.class);
+
+
+ @Export(attributeName = "api")
+ public final BindableAttribute<Row> rowAPI = new BindableAttribute<>(Row.class, this);
+ public Row() {
+ hAlign.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ vAlign.addOnUpdate((a,b) -> getDomElement().requestRelayout());
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return children.getValue();
+ }
+
+ @Override
+ protected Renderer createRenderer() {
+ return OnlyChildrenRenderer.INSTANCE;
+ }
+
+
+ public void addWidget(Widget widget) {
+ if (getDomElement().getWidget() == null) {
+ children.getValue().add(widget);
+ } else {
+ DomElement domElement = widget.createDomElement(getDomElement());
+ getDomElement().addElement(domElement);
+ }
+ }
+
+ public void removeWidget(Widget widget) {
+ getDomElement().removeElement(widget.getDomElement());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ double height = 0;
+ double width = 0;
+ double effwidth = constraintBox.getMaxWidth(); // max does not count for row.
+
+ CrossAxisAlignment crossAxisAlignment = vAlign.getValue();
+ MainAxisAlignment mainAxisAlignment = hAlign.getValue();
+ Map<DomElement, Size> saved = new HashMap<>();
+
+ for (DomElement child : buildContext.getChildren()) {
+ if (!(child.getWidget() instanceof Flexible)) {
+ Size requiredSize = child.getLayouter().layout(child, new ConstraintBox(
+ 0, Double.POSITIVE_INFINITY,
+ crossAxisAlignment == CrossAxisAlignment.STRETCH ? constraintBox.getMaxHeight() : 0, constraintBox.getMaxHeight()
+ ));
+ saved.put(child, requiredSize);
+ height = Math.max(height, requiredSize.getHeight());
+ width += requiredSize.getWidth();
+ }
+ }
+
+
+ boolean flexFound = false;
+ int sumFlex = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ sumFlex += Math.max(1, ((Flexible) child.getWidget()).flex.getValue());
+ flexFound =true;
+ }
+ }
+
+ if (flexFound && effwidth == Double.POSITIVE_INFINITY) throw new IllegalStateException("Max width can not be infinite with flex elements");
+ else if (effwidth == Double.POSITIVE_INFINITY) effwidth = width;
+
+ if (flexFound) {
+ double remainingWidth = effwidth - width;
+ if (remainingWidth < 0) remainingWidth = 0;
+ double widthPer = remainingWidth / sumFlex;
+
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ Size requiredSize = child.getLayouter().layout(child, new ConstraintBox(
+ 0, widthPer * ((Flexible) child.getWidget()).flex.getValue(),
+ crossAxisAlignment == CrossAxisAlignment.STRETCH ? constraintBox.getMaxHeight() : 0, constraintBox.getMaxHeight()
+ ));
+ saved.put(child, requiredSize);
+ height = Math.max(height, requiredSize.getHeight());
+ width += requiredSize.getWidth();
+ }
+ }
+ }
+
+
+ height = constraintBox.getMaxHeight() == Double.POSITIVE_INFINITY ? height : constraintBox.getMaxHeight();
+
+
+
+ double startx = 0;
+ double widthDelta = 0;
+
+ if (mainAxisAlignment == MainAxisAlignment.CENTER)
+ startx = (effwidth - width) / 2;
+ else if (mainAxisAlignment == MainAxisAlignment.END)
+ startx = effwidth - width;
+ else if (mainAxisAlignment == MainAxisAlignment.SPACE_BETWEEN) {
+ double remaining = effwidth - width;
+ if (remaining > 0) {
+ startx = 0;
+ widthDelta = remaining / (buildContext.getChildren().size()-1);
+ } else {
+ startx = (effwidth - width) / 2;
+ }
+ } else if (mainAxisAlignment == MainAxisAlignment.SPACE_EVENLY) {
+ double remaining = effwidth - width;
+ if (remaining > 0) {
+ widthDelta = remaining / (buildContext.getChildren().size()+1);
+ startx = widthDelta;
+ } else {
+ startx = (effwidth - width) / 2;
+ }
+ } else if (mainAxisAlignment == MainAxisAlignment.SPACE_AROUND) {
+ double remaining = effwidth - width;
+ if (remaining > 0) {
+ widthDelta = 2 * remaining / buildContext.getChildren().size();
+ startx = widthDelta / 2;
+ } else {
+ startx = (effwidth - width / 2);
+ }
+ }
+
+ for (DomElement child : buildContext.getChildren()) {
+ Size size = saved.get(child);
+
+ child.setRelativeBound(new Rect(
+ startx,
+ crossAxisAlignment == CrossAxisAlignment.START ? 0 :
+ crossAxisAlignment == CrossAxisAlignment.CENTER ? (height-size.getHeight())/2 :
+ crossAxisAlignment == CrossAxisAlignment.STRETCH ? (height-size.getHeight())/2 :
+ height - size.getHeight(),size.getWidth(), size.getHeight()
+ ));
+ startx += size.getWidth();
+ startx += widthDelta;
+ }
+ return new Size(
+ effwidth,
+ height
+ );
+ }
+
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ double prevHeight = 0;
+ double startingHeight = 0;
+ double maxHeight = 0;
+ int circuitBreaker = 0;
+ do {
+ circuitBreaker++;
+ if (circuitBreaker > 1000) {
+ try {
+ throw new RuntimeException("Caught in infinite loop welp");
+ } catch (Exception e) { e.printStackTrace(); }
+ break;
+ }
+ prevHeight = startingHeight;
+ startingHeight = maxHeight;
+ maxHeight = 0;
+ double widthTaken = 0;
+ int sumFlex = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (!(child.getWidget() instanceof Flexible)) {
+ widthTaken += child.getLayouter().getMaxIntrinsicWidth(child, startingHeight);
+ maxHeight = Double.max(maxHeight, child.getLayouter().getMaxIntrinsicHeight(child, 0));
+ } else {
+ sumFlex += ((Flexible) child.getWidget()).flex.getValue();
+ }
+ }
+ double leftOver = width - widthTaken;
+ if (leftOver <= 0) {
+ leftOver = 0;
+ if (prevHeight > 0)
+ maxHeight = prevHeight;
+ break;
+ }
+ if (sumFlex > 0) {
+ double per = leftOver / sumFlex;
+ if (width == 0) per = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ maxHeight = Double.max(maxHeight, child.getLayouter().getMaxIntrinsicHeight(child, per * ((Flexible) child.getWidget()).flex.getValue()));
+ }
+ }
+ }
+
+ if (leftOver == 0 || sumFlex == 0) break;
+ } while (startingHeight != maxHeight);
+ return maxHeight;
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ double width = 0;
+ double flex = 0;
+ double maxPer = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ if (child.getWidget() instanceof Flexible) {
+ flex += ((Flexible) child.getWidget()).flex.getValue();
+ maxPer = Double.max(maxPer, child.getLayouter().getMaxIntrinsicWidth(child, height) /
+ ((Flexible) child.getWidget()).flex.getValue());
+ } else {
+ width += child.getLayouter().getMaxIntrinsicWidth(child, height);
+ }
+ }
+ return width + maxPer * flex;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scaler.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scaler.java
new file mode 100644
index 00000000..bddc5bed
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scaler.java
@@ -0,0 +1,113 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Position;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.client.renderer.GlStateManager;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Scaler extends AnnotatedExportOnlyWidget implements Layouter, Renderer {
+
+ @Export(attributeName = "scale")
+ public final BindableAttribute<Double> scale = new BindableAttribute<>(Double.class, 1.0);
+
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(child.getValue());
+ }
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ DomElement child = buildContext.getChildren().get(0);
+ Size dims = child.getLayouter().layout(child, new ConstraintBox(
+ (constraintBox.getMinWidth() / scale.getValue()),
+ (constraintBox.getMaxWidth() / scale.getValue()),
+ (constraintBox.getMinHeight() / scale.getValue()),
+ (constraintBox.getMaxHeight() / scale.getValue())
+ ));
+ child.setRelativeBound(new Rect(0,0,
+ (dims.getWidth() * scale.getValue()),
+ (dims.getHeight() * scale.getValue())));
+ child.setSize(new Size(dims.getWidth(), dims.getHeight()));
+
+ return new Size(dims.getWidth() * scale.getValue(), dims.getHeight() * scale.getValue());
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ DomElement child = buildContext.getChildren().get(0);
+ return child.getLayouter().getMaxIntrinsicHeight(child, width / scale.getValue()) * scale.getValue();
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ DomElement child = buildContext.getChildren().get(0);
+ return child.getLayouter().getMaxIntrinsicWidth(child, height / scale.getValue()) * scale.getValue();
+ }
+
+ @Override
+ public Position transformPoint(DomElement element, Position pos) {
+ Rect elementRect = element.getRelativeBound();
+ double relX = pos.getX() - elementRect.getX();
+ double relY = pos.getY() - elementRect.getY();
+ return new Position(relX / scale.getValue(), relY / scale.getValue());
+ }
+
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ DomElement value = buildContext.getChildren().get(0);
+
+ Rect original = value.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+ GlStateManager.scale(scale.getValue(), scale.getValue(), 1);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ (relMouseX - original.getX()) / scale.getValue(),
+ (relMouseY - original.getY()) / scale.getValue(), partialTicks, context, value);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ScrollablePanel.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ScrollablePanel.java
new file mode 100644
index 00000000..523a880b
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ScrollablePanel.java
@@ -0,0 +1,136 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Passthrough;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import net.minecraft.util.ResourceLocation;
+
+@Passthrough(exportName = "_", bindName = "_", type = Widget.class)
+public class ScrollablePanel extends AnnotatedWidget {
+ @Bind(variableName = "contentX")
+ public final BindableAttribute<Double> contentX = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "contentY")
+ public final BindableAttribute<Double> contentY = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "contentSize")
+ public final BindableAttribute<Size> contentSize = new BindableAttribute<>(Size.class);
+ @Bind(variableName = "contentWidth")
+ public final BindableAttribute<Double> contentWidth = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "contentHeight")
+ public final BindableAttribute<Double> contentHeight = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "viewportSize")
+ public final BindableAttribute<Size> viewportSize = new BindableAttribute<>(Size.class);
+ @Bind(variableName = "viewportWidth")
+ public final BindableAttribute<Double> viewportWidth = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "viewportHeight")
+ public final BindableAttribute<Double> viewportHeight = new BindableAttribute<>(Double.class);
+
+ @Bind(variableName = "x")
+ public final BindableAttribute<Double> x = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "y")
+ public final BindableAttribute<Double> y = new BindableAttribute<>(Double.class);
+
+ @Export(attributeName = "direction")
+ @Bind(variableName = "direction")
+ public final BindableAttribute<Direction> direction = new BindableAttribute<>(Direction.class);
+ @Bind(variableName = "verticalThickness")
+ @Export(attributeName = "verticalThickness")
+ public final BindableAttribute<Double> vertThickness = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "horizontalThickness")
+ @Export(attributeName = "verticalThickness")
+ public final BindableAttribute<Double> horzThickness = new BindableAttribute<>(Double.class);
+
+ @Bind(variableName = "horzRef")
+ public final BindableAttribute<DomElement> horzRef = new BindableAttribute<>(DomElement.class);
+
+ @Bind(variableName = "vertRef")
+ public final BindableAttribute<DomElement> vertRef = new BindableAttribute<>(DomElement.class);
+ @AllArgsConstructor
+ @Getter
+ public enum Direction {
+ HORIZONTAL(true, false),
+ VERTICAL(false, true),
+ BOTH(true, true);
+
+ private boolean horizontal;
+ private boolean vertical;
+
+ }
+ public ScrollablePanel() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/scrollablePanel.gui"));
+
+ contentSize.addOnUpdate((old, neu) -> {
+ contentWidth.setValue(Math.max(0, neu.getWidth() - viewportWidth.getValue()));
+ contentHeight.setValue(Math.max(0, neu.getHeight() - viewportHeight.getValue()));
+ });
+
+ viewportSize.addOnUpdate((old, neu) -> {
+ viewportWidth.setValue(neu.getWidth());
+ viewportHeight.setValue(neu.getHeight());
+ contentWidth.setValue(Math.max(0, contentSize.getValue().getWidth() - viewportWidth.getValue()));
+ contentHeight.setValue(Math.max(0, contentSize.getValue().getHeight() - viewportHeight.getValue()));
+ });
+
+ x.addOnUpdate((old, neu) -> {
+ if (direction.getValue().isHorizontal())
+ contentX.setValue(-neu);
+ });
+ y.addOnUpdate((old, neu) ->{
+ if (direction.getValue().isVertical())
+ contentY.setValue(-neu);
+ });
+ direction.addOnUpdate((old, neu) -> {
+ vertThickness.setValue(neu.isVertical() ? 7 : 0.0);
+ horzThickness.setValue(neu.isHorizontal() ? 7 :0.0);
+ });
+
+ }
+
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ return false;
+ }
+
+ @Override
+ public boolean mouseScrolled(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int scrollAmount) {
+ if (direction.getValue().vertical) {
+ double old = this.y.getValue(), neu;
+ this.y.setValue(
+ neu = Layouter.clamp(this.y.getValue() + scrollAmount,0, contentHeight.getValue())
+ );
+ return old != neu;
+ } else if (direction.getValue().horizontal) {
+ double old = this.x.getValue(), neu;
+ this.x.setValue(
+ neu = Layouter.clamp(this.x.getValue() + scrollAmount,0, contentWidth.getValue())
+ );
+ return old != neu;
+ }
+ return false;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scrollbar.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scrollbar.java
new file mode 100644
index 00000000..89549107
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Scrollbar.java
@@ -0,0 +1,165 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.OnlyChildrenRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Passthrough;
+import net.minecraft.util.ResourceLocation;
+
+@Passthrough(exportName = "_track", bindName = "track", type = Widget.class)
+@Passthrough(exportName = "_thumb", bindName = "thumb", type = Widget.class)
+public class Scrollbar extends AnnotatedWidget {
+ // to set location and stuff
+ @Bind(variableName = "x")
+ public final BindableAttribute<Double> thumbX = new BindableAttribute<>(Double.class, 0.0);
+ @Bind(variableName = "y")
+ public final BindableAttribute<Double> thumbY = new BindableAttribute<>(Double.class, 0.0);
+ @Bind(variableName = "width")
+ public final BindableAttribute<Double> width = new BindableAttribute<>(Double.class, Double.POSITIVE_INFINITY);
+ @Bind(variableName = "height")
+ public final BindableAttribute<Double> height = new BindableAttribute<>(Double.class, Double.POSITIVE_INFINITY);
+ @Bind(variableName = "size")
+ public final BindableAttribute<Size> size = new BindableAttribute<>(Size.class);
+
+ @Export(attributeName = "thumbValue")
+ public final BindableAttribute<Double> thumbValue = new BindableAttribute<>(Double.class, 10.0);
+
+
+ @Export(attributeName = "minThumbSize")
+ public final BindableAttribute<Double> minimumThumbSize = new BindableAttribute<>(Double.class, 10.0);
+
+ @Export(attributeName = "min")
+ public final BindableAttribute<Double> min = new BindableAttribute<>(Double.class, 0.0);
+ @Export(attributeName = "max")
+ public final BindableAttribute<Double> max = new BindableAttribute<>(Double.class, 100.0);
+ @Export(attributeName = "current")
+ public final BindableAttribute<Double> current = new BindableAttribute<>(Double.class, 0.0);
+
+ @Export(attributeName = "orientation")
+ public final BindableAttribute<Line.Orientation> orientation = new BindableAttribute<>(Line.Orientation.class);
+
+ @Override
+ protected Renderer createRenderer() {
+ return OnlyChildrenRenderer.INSTANCE;
+ }
+
+ public Scrollbar() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/scrollBar.gui"));
+
+ thumbValue.addOnUpdate(this::updateStuff);
+ min.addOnUpdate(this::updateStuff);
+ max.addOnUpdate(this::updateStuff);
+ current.addOnUpdate(this::updateThumbLocation);
+ size.addOnUpdate((a,b) -> this.updateStuff(0,0));
+ updateStuff(0, 0);
+ }
+
+ private double per1, per2;
+
+ private void updateStuff(double _, double __) {
+ if (getDomElement().getSize() != null)
+ updatePers(getDomElement().getSize());
+ updateThumbLocation(0, 0);
+ }
+ private void updatePers(Size size) {
+
+ double min = this.min.getValue();
+ double max = this.max.getValue();
+ double thumbSize = this.thumbValue.getValue();
+
+ double movingLength = orientation.getValue() == Line.Orientation.VERTICAL ? size.getHeight() : size.getWidth();
+
+
+ per1 = movingLength / (max - min + thumbSize);
+ per2 = (max - min + thumbSize) / movingLength;
+
+ if (per1 * thumbSize > minimumThumbSize.getValue()) {
+ if (orientation.getValue() == Line.Orientation.VERTICAL) height.setValue(per1 * thumbSize);
+ else width.setValue(per1 * thumbSize);
+ } else {
+ movingLength -= minimumThumbSize.getValue();
+ per1 = movingLength / (max - min);
+ per2 = (max - min) / movingLength;
+
+ if (orientation.getValue() == Line.Orientation.VERTICAL) height.setValue(minimumThumbSize.getValue());
+ else width.setValue(minimumThumbSize.getValue());
+ }
+ }
+
+ private void updateThumbLocation(double newCurrent, double newMin) {
+ double location = per1 * (current.getValue() - min.getValue());
+
+ if (orientation.getValue() == Line.Orientation.VERTICAL) thumbY.setValue(location);
+ else thumbX.setValue(location);
+ }
+
+
+ private double movingDir;
+ private double startCurr;
+ private boolean moving = false;
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ getDomElement().obtainFocus();
+ if (orientation.getValue() == Line.Orientation.VERTICAL) movingDir = relMouseY;
+ else movingDir = relMouseX;
+
+ startCurr = current.getValue();
+ moving = true;
+ return true;
+ }
+
+ @Override
+ public void mouseClickMove(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int clickedMouseButton, long timeSinceLastClick) {
+ double old = movingDir;
+ double movingDir = 0;
+ if (orientation.getValue() == Line.Orientation.VERTICAL) movingDir = relMouseY;
+ else movingDir = relMouseX;
+
+ double dThing = movingDir - old;
+ double newVal = dThing * per2 + startCurr;
+ newVal = Layouter.clamp(newVal, min.getValue(), max.getValue());
+ this.current.setValue(newVal);
+ }
+
+ @Override
+ public void mouseReleased(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int state) {
+ moving = false;
+ }
+
+ @Override
+ public boolean mouseScrolled(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int scrollAmount) {
+ if (moving) return false;
+
+// double old = this.current.getValue();
+// double neu;
+// this.current.setValue(
+// neu = Layouter.clamp(this.current.getValue() + scrollAmount, min.getValue(), max.getValue())
+// );
+// return old != neu;
+ return false;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/SelectiveContainer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/SelectiveContainer.java
new file mode 100644
index 00000000..603c527c
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/SelectiveContainer.java
@@ -0,0 +1,72 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SelectiveContainer extends AnnotatedExportOnlyWidget {
+ @Export(attributeName = "visible")
+ public final BindableAttribute<String> visibleChild = new BindableAttribute<>(String.class);
+
+ private final Map<String, BindableAttribute<Widget>> widgetMap = new HashMap<>();
+
+ public SelectiveContainer() {
+ visibleChild.addOnUpdate((old, neu) -> {
+ if (getDomElement().getChildren().size() > 0)
+ getDomElement().removeElement(getDomElement().getChildren().get(0));
+ if (getDomElement().getParent() != null) {
+ Widget widget = widgetMap.get("_"+visibleChild.getValue()).getValue();
+ getDomElement().addElement(widget.createDomElement(getDomElement()));
+ }
+ });
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widgetMap.get("_"+visibleChild.getValue()).getValue());
+ }
+
+ @Override
+ public <T> BindableAttribute<T> getExportedAttribute(String attributeName) {
+ BindableAttribute<T> bindableAttribute = super.getExportedAttribute(attributeName);
+ if (bindableAttribute == null && attributeName.startsWith("_")) {
+ BindableAttribute attribute = new BindableAttribute(Widget.class);
+ widgetMap.put(attributeName, attribute);
+ return attribute;
+ }
+ return bindableAttribute;
+ }
+
+ @Override
+ public void onUnmount() {
+ for (BindableAttribute<Widget> value : widgetMap.values()) {
+ value.unexportAll();
+ }
+ super.onUnmount();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Slot.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Slot.java
new file mode 100644
index 00000000..d239946b
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Slot.java
@@ -0,0 +1,60 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Slot extends AnnotatedExportOnlyWidget {
+ @Export(attributeName = "child")
+ public final BindableAttribute<Widget> replacement = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> original = new BindableAttribute<>(Widget.class);
+
+ public Slot() {
+ replacement.addOnUpdate(this::update);
+ original.addOnUpdate(this::update);
+ }
+
+ private void update(Widget widget, Widget widget1) {
+ if (this.getDomElement().getParent() == null) return;
+ getDomElement().removeElement(getDomElement().getChildren().get(0));
+
+ DomElement domElement = null;
+ if (replacement.getValue() != null) domElement = replacement.getValue().createDomElement(getDomElement());
+ else if (original.getValue() != null) domElement = original.getValue().createDomElement(getDomElement());
+
+ if (domElement != null)
+ getDomElement().addElement(domElement);
+ }
+
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ if (replacement.getValue() != null) return Collections.singletonList(replacement.getValue());
+ if (original.getValue() != null) return Collections.singletonList(original.getValue());
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stack.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stack.java
new file mode 100644
index 00000000..abcf4364
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stack.java
@@ -0,0 +1,113 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.WidgetList;
+import net.minecraft.client.renderer.GlStateManager;
+
+import java.util.List;
+
+public class Stack extends AnnotatedExportOnlyWidget implements Renderer {
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ for (int i = buildContext.getChildren().size() - 1; i >= 0; i --) {
+ DomElement value = buildContext.getChildren().get(i);
+ Rect original = value.getRelativeBound();
+ if (original == null) return;
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ if (i > 0)
+ value.getRenderer().doRender(-1, -1, -1, -1, partialTicks, context, value);
+ if (i == 0)
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks, context, value);
+ GlStateManager.popMatrix();
+ }
+ }
+
+ public static class StackingLayouter implements Layouter {
+ public static final StackingLayouter INSTANCE = new StackingLayouter();
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ double maxW = 0, maxH = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ Size dim = child.getLayouter().layout(child, constraintBox);
+ if (maxW< dim.getWidth()) maxW = dim.getWidth();
+ if (maxH< dim.getHeight()) maxH = dim.getHeight();
+ child.setRelativeBound(new Rect(0,0,dim.getWidth(), dim.getHeight()));
+ }
+ return new Size(maxW, maxH);
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ double max = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ max = Double.max(max, child.getLayouter().getMaxIntrinsicWidth(child, height));
+ }
+ return max;
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ double max = 0;
+ for (DomElement child : buildContext.getChildren()) {
+ max = Double.max(max, child.getLayouter().getMaxIntrinsicHeight(child, width));
+ }
+ return max;
+ }
+ }
+
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<WidgetList> widgets = new BindableAttribute<>(WidgetList.class);
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return widgets.getValue();
+ }
+
+ @Override
+ protected Layouter createLayouter() {
+ return StackingLayouter.INSTANCE;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stencil.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stencil.java
new file mode 100644
index 00000000..d9e45b24
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Stencil.java
@@ -0,0 +1,112 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.client.renderer.GlStateManager;
+import org.lwjgl.opengl.GL11;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Stencil extends AnnotatedExportOnlyWidget implements Renderer {
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> children = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "_stencil")
+ public final BindableAttribute<Widget> stencil = new BindableAttribute<>(Widget.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Arrays.asList(children.getValue(), stencil.getValue());
+ }
+
+ @Override
+ protected Layouter createLayouter() {
+ return Stack.StackingLayouter.INSTANCE;
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ DomElement theThingToDraw = buildContext.getChildren().get(0);
+ DomElement stencil = buildContext.getChildren().get(1);
+
+ GL11.glEnable(GL11.GL_STENCIL_TEST);
+ GL11.glClearStencil(0);
+ GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
+
+ GL11.glStencilMask(0xFF);
+ GL11.glColorMask(false, false, false, false);
+ GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
+ GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_REPLACE, GL11.GL_REPLACE);
+
+ GlStateManager.pushMatrix();
+ Rect original = stencil.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ stencil.setAbsBounds(elementABSBound);
+
+ stencil.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks,context, stencil);
+ GlStateManager.popMatrix();
+
+
+ GL11.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
+ GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
+ GL11.glColorMask(true, true, true, true);
+
+
+ original = theThingToDraw.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ theThingToDraw.setAbsBounds(elementABSBound);
+
+ theThingToDraw.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks,context, theThingToDraw);
+
+ GL11.glDisable(GL11.GL_STENCIL_TEST);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Text.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Text.java
new file mode 100644
index 00000000..761c7a0d
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Text.java
@@ -0,0 +1,205 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.BreakWord;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.RichText;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.TextSpan;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders.SingleColorShader;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.ParentDelegatingTextStyle;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class Text extends AnnotatedExportOnlyWidget {
+ @Export(attributeName = "text")
+ public final BindableAttribute<String> text = new BindableAttribute<>(String.class, "");
+ @Export(attributeName = "size")
+ public final BindableAttribute<Double> size = new BindableAttribute<>(Double.class, 8.0);
+
+ private final ParentDelegatingTextStyle textStyle = ParentDelegatingTextStyle.ofDefault();
+ private final RichText richText = new RichText(new TextSpan(textStyle, ""), BreakWord.WORD, false,RichText.TextAlign.LEFT);
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(richText);
+ }
+ public static enum WordBreak {
+ NEVER, WORD, LETTER
+ }
+ @Export(attributeName = "break")
+ public final BindableAttribute<WordBreak> wordBreak = new BindableAttribute<>(WordBreak.class, WordBreak.WORD);
+
+ @Export(attributeName = "lineSpacing")
+ public final BindableAttribute<Double> lineSpacing = new BindableAttribute<>(Double.class, 1.0);
+
+
+ public static enum TextAlign {
+ LEFT, CENTER, RIGHT
+ }
+ @Export(attributeName = "align")
+ public final BindableAttribute<TextAlign> textAlign = new BindableAttribute<>(TextAlign.class, TextAlign.LEFT);
+
+ @Export(attributeName = "color")
+ public final BindableAttribute<Integer> color = new BindableAttribute<>(Integer.class, 0xFF000000);
+
+ public Text() {
+ text.addOnUpdate((a,b) -> {
+ updateText();
+ });
+ wordBreak.addOnUpdate((a,b) -> {
+ richText.setBreakWord(b == WordBreak.WORD ? BreakWord.WORD : BreakWord.ALL);
+ });
+ lineSpacing.addOnUpdate((a,b) -> {
+ updateText();
+ });
+ size.addOnUpdate((a,b) -> {
+ updateText();
+ });
+ textAlign.addOnUpdate((a,b) -> {
+ richText.setAlign(b == TextAlign.LEFT ? RichText.TextAlign.LEFT : b == TextAlign.CENTER ? RichText.TextAlign.CENTER : RichText.TextAlign.RIGHT);
+ });
+ color.addOnUpdate((a,b) -> {
+ updateText();
+ });
+ }
+
+ public Text(String text, int color, TextAlign align, WordBreak wordBreak, double lineSpacing, double size) {
+ this();
+ this.text.setValue(text);
+ this.color.setValue(color);
+ this.textAlign.setValue(align);
+ this.wordBreak.setValue(wordBreak);
+ this.lineSpacing.setValue(lineSpacing);
+ this.size.setValue(size);
+ }
+
+ private void updateText() {
+ textStyle.textShader = new SingleColorShader(color.getValue());
+ textStyle.underlineShader = new SingleColorShader(color.getValue());
+ textStyle.strikeThroughShader = new SingleColorShader(color.getValue());
+ textStyle.topAscent = lineSpacing.getValue() - 1;
+ textStyle.size = size.getValue();
+ TextSpan textSpan = new TextSpan(textStyle, "");
+ for (TextSpan textSpan2 : parseText(text.getValue())) {
+ textSpan.addChild(textSpan2);
+ }
+ richText.setRootSpan(textSpan);
+ }
+
+ private static final int[] colorCode = new int[32];
+ static {
+ for(int i = 0; i < 32; ++i) {
+ int j = (i >> 3 & 1) * 85;
+ int k = (i >> 2 & 1) * 170 + j;
+ int l = (i >> 1 & 1) * 170 + j;
+ int i1 = (i & 1) * 170 + j;
+ if (i == 6) {
+ k += 85;
+ }
+
+ if (i >= 16) {
+ k /= 4;
+ l /= 4;
+ i1 /= 4;
+ }
+
+ colorCode[i] = (k & 255) << 16 | (l & 255) << 8 | i1 & 255;
+ }
+ }
+ private List<TextSpan> parseText(String text) {
+ Boolean boldStyle = null, strikethroughStyle = null, underlineStyle = null, italicStyle = null;
+ Integer textColor = null;
+ List<TextSpan> spans = new ArrayList<>();
+
+ StringBuilder stringBuilder = new StringBuilder();
+ for(int i = 0; i < text.length(); ++i) {
+ char c0 = text.charAt(i);
+ if (c0 == 167 && i + 1 < text.length()) {
+ if (!stringBuilder.toString().isEmpty()) {
+ spans.add(new TextSpan(
+ new ParentDelegatingTextStyle()
+ .setTextShader(textColor == null ? null : new SingleColorShader(textColor | 0xFF000000))
+ .setBold(boldStyle)
+ .setStrikeThroughShader(textColor == null ? null : new SingleColorShader(textColor | 0xFF000000))
+ .setStrikeThrough(strikethroughStyle)
+ .setUnderlineShader(textColor == null ? null : new SingleColorShader(textColor | 0xFF000000))
+ .setUnderline(underlineStyle)
+ .setItalics(italicStyle),
+ stringBuilder.toString()
+ ));
+ stringBuilder = new StringBuilder();
+ }
+
+
+ int i1 = "0123456789abcdefklmnor".indexOf(text.toLowerCase(Locale.ENGLISH).charAt(i + 1));
+ if (i1 < 16) {
+ boldStyle = false;
+ strikethroughStyle = false;
+ underlineStyle = false;
+ italicStyle = false;
+ if (i1 < 0) {
+ i1 = 15;
+ }
+ textColor = colorCode[i1];
+ } else if (i1 == 16) {
+ } else if (i1 == 17) {
+ boldStyle = true;
+ } else if (i1 == 18) {
+ strikethroughStyle = true;
+ } else if (i1 == 19) {
+ underlineStyle = true;
+ } else if (i1 == 20) {
+ italicStyle = true;
+ } else {
+ boldStyle = false;
+ strikethroughStyle = false;
+ underlineStyle = false;
+ italicStyle = false;
+ textColor = null;
+ }
+
+ ++i;
+ } else {
+ stringBuilder.append(c0);
+ }
+ }
+
+ if (!stringBuilder.toString().isEmpty()) {
+ spans.add(new TextSpan(
+ new ParentDelegatingTextStyle()
+ .setTextShader(textColor == null ? null : new SingleColorShader(textColor | 0xFF000000))
+ .setBold(boldStyle)
+ .setStrikeThroughShader(textColor == null ? null : new SingleColorShader(textColor | 0xFF000000))
+ .setStrikeThrough(strikethroughStyle)
+ .setUnderlineShader(textColor == null ? null : new SingleColorShader(textColor | 0xFF000000))
+ .setUnderline(underlineStyle)
+ .setItalics(italicStyle),
+ stringBuilder.toString()
+ ));
+ }
+ return spans;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/TextField.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/TextField.java
new file mode 100644
index 00000000..5cc977c8
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/TextField.java
@@ -0,0 +1,445 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.util.MathHelper;
+import org.lwjgl.input.Keyboard;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class TextField extends AnnotatedExportOnlyWidget implements Renderer, Layouter {
+
+ @Export(
+ attributeName = "value"
+ )
+ public final BindableAttribute<String> value = new BindableAttribute<>(String.class, "");
+
+ @Export(
+ attributeName = "placeholder"
+ )
+ public final BindableAttribute<String> placeholder = new BindableAttribute<>(String.class, "");
+
+ @Export(
+ attributeName = "color"
+ )
+ public final BindableAttribute<Integer> color = new BindableAttribute<>(Integer.class, 0xFFFFFFFF);
+
+
+ @Export(
+ attributeName = "placeholderColor"
+ )
+ public final BindableAttribute<Integer> placeholderColor = new BindableAttribute<>(Integer.class, 0xFFAAAAAA);
+
+
+ private int selectionStart = 0;
+ private int selectionEnd = 0;
+
+ private int cursor = 0;
+
+ private double xOffset = 0;
+
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return 0;
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return 15;
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ return new Size(constraintBox.getMaxWidth(), Layouter.clamp(15, constraintBox.getMinHeight(), constraintBox.getMaxHeight()));
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ Size bounds = getDomElement().getSize();
+
+ context.drawRect(0,0,bounds.getWidth(), bounds.getHeight(), getDomElement().isFocused() ? Color.white.getRGB() : Color.gray.getRGB());
+ context.drawRect(1,1,bounds.getWidth() - 1, bounds.getHeight() - 1, Color.black.getRGB());
+
+ Minecraft mc = Minecraft.getMinecraft();
+ context.pushClip(buildContext.getAbsBounds(), bounds, 1, 1, bounds.getWidth() -2, bounds.getHeight()-2);
+
+ String text = value.getValue();
+ FontRenderer fr = mc.fontRendererObj;
+ int y = (int) ((bounds.getHeight() - fr.FONT_HEIGHT) / 2);
+ GlStateManager.enableTexture2D();
+ fr.drawString(value.getValue(), (int) (3 - xOffset), y, color.getValue());
+ if (text.isEmpty())
+ fr.drawString(placeholder.getValue(), 3, y, placeholderColor.getValue());
+ // draw selection
+ if (getDomElement().isFocused()) {
+ if (selectionStart != -1) {
+ if (selectionEnd > text.length()) selectionEnd = text.length();
+ int startX = (int) (fr.getStringWidth(text.substring(0, selectionStart)) - xOffset);
+ int endX = (int) (fr.getStringWidth(text.substring(0, selectionEnd)) - xOffset);
+ Gui.drawRect( (3 + startX), y, (3 + endX), y + fr.FONT_HEIGHT, 0xFF00FF00);
+ GlStateManager.enableTexture2D();
+ fr.drawString(text.substring(selectionStart, selectionEnd), (int) (3 + startX), y, color.getValue());
+ }
+
+ // draw cursor
+ if (cursor != -1) {
+ if (cursor > text.length()) setCursor0(text.length());
+ int x = (int) (fr.getStringWidth(text.substring(0, cursor)) - xOffset);
+
+ if (System.currentTimeMillis() % 1500 < 750)
+ Gui.drawRect(3 + x, y, 4 + x, y + fr.FONT_HEIGHT, 0xFFFFFFFF);
+ }
+ }
+ context.popClip();
+ }
+
+ private void setCursor0(int cursor) {
+ if (cursor > value.getValue().length()) cursor = value.getValue().length();
+ if (cursor < 0) cursor = 0;
+ this.cursor = cursor;
+
+
+ int width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(value.getValue().substring(0, cursor));
+ double cursorX = width + 3- xOffset;
+ cursorX = MathHelper.clamp_double(cursorX,10, getDomElement().getSize().getWidth() - 10);
+ xOffset = width+ 3 - cursorX;
+ xOffset = MathHelper.clamp_double(xOffset, 0,Math.max(0, Minecraft.getMinecraft().fontRendererObj.getStringWidth(value.getValue()) - getDomElement().getSize().getWidth()+10));
+ }
+
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ getDomElement().obtainFocus();
+ Rect actualField = new Rect(1, 3,
+ getDomElement().getSize().getWidth() - 2,
+ getDomElement().getSize().getHeight() - 6);
+ if (!actualField.contains(relMouseX, relMouseY)) return false;
+
+
+
+ double relStartT = relMouseX-3;
+ double offseted = relStartT + xOffset;
+
+ selectionStart = -1;
+
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ for (int i = 0; i < value.getValue().length(); i++) {
+ int totalWidth = fr.getStringWidth(value.getValue().substring(0, i));
+ if (offseted < totalWidth) {
+ setCursor0(i);
+ return true;
+ }
+ }
+ setCursor0(value.getValue().length());
+ return true;
+ }
+
+ @Override
+ public void mouseClickMove(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int clickedMouseButton, long timeSinceLastClick) {
+ if (!getDomElement().isFocused()) return;
+ selectionStart = cursor;
+ selectionEnd = cursor;
+
+ double relStartT = relMouseX-3;
+ double offseted = relStartT + xOffset;
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ for (int i = 0; i < value.getValue().length(); i++) {
+ int totalWidth = fr.getStringWidth(value.getValue().substring(0, i));
+ if (offseted < totalWidth) {
+ if (i < cursor) {
+ selectionStart = i;
+ selectionEnd = cursor;
+ } else {
+ selectionStart = cursor;
+ selectionEnd = i;
+ }
+ return;
+ }
+ }
+ selectionEnd = value.getValue().length();
+ if (selectionStart == selectionEnd) {
+ selectionStart = -1;
+ }
+ }
+
+ @Override
+ public boolean mouseScrolled(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0, int scrollAmount) {
+ if (!getDomElement().isFocused()) return false;
+ double lastOffset = xOffset;
+ if (scrollAmount > 0) {
+ xOffset += 5;
+ } else if (scrollAmount < 0){
+ xOffset -= 5;
+ }
+ if (xOffset < 0) {
+ xOffset = 0;
+ }
+ int width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(value.getValue());
+ double overflow = getDomElement().getSize().getWidth() - 3 - width;
+
+
+ if (overflow >= 0) {
+ xOffset = 0;
+ } else if (width - xOffset + 10 < getDomElement().getSize().getWidth()) {
+ xOffset = width - getDomElement().getSize().getWidth()+10;
+ }
+ return lastOffset != xOffset;
+ }
+
+ @Override
+ public void keyHeld(char typedChar, int keyCode) {
+ this.keyPressed(typedChar, keyCode);
+ }
+
+ @Override
+ public void keyPressed(char typedChar, int keycode) {
+ if (selectionStart == -1) {
+ if (keycode == 199) { // home
+ setCursor0(0);
+ xOffset = 0;
+ return;
+ }
+
+ if (keycode == 207) { // end
+ setCursor0(value.getValue().length());
+
+ int width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(value.getValue());
+ xOffset = Math.max(0, width - getDomElement().getSize().getWidth()+10);
+ return;
+ }
+
+ if (keycode == 203) { // left
+ setCursor0(this.cursor-1);;
+ if (cursor < 0) setCursor0(0);
+ return;
+ }
+
+ if (keycode == 205) { // right
+ setCursor0(this.cursor+1);
+ if (cursor > value.getValue().length()) setCursor0(value.getValue().length());
+ return;
+ }
+
+ // backspace
+ if (keycode == 14 && cursor > 0) {
+ value.setValue(this.value.getValue().substring(0, cursor-1) + this.value.getValue().substring(cursor));
+ setCursor0(this.cursor-1);
+ return;
+ }
+
+ //del
+ if (keycode == 211 && cursor < value.getValue().length()) {
+ value.setValue(this.value.getValue().substring(0, cursor) + this.value.getValue().substring(cursor+1));
+ return;
+ }
+
+ // paste
+ boolean shouldPaste = false;
+ if (keycode == 47) {
+ if (Minecraft.isRunningOnMac) { // mac
+ if (Keyboard.isKeyDown(219) || Keyboard.isKeyDown(220)) {
+ shouldPaste = true;
+ }
+ } else { // literally everything else
+ if (Keyboard.isKeyDown(29) || Keyboard.isKeyDown(157)) {
+ shouldPaste = true;
+ }
+ }
+ }
+ if (shouldPaste) {
+ Transferable transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
+ if (transferable != null && transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ try {
+ Object theText = transferable.getTransferData(DataFlavor.stringFlavor);
+ value.setValue(
+ this.value.getValue().substring(0, this.cursor)
+ + theText
+ + this.value.getValue().substring(this.cursor));
+
+ cursor += theText.toString().length();
+ } catch (UnsupportedFlavorException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+
+ // text
+ if (isPrintableChar(typedChar)) {
+ value.setValue(
+ this.value.getValue().substring(0, this.cursor)
+ + typedChar
+ + this.value.getValue().substring(this.cursor));
+ this.setCursor0(this.cursor+1);;
+ return;
+ }
+ } else {
+ if (keycode == 199) { // home
+ setCursor0(0);
+ selectionStart = -1;
+ xOffset =0;
+ return;
+ }
+
+ if (keycode == 207) { // end
+ selectionStart = -1;
+ setCursor0(value.getValue().length());
+ int width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(value.getValue());
+ xOffset = Math.max(0, width - getDomElement().getSize().getWidth()+10);
+ return;
+ }
+
+ if (keycode == 203) { // left
+ setCursor0(selectionStart);
+ selectionStart = -1;
+ return;
+ }
+
+ if (keycode == 205) { // right
+ setCursor0(selectionEnd);
+ selectionStart = -1;
+ return;
+ }
+
+ // backspace
+ if (keycode == 14 && cursor > 0) {
+ value.setValue(this.value.getValue().substring(0, selectionStart) + this.value.getValue().substring(selectionEnd));
+ setCursor0(selectionStart);
+ selectionStart = -1;
+ return;
+ }
+
+ //del
+ if (keycode == 211 && cursor < value.getValue().length()) {
+ value.setValue(this.value.getValue().substring(0, selectionStart) + this.value.getValue().substring(selectionEnd));
+ setCursor0(selectionStart);
+ selectionStart = -1;
+ return;
+ }
+
+ // paste
+ boolean shouldPaste = false;
+ if (keycode == 47) {
+ if (Minecraft.isRunningOnMac) { // mac
+ if (Keyboard.isKeyDown(219) || Keyboard.isKeyDown(220)) {
+ shouldPaste = true;
+ }
+ } else { // literally everything else
+ if (Keyboard.isKeyDown(29) || Keyboard.isKeyDown(157)) {
+ shouldPaste = true;
+ }
+ }
+ }
+ if (shouldPaste) {
+ Transferable transferable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
+ if (transferable != null && transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ try {
+ Object theText = transferable.getTransferData(DataFlavor.stringFlavor);
+ value.setValue(
+ this.value.getValue().substring(0, this.selectionStart)
+ + theText
+ + this.value.getValue().substring(this.selectionEnd));
+ setCursor0(this.selectionStart + theText.toString().length());
+ } catch (UnsupportedFlavorException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ selectionStart = -1;
+ }
+ return;
+ }
+ boolean shouldCopy = false;
+ if (keycode == 46) {
+ if (Minecraft.isRunningOnMac) { // mac
+ if (Keyboard.isKeyDown(219) || Keyboard.isKeyDown(220)) {
+ shouldCopy = true;
+ }
+ } else { // literally everything else
+ if (Keyboard.isKeyDown(29) || Keyboard.isKeyDown(157)) {
+ shouldCopy = true;
+ }
+ }
+ }
+ if (shouldCopy) {
+ StringSelection selection = new StringSelection(value.getValue().substring(selectionStart, selectionEnd));
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(selection, selection);
+ return;
+ }
+
+ // text
+ if (isPrintableChar(typedChar)) {
+ value.setValue(
+ this.value.getValue().substring(0, this.selectionStart)
+ + typedChar
+ + this.value.getValue().substring(this.selectionEnd));
+ setCursor0(this.selectionStart + 1);
+ selectionStart = -1;
+ return;
+ }
+ }
+ }
+ public boolean isPrintableChar( char c ) {
+ Character.UnicodeBlock block = Character.UnicodeBlock.of( c );
+ return (!Character.isISOControl(c)) &&
+ c != KeyEvent.CHAR_UNDEFINED &&
+ block != null &&
+ block != Character.UnicodeBlock.SPECIALS;
+ }
+
+ @Override
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ if (getDomElement().getAbsBounds().contains(absMouseX, absMouseY))
+ getDomElement().setCursor(EnumCursor.BEAM_CURSOR);
+ return true;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ToggleButton.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ToggleButton.java
new file mode 100644
index 00000000..8b4e7256
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/ToggleButton.java
@@ -0,0 +1,104 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Passthrough;
+import kr.syeyoung.dungeonsguide.mod.utils.cursor.EnumCursor;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.util.ResourceLocation;
+
+@Passthrough(exportName = "_on", bindName = "wgtOn", type = Widget.class)
+@Passthrough(exportName = "_off", bindName = "wgtOff", type = Widget.class)
+@Passthrough(exportName = "_hoverOn", bindName = "wgtHoverOn", type = Widget.class)
+@Passthrough(exportName = "_hoverOff", bindName = "wgtHoverOff", type = Widget.class)
+public class ToggleButton extends AnnotatedWidget implements Renderer {
+
+ @Bind(variableName = "refOn")
+ public final BindableAttribute<DomElement> on = new BindableAttribute<DomElement>(DomElement.class);
+ @Bind(variableName = "refOff")
+ public final BindableAttribute<DomElement> off = new BindableAttribute<DomElement>(DomElement.class);
+ @Bind(variableName = "refHoverOn")
+ public final BindableAttribute<DomElement> hoverOn = new BindableAttribute<DomElement>(DomElement.class);
+ @Bind(variableName = "refHoverOff")
+ public final BindableAttribute<DomElement> hoverOff = new BindableAttribute<DomElement>(DomElement.class);
+
+
+ @Export(attributeName = "enabled")
+ public final BindableAttribute<Boolean> enabled = new BindableAttribute<>(Boolean.class);
+
+ public ToggleButton() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/toggleButton.gui"));
+ }
+
+ @Override
+ protected Layouter createLayouter() {
+ return Stack.StackingLayouter.INSTANCE;
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ boolean isHover = buildContext.getSize().contains(relMouseX, relMouseY);
+ DomElement value;
+ if (enabled.getValue()) {
+ value = isHover ? hoverOn.getValue() : on.getValue();
+ } else {
+ value = isHover ? hoverOff.getValue() : off.getValue();
+ }
+ Rect original = value.getRelativeBound();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks, context, value);
+ }
+
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ getDomElement().obtainFocus();
+ enabled.setValue(!enabled.getValue());
+ return true;
+ }
+
+ @Override
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ getDomElement().setCursor(EnumCursor.POINTING_HAND);
+ return true;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/UnconstrainedBox.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/UnconstrainedBox.java
new file mode 100644
index 00000000..b8ef7fdd
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/UnconstrainedBox.java
@@ -0,0 +1,71 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class UnconstrainedBox extends AnnotatedExportOnlyWidget implements Layouter {
+
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<Widget> widget = new BindableAttribute<>(Widget.class);
+ @Export(attributeName = "direction")
+ public final BindableAttribute<ScrollablePanel.Direction> direction = new BindableAttribute<>(ScrollablePanel.Direction.class, ScrollablePanel.Direction.BOTH);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(widget.getValue());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ if (buildContext.getChildren().isEmpty()) {
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+ DomElement childCtx = buildContext.getChildren().get(0);
+
+ Size dim = childCtx.getLayouter().layout(childCtx, new ConstraintBox(0,
+ direction.getValue().isHorizontal() ? Double.POSITIVE_INFINITY : constraintBox.getMaxWidth(),
+ 0, direction.getValue().isVertical() ? Double.POSITIVE_INFINITY : constraintBox.getMaxHeight()));
+ childCtx.setRelativeBound(new Rect(0,0, dim.getWidth(), dim.getHeight()));
+ return new Size(dim.getWidth(), dim.getHeight());
+ }
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0), height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0), width);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Wrap.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Wrap.java
new file mode 100644
index 00000000..7105d498
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/Wrap.java
@@ -0,0 +1,130 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.OnlyChildrenRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.WidgetList;
+
+import java.util.List;
+
+public class Wrap extends AnnotatedExportOnlyWidget implements Layouter {
+
+ @Export(attributeName = "minimumWidth")
+ public final BindableAttribute<Double> minimumWidth = new BindableAttribute<>(Double.class);
+ @Export(attributeName = "gap")
+ public final BindableAttribute<Double> gap = new BindableAttribute<>(Double.class);
+
+ @Export(attributeName = "_")
+ public final BindableAttribute<WidgetList> widgetListBindableAttribute = new BindableAttribute<>(WidgetList.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return widgetListBindableAttribute.getValue();
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ List<DomElement> elements = buildContext.getChildren();
+ if (elements.isEmpty()) return new Size(constraintBox.getMinWidth(),constraintBox.getMinHeight());
+ int itemPerRow = (int) ((constraintBox.getMaxWidth()+gap.getValue()) / (minimumWidth.getValue() + gap.getValue()));
+ if (itemPerRow == 0) itemPerRow = 1;
+ if (itemPerRow == Integer.MAX_VALUE) itemPerRow = elements.size();
+ int rows = (int) Math.ceil((double)elements.size() / itemPerRow);
+
+ double maxWidth = (constraintBox.getMaxWidth() + gap.getValue()) / itemPerRow - gap.getValue();
+ double maxHeight = (constraintBox.getMaxHeight() + gap.getValue()) / rows - gap.getValue();
+ double h = 0;
+ for (int y = 0; y < rows; y++) {
+ double maxH = 0;
+ for (int x = 0; x < itemPerRow; x++) {
+ if (elements.size() <= y * itemPerRow + x) continue;
+ DomElement element = elements.get(y * itemPerRow + x);
+ double height = element.getLayouter().getMaxIntrinsicHeight(element, maxWidth);
+ if (height > maxH) maxH = height;
+ }
+ for (int x = 0; x < itemPerRow; x++) {
+ if (elements.size() <= y * itemPerRow + x) continue;
+ DomElement element = elements.get(y * itemPerRow + x);
+ Size size = element.getLayouter().layout(element, new ConstraintBox(
+ maxWidth, maxWidth, maxH, maxH
+ ));
+ element.setRelativeBound(new Rect(
+ x * (maxWidth + gap.getValue()),
+ h,
+ size.getWidth(),
+ size.getHeight()
+ ));
+ }
+
+ h += maxH + gap.getValue();
+ }
+
+ return new Size(constraintBox.getMaxWidth(), Layouter.clamp(h - gap.getValue(), 0, constraintBox.getMaxHeight()));
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().stream()
+ .map(a -> a.getLayouter().getMaxIntrinsicWidth(a, height) + gap.getValue())
+ .reduce(Double::sum)
+ .orElse(gap.getValue()) - gap.getValue();
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ List<DomElement> elements = buildContext.getChildren();
+ if (elements.isEmpty()) return 0;
+ int itemPerRow = (int) ((width+gap.getValue()) / (minimumWidth.getValue() + gap.getValue()));
+ if (itemPerRow == 0) itemPerRow = 1;
+ if (itemPerRow == Integer.MAX_VALUE) itemPerRow = elements.size();
+ int rows = (int) Math.ceil((double)elements.size() / itemPerRow);
+ double maxWidth = (width + gap.getValue()) / itemPerRow - gap.getValue();
+
+ double sum = 0;
+ for (int y = 0; y < rows; y++) {
+ double maxH = 0;
+ for (int x = 0; x < itemPerRow; x++) {
+ if (elements.size() <= y * itemPerRow + x) continue;
+ DomElement element = elements.get(y * itemPerRow + x);
+ double h = element.getLayouter().getMaxIntrinsicHeight(element, maxWidth);
+ if (h > maxH) maxH = h;
+ }
+ sum += maxH + gap.getValue();
+ }
+ sum -= gap.getValue();
+ if (sum < 0) sum = 0;
+
+ return sum;
+ }
+
+ @Override
+ protected Renderer createRenderer() {
+ return OnlyChildrenRenderer.INSTANCE;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/Image.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/Image.java
new file mode 100644
index 00000000..422a5a41
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/Image.java
@@ -0,0 +1,60 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.image;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import lombok.AllArgsConstructor;
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.Collections;
+import java.util.List;
+
+@AllArgsConstructor
+public class Image extends Widget implements Renderer {
+ public final ResourceLocation location;
+ public final int uvX;
+ public final int uvY ;
+ public final int textureWidth;
+ public final int textureHeight;
+ public final int uvWidth;
+ public final int uvHeight;
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ Minecraft.getMinecraft().getTextureManager().bindTexture(location);
+ context.drawScaledCustomSizeModalRect(0, 0,
+ uvX,
+ uvY,
+ uvWidth,
+ uvHeight,
+ buildContext.getSize().getWidth(),
+ buildContext.getSize().getHeight(),
+ textureWidth,
+ textureHeight);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ImageTexture.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ImageTexture.java
new file mode 100644
index 00000000..e5ceec72
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ImageTexture.java
@@ -0,0 +1,171 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.image;
+
+
+import kr.syeyoung.dungeonsguide.mod.DungeonsGuide;
+import lombok.Data;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.texture.DynamicTexture;
+import net.minecraft.client.renderer.texture.TextureManager;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.util.ResourceLocation;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+@Data
+public class ImageTexture {
+ private String url;
+ private BufferedImage image;
+ private DynamicTexture previewTexture;
+ private ResourceLocation resourceLocation;
+
+ private int width;
+ private int height;
+ private int frames;
+ private int size;
+
+ private long startedPlayingAt = -1;
+
+ private int delayTime;
+
+ public void buildGLThings() {
+ previewTexture = new DynamicTexture(image);
+ resourceLocation = Minecraft.getMinecraft().getTextureManager().getDynamicTextureLocation("dgurl/"+url, previewTexture);
+ }
+
+ public ImageTexture(String url) throws IOException {
+ this.url = url;
+
+ URL urlObj = new URL(url);
+ HttpURLConnection huc = (HttpURLConnection) urlObj.openConnection();
+ huc.addRequestProperty("User-Agent", "DungeonsGuideMod (dungeons.guide, 1.0)");
+ ImageInputStream imageInputStream = ImageIO.createImageInputStream(huc.getInputStream());
+ Iterator<ImageReader> readers = ImageIO.getImageReaders(imageInputStream);
+ if(!readers.hasNext()) throw new IOException("No image reader what" + url);
+ ImageReader reader = readers.next();
+ reader.setInput(imageInputStream);
+ frames = reader.getNumImages(true);
+ BufferedImage dummyFrame = reader.read(0);
+ width = dummyFrame.getWidth(); height = dummyFrame.getHeight();
+
+ IIOMetadata imageMetaData = reader.getImageMetadata(0);
+ String metaFormatName = imageMetaData.getNativeMetadataFormatName();
+
+ IIOMetadataNode root = (IIOMetadataNode)imageMetaData.getAsTree(metaFormatName);
+
+ IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
+
+ try {
+ delayTime = Integer.parseInt(graphicsControlExtensionNode.getAttribute("delayTime")) * 10;
+ } catch (Exception e) {
+ delayTime = 1000;
+ }
+
+ image = new BufferedImage(width, height * frames, dummyFrame.getType());
+ Graphics2D graphics2D = image.createGraphics();
+
+ for (int i = 0; i < frames; i++) {
+ BufferedImage bufferedImage = reader.read(i);
+ graphics2D.drawImage(bufferedImage, 0, i*height, null);
+ }
+ reader.dispose(); imageInputStream.close(); huc.disconnect();
+ }
+
+
+ private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName) {
+ int nNodes = rootNode.getLength();
+ for (int i = 0; i < nNodes; i++) {
+ if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName)== 0) {
+ return((IIOMetadataNode) rootNode.item(i));
+ }
+ }
+ IIOMetadataNode node = new IIOMetadataNode(nodeName);
+ rootNode.appendChild(node);
+ return(node);
+ }
+
+ public void drawFrame(double x, double y, double width, double height) {
+ if (getResourceLocation() == null)
+ buildGLThings();
+ if (startedPlayingAt == -1) startedPlayingAt = System.currentTimeMillis();
+
+ int frame = (int) (((System.currentTimeMillis() - startedPlayingAt) / delayTime) % frames);
+
+ TextureManager textureManager = Minecraft.getMinecraft().getTextureManager();
+ textureManager.bindTexture(getResourceLocation());
+
+ GlStateManager.color(1, 1, 1, 1.0F);
+
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
+ worldrenderer.pos(x, (y + height), 0.0D)
+ .tex(0,((frame+1) * height)/ ((double)frames * height)).endVertex();
+ worldrenderer.pos((x + width), (y + height), 0.0D)
+ .tex(1, ((frame+1) * height)/ ((double)frames * height)).endVertex();
+ worldrenderer.pos((x + width), y, 0.0D)
+ .tex(1,(frame * height)/ ((double)frames * height)).endVertex();
+ worldrenderer.pos(x, y, 0.0D)
+ .tex(0, (frame * height) / ((double)frames * height)).endVertex();
+ tessellator.draw();
+ }
+
+ public static final ExecutorService executorService = Executors.newFixedThreadPool(3, DungeonsGuide.THREAD_FACTORY);
+ public static final Map<String, ImageTexture> imageMap = new HashMap<>();
+ public static final Logger logger = LogManager.getLogger("DG-ImageLoader");
+ public static void loadImage(String url, Consumer<ImageTexture> callback) {
+ if (imageMap.containsKey(url)) {
+ callback.accept(imageMap.get(url));
+ return;
+ }
+ if (url.isEmpty()) callback.accept(null);
+ executorService.submit(() -> {
+ try {
+ ImageTexture imageTexture = new ImageTexture(url);
+ imageMap.put(url, imageTexture);
+ callback.accept(imageTexture);
+ } catch (Exception e) {
+ callback.accept(null);
+ logger.log(Level.WARN, "An error occurred while loading image from: "+url, e);
+ }
+ });
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ResourceImage.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ResourceImage.java
new file mode 100644
index 00000000..f2797691
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/ResourceImage.java
@@ -0,0 +1,54 @@
+/*
+ * Dungeons Guide - The most Integerelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.image;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ResourceImage extends AnnotatedExportOnlyWidget {
+
+ @Export(attributeName="location")
+ public final BindableAttribute<String > location = new BindableAttribute<String >(String.class);
+ @Export(attributeName="uvX")
+ public final BindableAttribute<Integer> uvX = new BindableAttribute<Integer>(Integer.class);
+ @Export(attributeName="uvY")
+ public final BindableAttribute<Integer> uvY = new BindableAttribute<Integer>(Integer.class);
+ @Export(attributeName="textureWidth")
+ public final BindableAttribute<Integer> textureWidth = new BindableAttribute<Integer>(Integer.class, 256);
+ @Export(attributeName="textureHeight")
+ public final BindableAttribute<Integer> textureHeight = new BindableAttribute<Integer>(Integer.class, 256);
+ @Export(attributeName="uvWidth")
+ public final BindableAttribute<Integer> uvWidth = new BindableAttribute<Integer>(Integer.class);
+ @Export(attributeName="uvHeight")
+ public final BindableAttribute<Integer> uvHeight = new BindableAttribute<Integer>(Integer.class);
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(new Image(
+ new ResourceLocation(location.getValue()), uvX.getValue(), uvY.getValue(), textureWidth.getValue(), textureHeight.getValue(), uvWidth.getValue(), uvHeight.getValue()
+ ));
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/URLImage.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/URLImage.java
new file mode 100644
index 00000000..f47964dd
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/image/URLImage.java
@@ -0,0 +1,87 @@
+/*
+ * Dungeons Guide - The most Integerelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.image;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedExportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+
+import java.util.Collections;
+import java.util.List;
+
+public class URLImage extends AnnotatedExportOnlyWidget implements Renderer, Layouter {
+ @Export(attributeName="url")
+ public final BindableAttribute<String> url = new BindableAttribute<String >(String.class);
+
+ private ImageTexture imageTexture;
+
+ public URLImage() {
+ url.addOnUpdate((prev, neu) -> {
+ ImageTexture.loadImage(neu, (texture) -> {
+ this.imageTexture = texture;
+ if (getDomElement() != null)
+ getDomElement().requestRelayout();
+ });
+ });
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ if (imageTexture == null) return;
+ imageTexture.drawFrame(0,0,buildContext.getSize().getWidth(), buildContext.getSize().getHeight());
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ if (imageTexture == null) return new Size(constraintBox.getMinWidth(), constraintBox.getMinHeight());
+
+ double heightIfWidthFit = constraintBox.getMaxWidth() * imageTexture.getHeight() / imageTexture.getWidth();
+ double widthIfHeightFit = constraintBox.getMaxHeight() * imageTexture.getWidth() / imageTexture.getHeight();
+
+ if (heightIfWidthFit <= constraintBox.getMaxHeight()) {
+ return new Size(constraintBox.getMaxWidth(), Layouter.clamp(heightIfWidthFit, constraintBox.getMinHeight(), constraintBox.getMaxHeight()));
+ } else if (widthIfHeightFit <= constraintBox.getMaxWidth()){
+ return new Size(Layouter.clamp(widthIfHeightFit, constraintBox.getMinWidth(), constraintBox.getMaxWidth()), constraintBox.getMaxHeight());
+ } else {
+ throw new IllegalStateException("How is this possible mathematically?");
+ }
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return imageTexture == null ? 0 : height * imageTexture.getWidth() / imageTexture.getHeight();
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return imageTexture == null ? 0 : width * imageTexture.getHeight() / imageTexture.getWidth();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/AbsLocationPopup.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/AbsLocationPopup.java
new file mode 100644
index 00000000..f2cb80d0
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/AbsLocationPopup.java
@@ -0,0 +1,98 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedImportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import net.minecraft.util.ResourceLocation;
+
+public class AbsLocationPopup extends AnnotatedImportOnlyWidget {
+ @Bind(variableName = "x")
+ public final BindableAttribute<Double> x = new BindableAttribute<>(Double.class);
+ @Bind(variableName = "y")
+ public final BindableAttribute<Double> y = new BindableAttribute<>(Double.class);;
+ @Bind(variableName = "ref")
+ public final BindableAttribute<DomElement> ref = new BindableAttribute<>(DomElement.class);
+ @Bind(variableName = "child")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+ @Bind(variableName = "size")
+ public final BindableAttribute<Size> size = new BindableAttribute<>(Size.class, new Size(0,0));
+
+ public final BindableAttribute<Double> absX = new BindableAttribute<>(Double.class);
+ public final BindableAttribute<Double> absY = new BindableAttribute<>(Double.class);
+ public boolean autoclose = false;
+ public AbsLocationPopup(double x, double y, Widget child, boolean autoclose) {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/locationedPopup.gui"));
+ absX.setValue(x);
+ absY.setValue(y);
+ absX.addOnUpdate(this::updatePos);
+ absY.addOnUpdate(this::updatePos);
+ size.addOnUpdate((old, neu) -> updatePos(0,0));
+ this.child.setValue(child);
+ this.autoclose = autoclose;
+ }
+ public AbsLocationPopup(BindableAttribute<Double> x, BindableAttribute<Double> y, Widget child, boolean autoclose) {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/locationedPopup.gui"));
+ x.exportTo(this.absX);
+ y.exportTo(this.absY);
+ absX.addOnUpdate(this::updatePos);
+ absY.addOnUpdate(this::updatePos);
+ size.addOnUpdate((old, neu) -> updatePos(0,0));
+ this.child.setValue(child);
+ this.autoclose = autoclose;
+ }
+
+ @Override
+ public void onMount() {
+ updatePos(0,0);
+ }
+
+ public void updatePos(double old, double neu) {
+ PopupMgr popupMgr = PopupMgr.getPopupMgr(getDomElement());
+ Rect rect = popupMgr.getDomElement().getAbsBounds();
+ Size rel = popupMgr.getDomElement().getSize();
+
+ Size size = this.size.getValue();
+ this.x.setValue(
+ Layouter.clamp((absX.getValue() - rect.getX()) * rel.getWidth() / rect.getWidth(), 0, rel.getWidth()-size.getWidth())
+ );
+ this.y.setValue(
+ Layouter.clamp((absY.getValue() - rect.getY()) * rel.getHeight() / rect.getHeight(), 0, rel.getHeight() - size.getHeight())
+ );
+ }
+
+ @Override
+ public boolean mouseClicked(int absMouseX, int absMouseY, double relMouseX, double relMouseY, int mouseButton) {
+ if (!ref.getValue().getAbsBounds().contains(absMouseX, absMouseY) && autoclose) {
+ PopupMgr.getPopupMgr(getDomElement()).closePopup(this,null);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ return true;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MinecraftTooltip.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MinecraftTooltip.java
new file mode 100644
index 00000000..158feaf9
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MinecraftTooltip.java
@@ -0,0 +1,58 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import net.minecraft.client.Minecraft;
+import net.minecraftforge.fml.client.config.GuiUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MinecraftTooltip extends Widget implements Renderer, Layouter {
+ public List<String> tooltip = new ArrayList<>();
+
+ public void setTooltip(List<String> tooltip) {
+ this.tooltip = tooltip;
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ GuiUtils.drawHoveringText(tooltip,0,0,
+ (int) buildContext.getRelativeBound().getWidth(),
+ (int) buildContext.getRelativeBound().getHeight(), -1, Minecraft.getMinecraft().fontRendererObj);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MouseTooltip.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MouseTooltip.java
new file mode 100644
index 00000000..5aec45cc
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/MouseTooltip.java
@@ -0,0 +1,56 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import org.lwjgl.input.Mouse;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MouseTooltip extends Widget {
+ private final AbsLocationPopup absLocationPopup;
+
+ private final BindableAttribute<Double> x = new BindableAttribute<>(Double.class, (double)Mouse.getX());
+ private final BindableAttribute<Double> y = new BindableAttribute<>(Double.class, (double)Mouse.getY());
+
+ public MouseTooltip(Widget content) {
+ absLocationPopup = new AbsLocationPopup(x,y, content, false);
+ }
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.singletonList(absLocationPopup);
+ }
+
+ @Override
+ public void onUnmount() {
+ super.onUnmount();
+ x.unexportAll();
+ y.unexportAll();
+ }
+
+ @Override
+ public boolean mouseMoved(int absMouseX, int absMouseY, double relMouseX0, double relMouseY0) {
+ x.setValue((double) absMouseX);
+ y.setValue((double) absMouseY);
+ return false;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/PopupMgr.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/PopupMgr.java
new file mode 100644
index 00000000..31b06af8
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/popups/PopupMgr.java
@@ -0,0 +1,92 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class PopupMgr extends AnnotatedWidget {
+ public PopupMgr() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/elements/popupmgr.gui"));
+ }
+ // just stack
+
+ @Bind(variableName = "stackRef")
+ public final BindableAttribute<DomElement> domElementBindableAttribute = new BindableAttribute<>(DomElement.class);
+
+
+ @Export(attributeName = "_")
+ @Bind(variableName = "_")
+ public final BindableAttribute<Widget> child = new BindableAttribute<>(Widget.class);
+
+
+ @Override
+ public void onMount() {
+ super.onMount();
+ getDomElement().getContext().CONTEXT.put("popup", this);
+ getDomElement().getContext().CONTEXT.put("popupWidth", this.getDomElement().getRelativeBound().getWidth());
+ getDomElement().getContext().CONTEXT.put("popupHeight", this.getDomElement().getRelativeBound().getHeight());
+ }
+
+ @Override
+ public void onUnmount() {
+ domElementBindableAttribute.getValue().getChildren().clear();
+ super.onUnmount();
+ }
+
+ public static PopupMgr getPopupMgr(DomElement buildContext) {
+ return buildContext.getContext().getValue(PopupMgr.class, "popup");
+ }
+
+
+ private Map<DomElement, Consumer<Object>> callbacks = new HashMap<>();
+ public void openPopup(Widget element, Consumer<Object> callback) {
+ DomElement domElement = element.createDomElement(getDomElement());
+ domElementBindableAttribute.getValue().addElementFirst(domElement);
+ callbacks.put(domElement, callback);
+ }
+
+ public void closePopup(Object value) {
+ DomElement target = domElementBindableAttribute.getValue().getChildren().get(0);
+ domElementBindableAttribute.getValue().removeElement(
+ target
+ );
+
+ Consumer<Object> callback = callbacks.remove(target);
+ if (callback != null)
+ callback.accept(value);
+ }
+
+ public void closePopup(Widget widget, Object value) {
+ domElementBindableAttribute.getValue().removeElement(widget.getDomElement());
+
+ Consumer<Object> callback = callbacks.get(widget.getDomElement());
+ if (callback != null)
+ callback.accept(value);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BreakWord.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BreakWord.java
new file mode 100644
index 00000000..05b0a33e
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BreakWord.java
@@ -0,0 +1,23 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext;
+
+public enum BreakWord {
+ ALL, WORD
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BrokenWordData.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BrokenWordData.java
new file mode 100644
index 00000000..a5f41646
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/BrokenWordData.java
@@ -0,0 +1,31 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data @AllArgsConstructor
+public class BrokenWordData {
+ private FlatTextSpan first;
+ private FlatTextSpan second;
+ private boolean broken;
+
+ private double firstWidth;
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/FlatTextSpan.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/FlatTextSpan.java
new file mode 100644
index 00000000..f0a93ed7
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/FlatTextSpan.java
@@ -0,0 +1,202 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.ITextStyle;
+
+public class FlatTextSpan {
+ public final ITextStyle textStyle;
+
+ public final char[] value;
+
+ public FlatTextSpan(ITextStyle textStyle, char[] value) {
+ this.textStyle = textStyle;
+ this.value = value;
+ }
+
+ public double getWidth() {
+ double sum =0;
+ for (char c : value) {
+ sum += textStyle.getFontRenderer().getWidth(c, textStyle);
+ }
+ return sum;
+ }
+ public double getHeight() {
+ return (1 + textStyle.getTopAscent() + textStyle.getBottomAscent()) * textStyle.getSize();
+ }
+ public double getBaseline() {
+ return textStyle.getSize() * (textStyle.getFontRenderer().getBaselineHeight(textStyle) + textStyle.getTopAscent());
+ }
+
+ public BrokenWordData breakWord(double remainingWidth, double nextLineWidth, BreakWord breakWord) {
+ if (breakWord == BreakWord.WORD) {
+ // prefer to break on words
+ double scaledRemainingWidth = remainingWidth;
+ double scaledNextLineWidth = nextLineWidth;
+
+ double totalWidth = 0;
+ double wordWidth = 0;
+ double charWidth = 0;
+ int wordStart = 0;
+ int potentialBreak = -1;
+
+ int endIdx = value.length;
+ boolean lineBroken = false;
+ for (int i = 0; i < value.length; i++) {
+ char character = value[i];
+ charWidth = textStyle.getFontRenderer().getWidth(character, textStyle);
+
+ totalWidth += charWidth;
+ wordWidth += charWidth;
+
+ if (character == ' ' || character == '\n') {
+ if (potentialBreak == -1) {
+ } else if (scaledNextLineWidth < wordWidth) {
+ // Break at potential, word is greater than next line
+ endIdx = potentialBreak;
+ lineBroken = true;
+ break;
+ } else {
+ // Delegate to next line.
+ endIdx = wordStart;
+ lineBroken = true;
+ break;
+ }
+
+ // Force break.
+ if (character == '\n') {
+ endIdx = i+1;
+ lineBroken = true;
+ break;
+ }
+
+ // Since adding space exceeded remaining width, break without adding space.
+ if (totalWidth > scaledRemainingWidth) {
+ endIdx = i;
+ lineBroken = true;
+ break;
+ }
+
+ // reset states
+ wordStart = i + 1;
+ potentialBreak = -1;
+ wordWidth = 0;
+ }
+
+ if (totalWidth > scaledRemainingWidth && potentialBreak == -1) {
+ potentialBreak = i;
+ }
+ }
+ if (potentialBreak == -1) {
+ } else if (scaledNextLineWidth < wordWidth) {
+ // Break at potential, word is greater than next line
+ endIdx = potentialBreak;
+ lineBroken = true;
+ } else {
+ // Delegate to next line.
+ endIdx = wordStart;
+ lineBroken = true;
+ }
+
+ char[] first = new char[endIdx];
+ System.arraycopy(value, 0, first, 0, endIdx);
+
+ char[] second = null;
+ if (lineBroken) {
+ int startRealWord = -1;
+ for (int i = endIdx; i< value.length; i++) {
+ if (value[i] == ' ') continue;
+ startRealWord = i;
+ break;
+ }
+ if (startRealWord != -1) {
+ second = new char[value.length - startRealWord];
+ System.arraycopy(value, startRealWord, second, 0, second.length);
+ }
+ }
+
+ FlatTextSpan flatTextSpan = new FlatTextSpan(textStyle, first);
+ FlatTextSpan secondSpan = null;
+ if (second != null)
+ secondSpan = new FlatTextSpan(textStyle, second);
+
+
+ double resultingWidth = 0;
+ for (char c : first) {
+ resultingWidth += textStyle.getFontRenderer().getWidth(c, textStyle);
+ }
+
+ return new BrokenWordData(flatTextSpan, secondSpan, lineBroken, resultingWidth);
+ } else {
+ // break anywhere
+ double scaledRemainingWidth = remainingWidth;
+
+ double totalWidth = 0;
+ double effectiveWidth = 0;
+ boolean lineBroken =false;
+
+ int endIdx = value.length;
+ for (int i = 0; i < value.length; i++) {
+ char character = value[i];
+ double charWidth = textStyle.getFontRenderer().getWidth(character, textStyle);
+
+ totalWidth += charWidth;
+
+ if (character == '\n') {
+ // Force break.
+ endIdx = i + 1;
+ lineBroken = true;
+ break;
+ }
+
+ if (totalWidth > scaledRemainingWidth) {
+ // break here.
+ endIdx = i;
+ lineBroken = true;
+ break;
+ }
+ effectiveWidth += charWidth;
+ }
+
+ char[] first = new char[endIdx];
+ System.arraycopy(value, 0, first, 0, endIdx);
+
+ char[] second = null;
+ if (lineBroken) {
+ int startRealWord = -1;
+ for (int i = endIdx; i< value.length; i++) {
+ if (value[i] == ' ') continue;
+ startRealWord = i;
+ break;
+ }
+ if (startRealWord != -1) {
+ second = new char[value.length - startRealWord];
+ System.arraycopy(value, startRealWord, second, 0, second.length);
+ }
+ }
+
+ FlatTextSpan flatTextSpan = new FlatTextSpan(textStyle, first);
+ FlatTextSpan secondSpan = null;
+ if (second != null)
+ secondSpan = new FlatTextSpan(textStyle, second);
+
+ return new BrokenWordData(flatTextSpan, secondSpan, lineBroken, effectiveWidth);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichLine.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichLine.java
new file mode 100644
index 00000000..0e84d01c
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichLine.java
@@ -0,0 +1,33 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+@Data @AllArgsConstructor
+public class RichLine {
+ private List<FlatTextSpan> lineElements;
+
+ private double width;
+ private double height;
+ private double baseline;
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichText.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichText.java
new file mode 100644
index 00000000..18e6ea00
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/RichText.java
@@ -0,0 +1,224 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.layouter.Layouter;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.Renderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.renderer.RenderingContext;
+import lombok.Setter;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+public class RichText extends Widget implements Layouter, Renderer {
+ private TextSpan rootSpan;
+ private BreakWord breakWord;
+ private boolean takeAllSpace = false;
+ @Setter
+ private TextAlign align;
+
+
+ private List<RichLine> richLines = new LinkedList<>();
+ public static enum TextAlign {
+ LEFT, CENTER, RIGHT
+ }
+
+ public RichText(TextSpan textSpan, BreakWord breakWord, boolean takeAllSpace, TextAlign align) {
+ this.rootSpan = textSpan;
+ this.breakWord = breakWord;
+ this.takeAllSpace = takeAllSpace;
+ this.align = align;
+ }
+
+ public void setRootSpan(TextSpan rootSpan) {
+ this.rootSpan = rootSpan;
+ this.getDomElement().requestRelayout();
+ }
+
+ public void setBreakWord(BreakWord breakWord) {
+ this.breakWord = breakWord;
+ this.getDomElement().requestRelayout();
+ }
+
+ public void setTakeAllSpace(boolean takeAllSpace) {
+ this.takeAllSpace = takeAllSpace;
+ this.getDomElement().requestRelayout();
+ }
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ LinkedList<FlatTextSpan> flatTextSpans = new LinkedList<>();
+ rootSpan.flattenTextSpan(flatTextSpans::add);
+
+ LinkedList<RichLine> lines = new LinkedList<>();
+ LinkedList<FlatTextSpan> line = new LinkedList<>();
+ double remaining = constraintBox.getMaxWidth();
+ double usedUp = 0;
+ double maxHeight = 0;
+ double maxBaseline = 0;
+
+ double sumHeight = 0;
+ double maxWidth = 0;
+
+ while (!flatTextSpans.isEmpty()) {
+ FlatTextSpan first = flatTextSpans.pollFirst();
+
+ BrokenWordData brokenWordData = first.breakWord(remaining, constraintBox.getMaxWidth(), breakWord);
+ remaining -= brokenWordData.getFirstWidth();
+ usedUp += brokenWordData.getFirstWidth();
+ line.add(brokenWordData.getFirst());
+
+ if (brokenWordData.getFirst().value.length == 0 && first.value.length != 0 && remaining == constraintBox.getMaxWidth()) {
+ try {
+ throw new IllegalStateException("Can not fit stuff into this");
+ } catch (Exception e) {
+ e.printStackTrace();
+ break;
+ }
+ }
+
+ maxHeight = Math.max(maxHeight, first.getHeight());
+ maxBaseline = Math.max(maxBaseline, first.getBaseline());
+
+ if (brokenWordData.getSecond() != null) {
+ flatTextSpans.addFirst(brokenWordData.getSecond());
+ }
+
+ if (brokenWordData.isBroken()) {
+ lines.add(new RichLine(line, usedUp, maxHeight, maxBaseline));
+ line = new LinkedList<>();
+ maxWidth = Math.max(maxWidth, usedUp);
+ sumHeight += maxHeight;
+
+ remaining = constraintBox.getMaxWidth();
+ usedUp = 0;
+ maxHeight = 0;
+ maxBaseline = 0;
+ }
+ }
+ if (!line.isEmpty()) {
+ lines.add(new RichLine(line, usedUp, maxHeight, maxBaseline));
+ maxWidth = Math.max(maxWidth, usedUp);
+ sumHeight += maxHeight;
+ }
+
+ richLines = lines;
+
+ return new Size(takeAllSpace ? constraintBox.getMaxWidth() :
+ Layouter.clamp(maxWidth, constraintBox.getMinWidth(), constraintBox.getMaxWidth()),
+ Layouter.clamp(sumHeight, constraintBox.getMinHeight(), constraintBox.getMaxHeight()));
+ }
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ LinkedList<FlatTextSpan> flatTextSpans = new LinkedList<>();
+ rootSpan.flattenTextSpan(flatTextSpans::add);
+
+ LinkedList<FlatTextSpan> line = new LinkedList<>();
+ double usedUp = 0;
+
+ double maxWidth = 0;
+ while (!flatTextSpans.isEmpty()) {
+ FlatTextSpan first = flatTextSpans.pollFirst();
+ BrokenWordData brokenWordData = first.breakWord(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, breakWord);
+ usedUp += brokenWordData.getFirstWidth();
+ line.add(brokenWordData.getFirst());
+
+ if (brokenWordData.getSecond() != null) {
+ flatTextSpans.addFirst(brokenWordData.getSecond());
+ }
+
+ if (brokenWordData.isBroken()) {
+ maxWidth = Math.max(maxWidth, usedUp);
+ usedUp = 0;
+ }
+ }
+ if (!line.isEmpty()) {
+ maxWidth = Math.max(maxWidth, usedUp);
+ }
+
+ return maxWidth;
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ LinkedList<FlatTextSpan> flatTextSpans = new LinkedList<>();
+ rootSpan.flattenTextSpan(flatTextSpans::add);
+ double remaining = width == 0 ? Double.POSITIVE_INFINITY : width;
+ double maxHeight = 0;
+ double sumHeight = 0;
+ while (!flatTextSpans.isEmpty()) {
+ FlatTextSpan first = flatTextSpans.pollFirst();
+
+ BrokenWordData brokenWordData = first.breakWord(remaining, width == 0 ? Double.POSITIVE_INFINITY : width, breakWord);
+ remaining -= brokenWordData.getFirstWidth();
+
+ if (brokenWordData.getFirst().value.length == 0 && first.value.length != 0 && remaining == width) {
+ return 0;
+ }
+
+ maxHeight = Math.max(maxHeight, first.getHeight());
+
+ if (brokenWordData.getSecond() != null) {
+ flatTextSpans.addFirst(brokenWordData.getSecond());
+ }
+
+ if (brokenWordData.isBroken()) {
+ remaining = width == 0 ? Double.POSITIVE_INFINITY : width;
+ sumHeight += maxHeight;
+ maxHeight = 0;
+ }
+ }
+ sumHeight += maxHeight;
+ return sumHeight;
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ double x = 0;
+ double y = 0;
+ double width = buildContext.getSize().getWidth();
+ double currentScale = buildContext.getAbsBounds().getWidth() / width;
+ for (RichLine richLine : richLines) {
+ if (align == TextAlign.RIGHT)
+ x = width - richLine.getWidth();
+ else if (align == TextAlign.CENTER)
+ x = (width - richLine.getWidth()) / 2;
+ else
+ x = 0;
+ for (FlatTextSpan lineElement : richLine.getLineElements()) {
+ lineElement.textStyle.getFontRenderer()
+ .render(lineElement, x, y + richLine.getBaseline() - lineElement.getBaseline()
+ + lineElement.textStyle.getTopAscent() * lineElement.textStyle.getSize(), currentScale);
+ x += lineElement.getWidth();
+ }
+ y += richLine.getHeight();
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/TextSpan.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/TextSpan.java
new file mode 100644
index 00000000..abc78f51
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/TextSpan.java
@@ -0,0 +1,48 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.CompiledTextStyle;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.ITextStyle;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.ParentDelegatingTextStyle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class TextSpan {
+ private ITextStyle textStyle;
+ private String text;
+ private List<TextSpan> children = new ArrayList<>();
+ public TextSpan(ITextStyle textStyle, String text) {
+ this.textStyle = textStyle;
+ this.text = text;
+ }
+
+ public void addChild(TextSpan textSpan) {
+ if (textSpan.textStyle instanceof ParentDelegatingTextStyle)
+ ((ParentDelegatingTextStyle) textSpan.textStyle).setParent(textStyle);
+ children.add(textSpan);
+ }
+
+ public void flattenTextSpan(Consumer<FlatTextSpan> appender) {
+ appender.accept(new FlatTextSpan(new CompiledTextStyle(textStyle), text.toCharArray()));
+ children.forEach(a -> a.flattenTextSpan(appender));
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/DefaultFontRenderer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/DefaultFontRenderer.java
new file mode 100644
index 00000000..37c5e589
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/DefaultFontRenderer.java
@@ -0,0 +1,310 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.fonts;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.FlatTextSpan;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.ITextStyle;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.texture.TextureUtil;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.client.renderer.vertex.VertexFormat;
+import net.minecraft.client.renderer.vertex.VertexFormatElement;
+import net.minecraft.util.ResourceLocation;
+import org.apache.commons.io.IOUtils;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class DefaultFontRenderer implements FontRenderer {
+ public static DefaultFontRenderer DEFAULT_RENDERER = new DefaultFontRenderer();
+
+ private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
+ protected int[] charWidth = new int[256];
+ public int FONT_HEIGHT = 9;
+ protected byte[] glyphData = new byte[65536];
+ protected final ResourceLocation locationFontTexture = new ResourceLocation("textures/font/ascii.png");
+
+ public DefaultFontRenderer() {
+ readGlyphSizes();
+ }
+
+ public void onResourceManagerReload() {
+ this.readFontTexture();
+ this.readGlyphSizes();
+ }
+
+ private void readFontTexture() {
+ BufferedImage bufferedimage;
+ try {
+ bufferedimage = TextureUtil.readBufferedImage(
+ Minecraft.getMinecraft().getResourceManager().getResource(this.locationFontTexture).getInputStream());
+ } catch (IOException var17) {
+ throw new RuntimeException(var17);
+ }
+
+ int i = bufferedimage.getWidth();
+ int j = bufferedimage.getHeight();
+ int[] aint = new int[i * j];
+ bufferedimage.getRGB(0, 0, i, j, aint, 0, i);
+ int k = j / 16;
+ int l = i / 16;
+ int i1 = 1;
+ float f = 8.0F / (float)l;
+
+ for(int j1 = 0; j1 < 256; ++j1) {
+ int k1 = j1 % 16;
+ int l1 = j1 / 16;
+ if (j1 == 32) {
+ this.charWidth[j1] = 3 + i1;
+ }
+
+ int i2;
+ for(i2 = l - 1; i2 >= 0; --i2) {
+ int j2 = k1 * l + i2;
+ boolean flag = true;
+
+ for(int k2 = 0; k2 < k && flag; ++k2) {
+ int l2 = (l1 * l + k2) * i;
+ if ((aint[j2 + l2] >> 24 & 255) != 0) {
+ flag = false;
+ }
+ }
+
+ if (!flag) {
+ break;
+ }
+ }
+
+ ++i2;
+ this.charWidth[j1] = (int)(0.5 + (double)((float)i2 * f)) + i1;
+ }
+
+ }
+
+ private void readGlyphSizes() {
+ InputStream inputstream = null;
+
+ try {
+ inputstream = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
+ inputstream.read(this.glyphData);
+ } catch (IOException var6) {
+ throw new RuntimeException(var6);
+ } finally {
+ IOUtils.closeQuietly(inputstream);
+ }
+
+ }
+
+ private ResourceLocation getUnicodePageLocation(int page) {
+ if (unicodePageLocations[page] == null) {
+ unicodePageLocations[page] = new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", page));
+ }
+
+ return unicodePageLocations[page];
+ }
+
+ @Override
+ public double getWidth(char text, ITextStyle textStyle) {
+ double val;
+ if (text == ' ') {
+ val = 4;
+ } else {
+ int i = "ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u0000".indexOf(text);
+ if (text > 0 && i != -1) {
+ val = this.charWidth[i];
+ } else if (this.glyphData[text] != 0) {
+ int texStart = this.glyphData[text] >>> 4;
+ int texEnd = this.glyphData[text] & 15 + 1;
+ val = (texEnd - texStart) / 2.0 + 1;
+ } else {
+ val = 0;
+ }
+ }
+ return (val + (textStyle.isBold() ? 1 : 0)) * textStyle.getSize() / 8;
+ }
+
+ @Override
+ public double getBaselineHeight(ITextStyle textStyle) {
+ return 7 * textStyle.getSize() / 8.0;
+ }
+
+ public void render(WorldRenderer worldRenderer, FlatTextSpan lineElement, double x, double y, boolean isShadow) {
+ double startX = x;
+ if (isShadow) lineElement.textStyle.getShadowShader().useShader();
+
+ if (!isShadow) lineElement.textStyle.getTextShader().useShader();
+ GlStateManager.enableTexture2D();
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
+ for (char c : lineElement.value) {
+ x += renderChar(worldRenderer, x, y+1, c, lineElement.textStyle);
+ }
+ draw(worldRenderer);
+ if (!isShadow) lineElement.textStyle.getTextShader().freeShader();
+
+ GlStateManager.disableTexture2D();
+ double baseline = getBaselineHeight(lineElement.textStyle);
+ if (lineElement.textStyle.isStrikeThrough()) {
+ if (!isShadow) lineElement.textStyle.getStrikeThroughShader().useShader();
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(startX, y + baseline/2, 0).endVertex();
+ worldRenderer.pos(x, y + baseline/2, 0).endVertex();
+ worldRenderer.pos(x, y +baseline/2+1, 0).endVertex();
+ worldRenderer.pos(startX, y + baseline/2+1, 0).endVertex();
+ draw(worldRenderer);
+ if (!isShadow) lineElement.textStyle.getStrikeThroughShader().freeShader();
+ }
+ if (lineElement.textStyle.isUnderline()) {
+ if (!isShadow) lineElement.textStyle.getUnderlineShader().useShader();
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(startX, y + baseline, 0).endVertex();
+ worldRenderer.pos(x, y + baseline, 0).endVertex();
+ worldRenderer.pos(x, y + baseline+1, 0).endVertex();
+ worldRenderer.pos(startX, y + baseline+1, 0).endVertex();
+ draw(worldRenderer);
+ if (!isShadow) lineElement.textStyle.getUnderlineShader().freeShader();
+ }
+
+ if (isShadow) lineElement.textStyle.getShadowShader().freeShader();
+ GlStateManager.enableTexture2D(); // probably it expects it to be on
+ }
+
+ @Override
+ public void render(FlatTextSpan lineElement, double x, double y, double currentScale) {
+ lastBound = null;
+ WorldRenderer worldRenderer = Tessellator.getInstance().getWorldRenderer();
+ GlStateManager.disableTexture2D();
+ GlStateManager.enableAlpha();
+ if (lineElement.textStyle.getBackgroundShader() != null) {
+ lineElement.textStyle.getBackgroundShader().useShader();
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(x, y, 0).endVertex();
+ worldRenderer.pos(x + lineElement.getWidth(), y, 0).endVertex();
+ worldRenderer.pos(x + lineElement.getWidth(), y + lineElement.textStyle.getSize(), 0).endVertex();
+ worldRenderer.pos(x, y + lineElement.textStyle.getSize(), 0).endVertex();
+ draw(worldRenderer);
+ lineElement.textStyle.getBackgroundShader().freeShader();
+ }
+ y += lineElement.textStyle.getTopAscent();
+
+ if (lineElement.textStyle.isShadow())
+ render(worldRenderer, lineElement, x+1,y+1, true);
+ render(worldRenderer, lineElement, x,y, false);
+ }
+
+
+
+ private double renderChar(WorldRenderer worldRenderer, double x, double y, char ch, ITextStyle textStyle) {
+ if (ch == ' ') {
+ return 4.0F * textStyle.getSize() / 8.0;
+ } else {
+ int i = "ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u0000".indexOf(ch);
+ return i != -1 ? this.renderDefaultChar(worldRenderer, x, y, i, textStyle) : this.renderUnicodeChar(worldRenderer, x, y, ch, textStyle);
+ }
+ }
+
+
+ private void draw(WorldRenderer renderer) {
+ renderer.finishDrawing();
+ if (renderer.getVertexCount() > 0) {
+ VertexFormat vertexformat = renderer.getVertexFormat();
+ int i = vertexformat.getNextOffset();
+ ByteBuffer bytebuffer = renderer.getByteBuffer();
+ List<VertexFormatElement> list = vertexformat.getElements();
+
+ int i1;
+ for(i1 = 0; i1 < list.size(); ++i1) {
+ VertexFormatElement vertexformatelement = (VertexFormatElement)list.get(i1);
+ vertexformatelement.getUsage().preDraw(vertexformat, i1, i, bytebuffer);
+ }
+
+ GL11.glDrawArrays(renderer.getDrawMode(), 0, renderer.getVertexCount());
+ i1 = 0;
+
+ for(int j1 = list.size(); i1 < j1; ++i1) {
+ VertexFormatElement vertexFormatElement1 = (VertexFormatElement)list.get(i1);
+ vertexFormatElement1.getUsage().postDraw(vertexformat, i1, i, bytebuffer);
+ }
+ }
+
+ renderer.reset();
+ }
+
+ private ResourceLocation lastBound;
+ public void bindTexture(WorldRenderer worldRenderer, ResourceLocation resourceLocation) {
+ if (resourceLocation.equals(lastBound)) return;
+ lastBound = resourceLocation;
+ draw(worldRenderer);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation);
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
+ }
+
+ private double renderDefaultChar(WorldRenderer worldRenderer, double posX, double posY, int ch, ITextStyle textStyle) {
+ int texX = ch % 16 * 8;
+ int texY = ch / 16 * 8;
+ int italicsAddition = textStyle.isItalics() ? 1 : 0;
+ bindTexture(worldRenderer, this.locationFontTexture);
+ double texWidth = (this.charWidth[ch]);
+ double charWidth = (texWidth-1) * textStyle.getSize() / 8.0;
+ double charHeight = textStyle.getSize();
+ // char width contains the gap between next char
+
+
+ worldRenderer.pos(posX + (float)italicsAddition, posY, 0.0F).tex((float)texX / 128.0F, (float)texY / 128.0F).endVertex();
+ worldRenderer.pos(posX - (float)italicsAddition, posY + charHeight - 0.01F, 0.0F).tex((float)texX / 128.0F, ((float)texY + 7.99F) / 128.0F).endVertex();
+ worldRenderer.pos(posX + charWidth+ (float)italicsAddition - 0.01F , posY+ charHeight - 0.01F, 0.0F).tex(((float)texX + texWidth - 1.01F) / 128.0F, ((float)texY + 7.99F) / 128.0F).endVertex();
+ worldRenderer.pos(posX + charWidth - (float)italicsAddition - 0.01F, posY , 0.0F).tex(((float)texX + texWidth - 1.01F) / 128.0F, (float)texY / 128.0F).endVertex();
+
+ return texWidth * textStyle.getSize() / 8.0;
+ }
+ private double renderUnicodeChar(WorldRenderer worldRenderer, double posX, double posY, char ch, ITextStyle textStyle) {
+ if (this.glyphData[ch] == 0) {
+ return 0.0F;
+ } else {
+ int i = ch / 256;
+ bindTexture(worldRenderer, this.getUnicodePageLocation(i));
+ float xStart = (float)(this.glyphData[ch] >>> 4);
+ float xEnd = (float)(this.glyphData[ch] & 15 + 1);
+
+ float texX = (float)(ch % 16 * 16) + xStart;
+ float texY = (float)((ch & 255) / 16 * 16);
+ float texWidth = xEnd - xStart - 0.02F;
+ float italicSlope = textStyle.isItalics() ? 1.0F : 0.0F;
+
+ double charWidth = texWidth * textStyle.getSize() / 16.0;
+ double charHeight = textStyle.getSize();
+
+ worldRenderer.pos(posX + italicSlope, posY, 0.0F)
+ .tex(texX / 256.0F, texY / 256.0F).endVertex();
+ worldRenderer.pos(posX - italicSlope, posY + charHeight - 0.01F, 0.0F)
+ .tex(texX / 256.0F, (texY + 15.98F) / 256.0F).endVertex();
+ worldRenderer.pos(posX + charWidth + italicSlope, posY + charHeight - 0.01F, 0.0F)
+ .tex((texX + texWidth) / 256.0F, (texY+15.98F) / 256.0F).endVertex();
+ worldRenderer.pos(posX + charWidth - italicSlope, posY, 0.0F)
+ .tex((texX + texWidth) / 256.0F, (texY) / 256.0F).endVertex();
+ return charWidth + textStyle.getSize() / 8.0;
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/FontRenderer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/FontRenderer.java
new file mode 100644
index 00000000..a23702b4
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/fonts/FontRenderer.java
@@ -0,0 +1,30 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.fonts;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.FlatTextSpan;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles.ITextStyle;
+
+public interface FontRenderer {
+ double getWidth(char text, ITextStyle textStyle);
+ // from y 0 going downward
+ double getBaselineHeight(ITextStyle textStyle);
+
+ void render(FlatTextSpan lineElement, double x, double v, double currentScale);
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/ChromaShader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/ChromaShader.java
new file mode 100644
index 00000000..98b029be
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/ChromaShader.java
@@ -0,0 +1,41 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders;
+
+import net.minecraft.client.renderer.GlStateManager;
+
+public class ChromaShader implements Shader {
+ private float r, g, b, a;
+ public ChromaShader(float chromaSpeed, int color) {
+ r = ((color >> 16) & 0xFF) / 255.0f;
+ g = ((color >> 8) & 0xFF) / 255.0f;
+ b = ((color) & 0xFF) / 255.0f;
+ a = ((color >> 24) & 0xFF) / 255.0f;
+ }
+
+
+ // argb=
+ @Override
+ public void useShader() {
+ GlStateManager.color(r,g,b,a);
+ }
+
+ @Override
+ public void freeShader() {}
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/Shader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/Shader.java
new file mode 100644
index 00000000..45163a16
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/Shader.java
@@ -0,0 +1,24 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders;
+
+public interface Shader {
+ public void useShader();
+ public void freeShader();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/SingleColorShader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/SingleColorShader.java
new file mode 100644
index 00000000..4ed2caf0
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/shaders/SingleColorShader.java
@@ -0,0 +1,40 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders;
+
+import net.minecraft.client.renderer.GlStateManager;
+
+public class SingleColorShader implements Shader{
+ private float r, g, b, a;
+
+ // argb
+ public SingleColorShader(int color) {
+ r = ((color >> 16) & 0xFF) / 255.0f;
+ g = ((color >> 8) & 0xFF) / 255.0f;
+ b = ((color) & 0xFF) / 255.0f;
+ a = ((color >> 24) & 0xFF) / 255.0f;
+ }
+ @Override
+ public void useShader() {
+ GlStateManager.color(r,g,b,a);
+ }
+
+ @Override
+ public void freeShader() {}
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/CompiledTextStyle.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/CompiledTextStyle.java
new file mode 100644
index 00000000..aa76fcde
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/CompiledTextStyle.java
@@ -0,0 +1,146 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.fonts.FontRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders.Shader;
+
+public class CompiledTextStyle implements ITextStyle {
+ public Double size;
+ public Double topAscent;
+ public Double bottomAscent;
+
+ public boolean bold;
+ public boolean italics;
+ public boolean strikeThrough;
+ public boolean underline;
+ public boolean outline;
+ public boolean shadow;
+
+ public Shader backgroundShader;
+ public Shader textShader;
+ public Shader strikeThroughShader;
+ public Shader underlineShader;
+ public Shader outlineShader;
+ public Shader shadowShader;
+
+
+ public FontRenderer fontRenderer;
+
+ public CompiledTextStyle(ITextStyle from) {
+ this.size = from.getSize();
+ this.topAscent = from.getTopAscent();
+ this.bottomAscent = from.getBottomAscent();
+
+ this.bold = from.isBold();
+ this.italics = from.isItalics();
+ this.strikeThrough = from.isStrikeThrough();
+ this.underline = from.isUnderline();
+ this.outline = from.isOutline();
+ this.shadow = from.isShadow();
+
+ this.backgroundShader = from.getBackgroundShader();
+ this.textShader = from.getTextShader();
+ this.strikeThroughShader = from.getStrikeThroughShader();
+ this.underlineShader = from.getUnderlineShader();
+ this.outlineShader = from.getOutlineShader();
+ this.shadowShader = from.getShadowShader();
+ this.fontRenderer = from.getFontRenderer();
+ }
+
+ @Override
+ public Double getSize() {
+ return size;
+ }
+
+ @Override
+ public Double getTopAscent() {
+ return topAscent;
+ }
+
+ @Override
+ public Double getBottomAscent() {
+ return bottomAscent;
+ }
+
+ @Override
+ public Boolean isBold() {
+ return bold;
+ }
+
+ @Override
+ public Boolean isItalics() {
+ return italics;
+ }
+
+ @Override
+ public Boolean isStrikeThrough() {
+ return strikeThrough;
+ }
+
+ @Override
+ public Boolean isUnderline() {
+ return underline;
+ }
+
+ @Override
+ public Boolean isOutline() {
+ return outline;
+ }
+
+ @Override
+ public Boolean isShadow() {
+ return shadow;
+ }
+
+ @Override
+ public Shader getBackgroundShader() {
+ return backgroundShader;
+ }
+
+ @Override
+ public Shader getTextShader() {
+ return textShader;
+ }
+
+ @Override
+ public Shader getStrikeThroughShader() {
+ return strikeThroughShader;
+ }
+
+ @Override
+ public Shader getUnderlineShader() {
+ return underlineShader;
+ }
+
+ @Override
+ public Shader getOutlineShader() {
+ return outlineShader;
+ }
+
+ @Override
+ public Shader getShadowShader() {
+ return shadowShader;
+ }
+
+ @Override
+ public FontRenderer getFontRenderer() {
+ return fontRenderer;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ITextStyle.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ITextStyle.java
new file mode 100644
index 00000000..48cee96f
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ITextStyle.java
@@ -0,0 +1,52 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles;
+
+public interface ITextStyle {
+ Double getSize();
+
+ Double getTopAscent();
+ Double getBottomAscent();
+
+ Boolean isBold();
+
+ Boolean isItalics();
+
+ Boolean isStrikeThrough();
+
+ Boolean isUnderline();
+
+ Boolean isOutline();
+
+ Boolean isShadow();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader getBackgroundShader();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader getTextShader();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader getStrikeThroughShader();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader getUnderlineShader();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader getOutlineShader();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader getShadowShader();
+
+ kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.fonts.FontRenderer getFontRenderer();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ParentDelegatingTextStyle.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ParentDelegatingTextStyle.java
new file mode 100644
index 00000000..621ac2cd
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/elements/richtext/styles/ParentDelegatingTextStyle.java
@@ -0,0 +1,158 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.styles;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.fonts.DefaultFontRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.fonts.FontRenderer;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders.Shader;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.richtext.shaders.SingleColorShader;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Setter @Accessors(chain = true)
+public class ParentDelegatingTextStyle implements ITextStyle {
+ public Double size;
+ public Double topAscent;
+ public Double bottomAscent;
+
+
+
+ public Boolean bold;
+ public Boolean italics;
+ public Boolean strikeThrough;
+ public Boolean underline;
+ public Boolean outline;
+ public Boolean shadow;
+
+ public Shader backgroundShader;
+ public Shader textShader;
+ public Shader strikeThroughShader;
+ public Shader underlineShader;
+ public Shader outlineShader;
+ public Shader shadowShader;
+
+
+ @Getter @Setter
+ public ITextStyle parent;
+ public FontRenderer fontRenderer;
+
+ public static ParentDelegatingTextStyle ofDefault() {
+ ParentDelegatingTextStyle parentDelegatingTextStyle = new ParentDelegatingTextStyle();
+ parentDelegatingTextStyle.size = 8.0;
+ parentDelegatingTextStyle.topAscent = 0.0;
+ parentDelegatingTextStyle.bottomAscent = 1 / 8.0;
+ parentDelegatingTextStyle.bold = false;
+ parentDelegatingTextStyle.italics = false;
+ parentDelegatingTextStyle.strikeThrough = false;
+ parentDelegatingTextStyle.underline = false;
+ parentDelegatingTextStyle.shadow = false;
+ parentDelegatingTextStyle.outline = false;
+
+ parentDelegatingTextStyle.backgroundShader = new SingleColorShader(0xFFFFFFFF);
+ parentDelegatingTextStyle.textShader = new SingleColorShader(0xFFFFFFFF);
+ parentDelegatingTextStyle.strikeThroughShader = new SingleColorShader(0xFF000000);
+ parentDelegatingTextStyle.underlineShader = new SingleColorShader(0xFF000000);
+ parentDelegatingTextStyle.outlineShader = new SingleColorShader(0xFF000000);
+ parentDelegatingTextStyle.shadowShader = new SingleColorShader(0xFF000000);
+
+ parentDelegatingTextStyle.fontRenderer = DefaultFontRenderer.DEFAULT_RENDERER;
+ return parentDelegatingTextStyle;
+ }
+
+ @Override
+ public Double getSize() {
+ return parent != null && size == null ? parent.getSize() : size;
+ }
+
+ @Override
+ public Double getTopAscent() {
+ return parent != null && topAscent == null ? parent.getTopAscent() : topAscent;
+ }
+
+ @Override
+ public Double getBottomAscent() {
+ return parent != null && bottomAscent == null ? parent.getBottomAscent() : bottomAscent;
+ }
+
+ @Override
+ public Boolean isBold() {
+ return parent != null && bold == null ? parent.isBold() : bold;
+ }
+
+ @Override
+ public Boolean isItalics() {
+ return parent != null && italics == null ? parent.isItalics() : italics;
+ }
+
+ @Override
+ public Boolean isOutline() {
+ return parent != null && outline == null ? parent.isOutline() : outline;
+ }
+
+ @Override
+ public Boolean isShadow() {
+ return parent != null && shadow == null ? parent.isShadow() : shadow;
+ }
+
+ @Override
+ public Boolean isStrikeThrough() {
+ return parent != null && strikeThrough == null ? parent.isStrikeThrough() : strikeThrough;
+ }
+
+ @Override
+ public Boolean isUnderline() {
+ return parent != null && underline == null ? parent.isUnderline() : underline;
+ }
+
+ @Override
+ public FontRenderer getFontRenderer() {
+ return parent != null && fontRenderer == null ? parent.getFontRenderer() : fontRenderer;
+ }
+
+ @Override
+ public Shader getShadowShader() {
+ return parent != null && shadowShader == null ? parent.getShadowShader() : shadowShader;
+ }
+
+ @Override
+ public Shader getBackgroundShader() {
+ return parent != null && backgroundShader == null ? parent.getBackgroundShader() : backgroundShader;
+ }
+
+ @Override
+ public Shader getOutlineShader() {
+ return parent != null && outlineShader == null ? parent.getOutlineShader() : outlineShader;
+ }
+
+ @Override
+ public Shader getStrikeThroughShader() {
+ return parent != null && strikeThroughShader == null ? parent.getStrikeThroughShader() : strikeThroughShader;
+ }
+
+ @Override
+ public Shader getTextShader() {
+ return parent != null && textShader == null ? parent.getTextShader() : textShader;
+ }
+
+ @Override
+ public Shader getUnderlineShader() {
+ return parent != null && underlineShader == null ? parent.getUnderlineShader() : underlineShader;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/Layouter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/Layouter.java
new file mode 100644
index 00000000..c7cc4c11
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/Layouter.java
@@ -0,0 +1,43 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.layouter;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+
+public interface Layouter {
+ public abstract Size layout(DomElement buildContext, ConstraintBox constraintBox);
+ public static double clamp(double val, double min, double max) {
+ if (val < min) return min;
+ if (val > max) return max;
+ return val;
+ }
+
+ default boolean canCutRequest() {
+ return false;
+ }
+
+ default double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return 0;
+ }
+ default double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return 0;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/NullLayouter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/NullLayouter.java
new file mode 100644
index 00000000..722784eb
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/NullLayouter.java
@@ -0,0 +1,33 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.layouter;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+
+public class NullLayouter implements Layouter {
+ public static final NullLayouter INSTANCE = new NullLayouter();
+ private NullLayouter() {}
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/SingleChildPassingLayouter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/SingleChildPassingLayouter.java
new file mode 100644
index 00000000..b770a4b9
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/layouter/SingleChildPassingLayouter.java
@@ -0,0 +1,53 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.layouter;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.ConstraintBox;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+
+public class SingleChildPassingLayouter implements Layouter {
+ public static final SingleChildPassingLayouter INSTANCE = new SingleChildPassingLayouter();
+ private SingleChildPassingLayouter() {}
+
+ @Override
+ public Size layout(DomElement buildContext, ConstraintBox constraintBox) {
+ if (buildContext.getChildren().isEmpty()) {
+ return new Size(constraintBox.getMaxWidth(), constraintBox.getMaxHeight());
+ }
+
+ DomElement childCtx = buildContext.getChildren().get(0);
+
+ Size dim = childCtx.getLayouter().layout(childCtx, constraintBox);
+ childCtx.setRelativeBound(new Rect(0,0, dim.getWidth(), dim.getHeight()));
+ return new Size(dim.getWidth(), dim.getHeight());
+ }
+
+
+ @Override
+ public double getMaxIntrinsicWidth(DomElement buildContext, double height) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicWidth(buildContext.getChildren().get(0), height);
+ }
+
+ @Override
+ public double getMaxIntrinsicHeight(DomElement buildContext, double width) {
+ return buildContext.getChildren().isEmpty() ? 0 : buildContext.getChildren().get(0).getLayouter().getMaxIntrinsicHeight(buildContext.getChildren().get(0), width);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Animation.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Animation.java
new file mode 100644
index 00000000..f42439aa
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Animation.java
@@ -0,0 +1,38 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+public class Animation<T> {
+ private long startMillies;
+ private double lengthMillies;
+
+ private T start;
+ private T end;
+
+ public Animation(long startMillies, double lengthMillies, T start, T end) {
+ this.startMillies = startMillies;
+ this.lengthMillies = lengthMillies;
+ this.start = start;
+ this.end = end;
+ }
+
+ public double getPhase() {
+ return (System.currentTimeMillis() - startMillies) / lengthMillies;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ConstraintBox.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ConstraintBox.java
new file mode 100644
index 00000000..86289120
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ConstraintBox.java
@@ -0,0 +1,40 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+// Idea heavily taken from flutter.
+@AllArgsConstructor @Getter
+public class ConstraintBox {
+ private double minWidth;
+ private double maxWidth;
+ private double minHeight;
+ private double maxHeight;
+
+ public static ConstraintBox loose(double width, double height) {
+ return new ConstraintBox(0,width,0,height);
+ }
+
+ public static ConstraintBox tight(double width, double height) {
+ return new ConstraintBox(width, width, height, height);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IPosition.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IPosition.java
new file mode 100644
index 00000000..e3a14f91
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IPosition.java
@@ -0,0 +1,25 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+public interface IPosition {
+ double getX();
+
+ double getY();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IRect.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IRect.java
new file mode 100644
index 00000000..f9655803
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/IRect.java
@@ -0,0 +1,34 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+public interface IRect {
+ default boolean contains(double x, double y) {
+ return getX() <= x && x < getX() + getWidth() &&
+ getY() <= y && y < getY()+ getHeight();
+ }
+
+ double getX();
+
+ double getY();
+
+ double getWidth();
+
+ double getHeight();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ISize.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ISize.java
new file mode 100644
index 00000000..a6199400
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/ISize.java
@@ -0,0 +1,29 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+public interface ISize {
+ double getWidth();
+
+ double getHeight();
+
+ default boolean contains(double x, double y) {
+ return 0 <= x && 0 <= y && x < getWidth() && y < getHeight();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Position.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Position.java
new file mode 100644
index 00000000..5d0b7ac4
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Position.java
@@ -0,0 +1,28 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data @AllArgsConstructor
+public class Position implements IPosition {
+ public final double x;
+ public final double y;
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Rect.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Rect.java
new file mode 100644
index 00000000..310382f2
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Rect.java
@@ -0,0 +1,32 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data @AllArgsConstructor
+public class Rect implements IRect {
+ private final double x;
+ private final double y;
+ private final double width;
+ private final double height;
+
+ public static Rect fromPositionSize(Position pos, Size size) { return new Rect(pos.getX(), pos.getY(), size.getWidth(), size.getHeight()); }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Size.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Size.java
new file mode 100644
index 00000000..32803616
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/primitive/Size.java
@@ -0,0 +1,28 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.primitive;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data @AllArgsConstructor
+public class Size implements ISize {
+ private final double width;
+ private final double height;
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/DrawNothingRenderer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/DrawNothingRenderer.java
new file mode 100644
index 00000000..2ba4f260
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/DrawNothingRenderer.java
@@ -0,0 +1,29 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.renderer;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+
+public class DrawNothingRenderer implements Renderer {
+ public static DrawNothingRenderer INSTANCE = new DrawNothingRenderer();
+ private DrawNothingRenderer() {}
+ @Override
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) {
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/OnlyChildrenRenderer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/OnlyChildrenRenderer.java
new file mode 100644
index 00000000..267c6b16
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/OnlyChildrenRenderer.java
@@ -0,0 +1,52 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.renderer;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import net.minecraft.client.renderer.GlStateManager;
+
+public class OnlyChildrenRenderer implements Renderer {
+ public static OnlyChildrenRenderer INSTANCE = new OnlyChildrenRenderer();
+ protected OnlyChildrenRenderer() {}
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext renderingContext, DomElement buildContext) {
+ for (DomElement value : buildContext.getChildren()) {
+ Rect original = value.getRelativeBound();
+ if (original == null) continue;
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks,renderingContext, value);
+ GlStateManager.popMatrix();
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/Renderer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/Renderer.java
new file mode 100644
index 00000000..164447cc
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/Renderer.java
@@ -0,0 +1,36 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.renderer;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Position;
+
+public interface Renderer {
+ void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext);
+
+ /**
+ * Transform point so that it's in child's coordinate system.
+ * @param element
+ * @param pos
+ * @return
+ */
+ default Position transformPoint(DomElement element, Position pos) {
+ if (element.getRelativeBound() == null) return pos;
+ return new Position(pos.getX() - element.getRelativeBound().getX(), pos.getY() - element.getRelativeBound().getY());}
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/RenderingContext.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/RenderingContext.java
new file mode 100644
index 00000000..f227b446
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/RenderingContext.java
@@ -0,0 +1,142 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.renderer;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Size;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.util.Stack;
+
+/**
+ * Why are render methods here not static?
+ * Well, might put them all into one gigantic array and only do 1 call.
+ */
+public class RenderingContext {
+ public void drawRect(double left, double top, double right, double bottom, int color) {
+ double i;
+ if (left < right) {
+ i = left;
+ left = right;
+ right = i;
+ }
+
+ if (top < bottom) {
+ i = top;
+ top = bottom;
+ bottom = i;
+ }
+
+ float f = (float)(color >> 24 & 255) / 255.0F;
+ float g = (float)(color >> 16 & 255) / 255.0F;
+ float h = (float)(color >> 8 & 255) / 255.0F;
+ float j = (float)(color & 255) / 255.0F;
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldRenderer = tessellator.getWorldRenderer();
+ GlStateManager.enableBlend();
+ GlStateManager.disableTexture2D();
+ GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
+ GlStateManager.color(g, h, j, f);
+ worldRenderer.begin(7, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(left, bottom, 0.0).endVertex();
+ worldRenderer.pos(right, bottom, 0.0).endVertex();
+ worldRenderer.pos(right, top, 0.0).endVertex();
+ worldRenderer.pos(left, top, 0.0).endVertex();
+ tessellator.draw();
+ GlStateManager.enableTexture2D();
+ }
+ public void drawScaledCustomSizeModalRect(double x, double y, float u, float v, int uWidth, int vHeight, double width, double height, float tileWidth, float tileHeight) {
+ double f = 1.0F / tileWidth;
+ double g = 1.0F / tileHeight;
+ GlStateManager.enableTexture2D();
+ GlStateManager.enableBlend();
+ GlStateManager.color(1,1,1,1);
+ GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldRenderer = tessellator.getWorldRenderer();
+ worldRenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
+ worldRenderer.pos(x, (y + height), 0.0).tex((u * f), ((v + vHeight) * g)).endVertex();
+ worldRenderer.pos((x + width), (y + height), 0.0).tex(((u + uWidth) * f), ((v + vHeight) * g)).endVertex();
+ worldRenderer.pos((x + width), y, 0.0).tex(((u + uWidth) * f), (v * g)).endVertex();
+ worldRenderer.pos(x, y, 0.0).tex((u * f), (v * g)).endVertex();
+ tessellator.draw();
+ }
+
+ public Stack<Rectangle> clips = new Stack<>();
+
+ public Rectangle currentClip() {
+ return clips.empty() ? null : clips.peek();
+ }
+
+ public void pushClip(Rect absBounds, Size size, double x, double y, double width, double height) {
+ if (width < 0 || height < 0) {
+ width = 0;
+ height = 0;
+ }
+
+ Rectangle previousClip;
+ if (clips.size() == 0)
+ previousClip = new Rectangle(0,0,Integer.MAX_VALUE, Integer.MAX_VALUE);
+ else
+ previousClip = clips.peek();
+
+ double xScale = absBounds.getWidth() / size.getWidth();
+ double yScale = absBounds.getHeight() / size.getHeight();
+
+ int resWidth = (int) Math.ceil(width * xScale);
+ int resHeight = (int) Math.ceil(height * yScale);
+ int resX = (int) (absBounds.getX()+ x * xScale);
+ int resY = (int) (absBounds.getY() + y * yScale);
+
+
+ Rectangle newClip = new Rectangle(resX, Minecraft.getMinecraft().displayHeight - (resY+resHeight), resWidth, resHeight);
+ newClip = previousClip.intersection(newClip);
+
+ if (clips.size() == 0)
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+
+ clips.push(newClip);
+
+ if (newClip.width <= 0 || newClip.height <= 0)
+ GL11.glColorMask(false, false ,false ,false);
+ else
+ GL11.glScissor(newClip.x, newClip.y, newClip.width, newClip.height);
+ }
+
+ public void popClip() {
+ Rectangle currentClip = clips.pop();
+
+ GL11.glColorMask(true, true ,true ,true);
+ if (clips.size() == 0)
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+ else {
+ Rectangle newClip = clips.peek();
+ if (newClip.width <= 0 || newClip.height <= 0)
+ GL11.glColorMask(false, false ,false ,false);
+ else
+ GL11.glScissor(newClip.x, newClip.y, newClip.width, newClip.height);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/SingleChildRenderer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/SingleChildRenderer.java
new file mode 100644
index 00000000..cf2645f3
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/renderer/SingleChildRenderer.java
@@ -0,0 +1,52 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.renderer;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.primitive.Rect;
+import net.minecraft.client.renderer.GlStateManager;
+
+public class SingleChildRenderer implements Renderer {
+ public static final SingleChildRenderer INSTANCE = new SingleChildRenderer();
+ protected SingleChildRenderer() {}
+
+ public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext renderingContext, DomElement buildContext) {
+ if (buildContext.getChildren().isEmpty()) return;
+ DomElement value = buildContext.getChildren().get(0);
+
+ Rect original = value.getRelativeBound();
+ if (original == null) return;
+ GlStateManager.translate(original.getX(), original.getY(), 0);
+
+ double absXScale = buildContext.getAbsBounds().getWidth() / buildContext.getSize().getWidth();
+ double absYScale = buildContext.getAbsBounds().getHeight() / buildContext.getSize().getHeight();
+
+ Rect elementABSBound = new Rect(
+ (buildContext.getAbsBounds().getX() + original.getX() * absXScale),
+ (buildContext.getAbsBounds().getY() + original.getY() * absYScale),
+ (original.getWidth() * absXScale),
+ (original.getHeight() * absYScale)
+ );
+ value.setAbsBounds(elementABSBound);
+
+ value.getRenderer().doRender(absMouseX, absMouseY,
+ relMouseX - original.getX(),
+ relMouseY - original.getY(), partialTicks,renderingContext, value);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestPopup.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestPopup.java
new file mode 100644
index 00000000..3f8f90e2
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestPopup.java
@@ -0,0 +1,35 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.view;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups.PopupMgr;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedImportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.On;
+import net.minecraft.util.ResourceLocation;
+
+public class TestPopup extends AnnotatedImportOnlyWidget {
+ public TestPopup() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/testpopup.gui"));
+ }
+
+ @On(functionName = "close")
+ public void onClick() {
+ PopupMgr.getPopupMgr(getDomElement()).closePopup(null);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestView.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestView.java
new file mode 100644
index 00000000..b1bb09ff
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/view/TestView.java
@@ -0,0 +1,50 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2022 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.view;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups.PopupMgr;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.AnnotatedImportOnlyWidget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.On;
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ResourceLocation;
+
+public class TestView extends AnnotatedImportOnlyWidget {
+ public TestView() {
+ super(new ResourceLocation("dungeons_guide_loader:gui/testview.gui"));
+ }
+
+ @Bind(variableName = "variable")
+ public final BindableAttribute<String> bindableAttribute = new BindableAttribute<>(String.class, "");
+
+ @Bind(variableName = "bDisable")
+ public final BindableAttribute<Boolean> bindableAttribute2 = new BindableAttribute<>(Boolean.class, false);
+
+ @Override
+ public void onMount() {
+ super.onMount();
+ bindableAttribute.setValue(Minecraft.getMinecraft().thePlayer.getName());
+ }
+
+ @On(functionName = "buttonClick")
+ public void onClick() {
+ PopupMgr.getPopupMgr(getDomElement()).openPopup(new TestPopup(), null);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedExportOnlyWidget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedExportOnlyWidget.java
new file mode 100644
index 00000000..3dfdf08a
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedExportOnlyWidget.java
@@ -0,0 +1,88 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import org.apache.commons.lang3.reflect.FieldUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is for widgets using xml to describe their layout
+ */
+public abstract class AnnotatedExportOnlyWidget extends Widget implements ExportedWidget {
+
+ private Map<String, BindableAttribute> exportedAttributes = null;
+
+ @Export(attributeName = "ref")
+ public final BindableAttribute<DomElement> ref = new BindableAttribute<>(DomElement.class);
+
+ public DomElement createDomElement(DomElement parent) {
+ DomElement domElement = super.createDomElement(parent);
+ ref.setValue(domElement);
+ return domElement;
+ }
+
+ public abstract List<Widget> build(DomElement buildContext);
+
+ protected static Map<String, BindableAttribute> getExportedAttributes(Class clazz, Object inst) {
+ Map<String, BindableAttribute> attributeMap = new HashMap<>();
+ for (Field declaredField : FieldUtils.getAllFields(clazz)) {
+ if (declaredField.getAnnotation(Export.class) != null) {
+ Export export = declaredField.getAnnotation(Export.class);
+
+ if (declaredField.getType() != BindableAttribute.class) throw new IllegalStateException("Export Annotation must be applied on BindableAttribute field. : "+declaredField.getName());
+ if (!Modifier.isFinal(declaredField.getModifiers())) throw new IllegalStateException("Exported Bindable Attribute must be final : "+declaredField.getName());
+
+ try {
+ attributeMap.put(export.attributeName(), (BindableAttribute) declaredField.get(inst));
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return attributeMap;
+ }
+
+
+
+ private Map<String, BindableAttribute> getExportedAttributes() {
+ if (exportedAttributes == null)
+ exportedAttributes = getExportedAttributes(getClass(), this);
+ return exportedAttributes;
+ }
+
+ @Override
+ public <T> BindableAttribute<T> getExportedAttribute(String attributeName) {
+ return getExportedAttributes().get(attributeName);
+ }
+
+ public void onUnmount() {
+ for (BindableAttribute value : getExportedAttributes().values()) {
+ value.unexportAll();
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedImportOnlyWidget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedImportOnlyWidget.java
new file mode 100644
index 00000000..29bcc4ef
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedImportOnlyWidget.java
@@ -0,0 +1,125 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Bind;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.On;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.Parser;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserElement;
+import net.minecraft.util.ResourceLocation;
+import org.apache.commons.lang3.reflect.FieldUtils;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is for widgets using xml to describe their layout
+ */
+public abstract class AnnotatedImportOnlyWidget extends Widget implements ImportingWidget {
+ private Map<String, BindableAttribute> importedAttributes = null;
+ private Map<String, MethodHandle> invocationTargets = null;
+
+ private final ResourceLocation target;
+
+ public AnnotatedImportOnlyWidget(ResourceLocation resourceLocation) {
+ target = resourceLocation;
+ }
+
+ public final List<Widget> build(DomElement buildContext) {
+ try (Parser parser = DomElementRegistry.obtainParser(target)) {
+ ParserElement element = parser.getRootNode();
+ ParsedWidgetConverter converter = DomElementRegistry.obtainConverter(element.getNodeName());
+ Widget w = converter.convert(this, element);
+ return Collections.singletonList(w);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected static Map<String, BindableAttribute> getImportedAttributes(Class clazz, Object inst) {
+ Map<String, BindableAttribute> attributes = new HashMap<>();
+ for (Field declaredField : FieldUtils.getAllFieldsList(clazz)) {
+ if (declaredField.getAnnotation(Bind.class) != null) {
+ Bind bind = declaredField.getAnnotation(Bind.class);
+
+ if (declaredField.getType() != BindableAttribute.class) throw new IllegalStateException("Bind Annotation must be applied on BindableAttribute field.");
+ if (!Modifier.isFinal(declaredField.getModifiers())) throw new IllegalStateException("Bound Bindable Attribute must be final ");
+
+ try {
+ attributes.put(bind.variableName(), (BindableAttribute) declaredField.get(inst));
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ return attributes;
+ }
+
+ protected static Map<String, MethodHandle> getInvocationTargets(Class clazz, Object inst) {
+ Map<String, MethodHandle> invocationTargets = new HashMap<>();
+ for (Method declaredMethod : clazz.getDeclaredMethods()) {
+ if (declaredMethod.getAnnotation(On.class) != null) {
+ On on = declaredMethod.getAnnotation(On.class);
+
+ try {
+ MethodHandle handle = MethodHandles.publicLookup().unreflect(declaredMethod);
+ invocationTargets.put(on.functionName(), handle);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return invocationTargets;
+ }
+
+ private Map<String, BindableAttribute> getImportedAttributes() {
+ if (importedAttributes == null)
+ importedAttributes = getImportedAttributes(getClass(), this);
+ return importedAttributes;
+ }
+
+ @Override
+ public <T> BindableAttribute<T> getBindTarget(String variableName, BindableAttribute<T> _) {
+ return getImportedAttributes().get(variableName);
+ }
+
+ private Map<String, MethodHandle> getInvocationTargets() {
+ if (invocationTargets == null)
+ invocationTargets = getInvocationTargets(getClass(), this);
+ return invocationTargets;
+ }
+ @Override
+ public MethodHandle getInvocationTarget(String functionName) {
+ return getInvocationTargets().get(functionName);
+ }
+}
+
+
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedWidget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedWidget.java
new file mode 100644
index 00000000..6b33b739
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/AnnotatedWidget.java
@@ -0,0 +1,151 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Export;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Passthrough;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations.Passthroughs;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.Parser;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserElement;
+import net.minecraft.util.ResourceLocation;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.util.*;
+
+/**
+ * This class is for widgets using xml to describe their layout
+ */
+public abstract class AnnotatedWidget extends Widget implements ImportingWidget, ExportedWidget {
+ private Map<String, BindableAttribute> importedAttributes = null;
+ private Map<String, BindableAttribute> exportedAttributes = null;
+ private Map<String, MethodHandle> invocationTargets = null;
+ @Export(attributeName = "ref")
+ public final BindableAttribute<DomElement> ref = new BindableAttribute<>(DomElement.class);
+
+
+ private final ResourceLocation target;
+ public AnnotatedWidget(ResourceLocation location) {
+ target = location;
+ }
+
+ public DomElement createDomElement(DomElement parent) {
+ DomElement domElement = super.createDomElement(parent);
+ ref.setValue(domElement);
+ return domElement;
+ }
+
+ public final List<Widget> build(DomElement buildContext) {
+ try (Parser parser = DomElementRegistry.obtainParser(target)) {
+ ParserElement element = parser.getRootNode();
+ if (element.getNodeName().equals("multi")) {
+ List<Widget> widgets = new ArrayList<>();
+ for (ParserElement child : element.getChildren()) {
+ ParsedWidgetConverter converter = DomElementRegistry.obtainConverter(child.getNodeName());
+ Widget w = converter.convert(this, child);
+ widgets.add(w);
+ }
+ return widgets;
+ } else {
+ ParsedWidgetConverter converter = DomElementRegistry.obtainConverter(element.getNodeName());
+ Widget w = converter.convert(this, element);
+ return Collections.singletonList(w);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ protected static Pair<Map<String, BindableAttribute>, Map<String, BindableAttribute>> createPassthroughs(Class clazz) {
+ if (clazz.getAnnotation(Passthroughs.class) == null && clazz.getAnnotation(Passthrough.class) == null) return Pair.of(Collections.EMPTY_MAP, Collections.EMPTY_MAP);
+ Map<String, BindableAttribute> attributeMap1 = new HashMap<>();
+ Map<String, BindableAttribute> attributeMap2 = new HashMap<>();
+ if (clazz.getAnnotation(Passthroughs.class) != null) {
+ Passthrough[] throughs = ((Passthroughs) clazz.getAnnotation(Passthroughs.class)).value();
+ for (Passthrough through : throughs) {
+ BindableAttribute attribute = new BindableAttribute(through.type());
+ attributeMap1.put(through.exportName(), attribute);
+ attributeMap2.put(through.bindName(), attribute);
+ }
+ }
+ if (clazz.getAnnotation(Passthrough.class) != null) {
+ Passthrough through = (Passthrough) clazz.getAnnotation(Passthrough.class);
+ BindableAttribute attribute = new BindableAttribute(through.type());
+ attributeMap1.put(through.exportName(), attribute);
+ attributeMap2.put(through.bindName(), attribute);
+ }
+ return Pair.of(attributeMap1, attributeMap2);
+ }
+
+
+ private Map<String, BindableAttribute> getExportedAttributes() {
+ if (exportedAttributes == null) {
+ exportedAttributes = AnnotatedExportOnlyWidget.getExportedAttributes(getClass(), this);
+ importedAttributes = AnnotatedImportOnlyWidget.getImportedAttributes(getClass(), this);
+
+ Pair<Map<String, BindableAttribute>, Map<String, BindableAttribute>> stuff = createPassthroughs(getClass());
+ exportedAttributes.putAll(stuff.getLeft());
+ importedAttributes.putAll(stuff.getRight());
+ }
+ return exportedAttributes;
+ }
+
+ @Override
+ public <T> BindableAttribute<T> getExportedAttribute(String attributeName) {
+ return getExportedAttributes().get(attributeName);
+ }
+
+ private Map<String, BindableAttribute> getImportedAttributes() {
+ if (importedAttributes == null) {
+ exportedAttributes = AnnotatedExportOnlyWidget.getExportedAttributes(getClass(), this);
+ importedAttributes = AnnotatedImportOnlyWidget.getImportedAttributes(getClass(), this);
+
+ Pair<Map<String, BindableAttribute>, Map<String, BindableAttribute>> stuff = createPassthroughs(getClass());
+ exportedAttributes.putAll(stuff.getLeft());
+ importedAttributes.putAll(stuff.getRight());
+ }
+ return importedAttributes;
+ }
+
+ @Override
+ public <T> BindableAttribute<T> getBindTarget(String variableName, BindableAttribute<T> _) {
+ return getImportedAttributes().get(variableName);
+ }
+ private Map<String, MethodHandle> getInvocationTargets() {
+ if (invocationTargets == null)
+ invocationTargets = AnnotatedImportOnlyWidget.getInvocationTargets(getClass(), this);
+ return invocationTargets;
+ }
+ @Override
+ public MethodHandle getInvocationTarget(String functionName) {
+ return getInvocationTargets().get(functionName);
+ }
+
+
+ public void onUnmount() {
+ for (BindableAttribute value : getExportedAttributes().values()) {
+ value.unexportAll();
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidget.java
new file mode 100644
index 00000000..d8069963
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidget.java
@@ -0,0 +1,99 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.DomElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.Parser;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserElement;
+import net.minecraft.util.ResourceLocation;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DelegatingWidget extends Widget implements ExportedWidget, ImportingWidget {
+
+ private final List<Widget> widgets;
+ public DelegatingWidget(ResourceLocation location) {
+
+
+ try (Parser parser = DomElementRegistry.obtainParser(location)) {
+ ParserElement element = parser.getRootNode();
+ if (!element.getNodeName().equals("wrapper")) throw new IllegalArgumentException("Delegating widget root element Must be wrapper");
+ List<Widget> widgets = new ArrayList<>();
+ for (ParserElement child : element.getChildren()) {
+ ParsedWidgetConverter converter = DomElementRegistry.obtainConverter(child.getNodeName());
+ Widget w = converter.convert(this, child);
+ widgets.add(w);
+ }
+ this.widgets = widgets;
+
+
+ for (String attribute : element.getAttributes()) {
+ getExportedAttribute(attribute).setValue(
+ StringConversions.convert(getExportedAttribute(attribute).getType(), element.getAttributeValue(attribute))
+ );
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ exportedAttributes.put("ref", ref);
+ }
+
+ private final Map<String, BindableAttribute> exportedAttributes = new HashMap<>();
+
+ private BindableAttribute<DomElement> ref = new BindableAttribute<>(DomElement.class);
+
+
+ @Override
+ public <T> BindableAttribute<T> getExportedAttribute(String attributeName) {
+ return exportedAttributes.get(attributeName);
+ }
+ @Override
+ public <T> BindableAttribute<T> getBindTarget(String variableName, BindableAttribute<T> from) {
+ if (exportedAttributes.containsKey(variableName))
+ return exportedAttributes.get(variableName);
+ BindableAttribute<T> newThing = new BindableAttribute<T>(from.getType());
+ exportedAttributes.put(variableName, newThing);
+ return newThing;
+ }
+
+ @Override
+ public MethodHandle getInvocationTarget(String functionName) {
+ throw new UnsupportedOperationException("lol");
+ }
+
+ @Override
+ public DomElement createDomElement(DomElement parent) {
+ DomElement domElement = super.createDomElement(parent);
+ ref.setValue(domElement);
+ return domElement;
+ }
+
+ @Override
+ public List<Widget> build(DomElement buildContext) {
+ return widgets;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidgetConverter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidgetConverter.java
new file mode 100644
index 00000000..0f6e5b88
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DelegatingWidgetConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import net.minecraft.util.ResourceLocation;
+
+public class DelegatingWidgetConverter<R extends Widget & ImportingWidget> extends PropByPropParsedWidgetConverter<DelegatingWidget, R> {
+ private final ResourceLocation resourceLocation;
+ public DelegatingWidgetConverter(ResourceLocation resourceLocation) {
+ this.resourceLocation = resourceLocation;
+ }
+
+ public DelegatingWidget instantiateWidget() {
+ return new DelegatingWidget(resourceLocation);
+ }
+
+ @Override
+ public BindableAttribute getExportedAttribute(DelegatingWidget widget, String attributeName) {
+ return widget.getExportedAttribute(attributeName);
+ }
+
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DomElementRegistry.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DomElementRegistry.java
new file mode 100644
index 00000000..8ca03ab5
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/DomElementRegistry.java
@@ -0,0 +1,107 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.image.ResourceImage;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.image.URLImage;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.elements.popups.PopupMgr;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.view.TestView;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.Parser;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserException;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.W3CBackedParser;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.resources.IResource;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DomElementRegistry {
+ private static final Map<String, ParsedWidgetConverter> converters = new HashMap<>();
+
+ public static void register(String xmlName, ParsedWidgetConverter converter) {
+ converters.put(xmlName, converter);
+ }
+
+ public static <T extends Widget, R extends Widget & ImportingWidget> ParsedWidgetConverter<T, R> obtainConverter(String name) {
+ if (!converters.containsKey(name))
+ System.out.println("Try to get nonexistent widget "+name);
+ return converters.get(name);
+ }
+
+ static {
+ register("stack", new ExportedWidgetConverter(Stack::new));
+ register("scaler", new ExportedWidgetConverter(Scaler::new));
+ register("row", new ExportedWidgetConverter(Row::new));
+ register("padding", new ExportedWidgetConverter(Padding::new));
+ register("col", new ExportedWidgetConverter(Column::new));
+ register("bgcolor", new ExportedWidgetConverter(Background::new));
+ register("align", new ExportedWidgetConverter(Align::new));
+ register("flexible", new ExportedWidgetConverter(Flexible::new));
+ register("line", new ExportedWidgetConverter(Line::new));
+ register("border", new ExportedWidgetConverter(Border::new));
+ register("Text", new ExportedWidgetConverter(Text::new));
+ register("slot", new ExportedWidgetConverter(Slot::new));
+ register("clip", new ExportedWidgetConverter(Clip::new));
+ register("measure", new ExportedWidgetConverter(Measure::new));
+ register("ConstrainedBox", new ExportedWidgetConverter(ConstrainedBox::new));
+ register("UnconstrainedBox", new ExportedWidgetConverter(UnconstrainedBox::new));
+ register("absXY", new ExportedWidgetConverter(AbsXY::new));
+ register("Placeholder", new ExportedWidgetConverter(Placeholder::new));
+ register("TextField", new ExportedWidgetConverter(TextField::new));
+ register("PopupManager", new ExportedWidgetConverter(PopupMgr::new));
+ register("AbstractButton", new ExportedWidgetConverter(Button::new));
+ register("AbstractToggleButton", new ExportedWidgetConverter(ToggleButton::new));
+ register("ScrollablePanel", new ExportedWidgetConverter(ScrollablePanel::new));
+ register("AbstractScrollBar", new ExportedWidgetConverter(Scrollbar::new));
+ register("aspectRatio", new ExportedWidgetConverter(AspectRatioFitter::new));
+ register("BareResourceImage", new ExportedWidgetConverter(ResourceImage::new));
+ register("IntrinsicWidth", new ExportedWidgetConverter(IntrinsicWidth::new));
+ register("IntrinsicHeight", new ExportedWidgetConverter(IntrinsicHeight::new));
+ register("TestView", new ExportedWidgetConverter(TestView::new));
+ register("RoundRect", new ExportedWidgetConverter(RoundRect::new));
+ register("CircularRect", new ExportedWidgetConverter(CircularRect::new));
+ register("Navigator", new ExportedWidgetConverter(Navigator::new));
+ register("Stencil", new ExportedWidgetConverter(Stencil::new));
+ register("InvertStencil", new ExportedWidgetConverter(NegativeStencil::new));
+ register("WrapGrid", new ExportedWidgetConverter(Wrap::new));
+
+ register("ColorButton", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/simpleButton.gui")));
+ register("RoundButton", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/dgButton.gui")));
+ register("IconButton", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/iconButton.gui")));
+ register("SimpleToggleButton", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/simpleToggleButton.gui")));
+ register("SimpleHorizontalScrollBar", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/simpleHorizontalScrollBar.gui")));
+ register("SimpleVerticalScrollBar", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/simpleVerticalScrollBar.gui")));
+ register("SlowList", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/slowlist.gui")));
+ register("size", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/size.gui")));
+ register("ResourceImage", new DelegatingWidgetConverter(new ResourceLocation("dungeons_guide_loader:gui/elements/ratioResourceImage.gui")));
+ register("UrlImage", new ExportedWidgetConverter(URLImage::new));
+ register("SelectiveContainer", new ExportedWidgetConverter(SelectiveContainer::new));
+ }
+
+ public static Parser obtainParser(ResourceLocation resourceLocation) {
+ try {
+ IResource iResource = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation);
+ return new W3CBackedParser(iResource.getInputStream());
+ } catch (Exception e) {
+ throw new ParserException("An error occurred while parsing "+resourceLocation, e);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidget.java
new file mode 100644
index 00000000..e8fe6798
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidget.java
@@ -0,0 +1,25 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+
+public interface ExportedWidget {
+ <T> BindableAttribute<T> getExportedAttribute(String attributeName);
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidgetConverter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidgetConverter.java
new file mode 100644
index 00000000..5410a576
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ExportedWidgetConverter.java
@@ -0,0 +1,42 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public class ExportedWidgetConverter<W extends Widget & ExportedWidget, R extends Widget & ImportingWidget> extends PropByPropParsedWidgetConverter<W, R> {
+ private Supplier<W> constructor;
+
+ public ExportedWidgetConverter(Supplier<W> constructor) {
+ this.constructor = Objects.requireNonNull(constructor);
+ }
+
+
+ public W instantiateWidget() {
+ return constructor.get();
+ }
+
+ public final BindableAttribute getExportedAttribute(W widget, String attributeName) {
+ return widget.getExportedAttribute(attributeName);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ImportingWidget.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ImportingWidget.java
new file mode 100644
index 00000000..2a5921e3
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ImportingWidget.java
@@ -0,0 +1,29 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+
+import java.lang.invoke.MethodHandle;
+
+public interface ImportingWidget {
+ <T> BindableAttribute<T> getBindTarget(String variableName, BindableAttribute<T> from);
+
+ MethodHandle getInvocationTarget(String functionName);
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ParsedWidgetConverter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ParsedWidgetConverter.java
new file mode 100644
index 00000000..534280e3
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/ParsedWidgetConverter.java
@@ -0,0 +1,43 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserElement;
+
+/**
+ * This class is for converting xml elements to widgets, to be used by XMLWidget
+ *
+ * type parameter R should be kept.
+ */
+public interface ParsedWidgetConverter<W extends Widget, R extends Widget & ImportingWidget> {
+ /**
+ * Converts xml element into a Widget
+ * xmlWidget is the widget that triggered reading the xml file
+ * Element ofc has all the props
+ * It's the converter's job to instantiate the widget and bind to appropriate variables in XMLWidget
+ * ^^ yes, I love dependency injection-like stuff
+ *
+ *
+ * @param rootWidget
+ * @param element
+ * @return
+ */
+ W convert(R rootWidget, ParserElement element);
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/PropByPropParsedWidgetConverter.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/PropByPropParsedWidgetConverter.java
new file mode 100644
index 00000000..8f6e2feb
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/PropByPropParsedWidgetConverter.java
@@ -0,0 +1,140 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.BindableAttribute;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserElement;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.ParserElementList;
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data.WidgetList;
+
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public abstract class PropByPropParsedWidgetConverter<W extends Widget, R extends Widget & ImportingWidget> implements ParsedWidgetConverter<W, R> {
+
+ public abstract W instantiateWidget();
+
+ public abstract BindableAttribute getExportedAttribute(W widget, String attributeName);
+
+ @Override
+ public W convert(R rootWidget, ParserElement element) {
+ W partial = instantiateWidget();
+
+ Set<String> boundSlots = new HashSet<>();
+ for (String attribute : element.getAttributes()) {
+ if (attribute.startsWith("bind:")) {
+ String name = attribute.substring(5);
+ String variable = element.getAttributeValue(attribute);
+ if (name.startsWith("_"))
+ boundSlots.add(name);
+
+ BindableAttribute exported = getExportedAttribute(partial, name);
+ if (exported == null) throw new IllegalStateException("No exported variable found named "+name+"!");
+ BindableAttribute bound = rootWidget.getBindTarget(variable, exported);
+ if (bound == null) throw new IllegalStateException("No bind target found for "+attribute+" for "+variable+"!");
+ exported.exportTo(bound);
+ } else if (attribute.startsWith("on:")) {
+ String name = attribute.substring(3);
+ String variable = element.getAttributeValue(attribute);
+
+ BindableAttribute exported = getExportedAttribute(partial, name);
+ if (exported == null) throw new IllegalStateException("No exported invocation target found named "+name+"!");
+ MethodHandle invocationTarget = rootWidget.getInvocationTarget(variable);
+ if (invocationTarget == null) throw new IllegalStateException("No invocationTarget target found for "+attribute+" for "+variable+"!");
+
+ // convert methodhandle to functional interface.
+ Class functionalInterface = exported.getType();
+ if (!functionalInterface.isInterface()) throw new IllegalArgumentException("Should be interface");
+ if (functionalInterface.getDeclaredMethods().length != 1)
+ throw new IllegalArgumentException("Should be functional interface");
+ Method m = functionalInterface.getDeclaredMethods()[0];
+
+ MethodType mt = MethodType.methodType(m.getReturnType(), m.getParameterTypes());
+ try {
+ Object obj = LambdaMetafactory.metafactory(MethodHandles.lookup(), m.getName(),
+ MethodType.methodType(functionalInterface, rootWidget.getClass()),
+ mt,
+ invocationTarget,
+ invocationTarget.type().dropParameterTypes(0, 1))
+ .getTarget()
+ .invoke(rootWidget);
+ exported.setValue(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ // this should bind to methodhandle
+ } else if (attribute.equals("slot")) {
+ } else {
+ BindableAttribute bindableAttribute = getExportedAttribute(partial, attribute);
+ if (bindableAttribute == null) throw new IllegalStateException("No exported variable found named "+attribute+"!");
+ bindableAttribute.setValue(element.getConvertedAttributeValue(bindableAttribute.getType(), attribute));
+ }
+ }
+
+
+ Map<String, List<ParserElement>> children = new HashMap<>();
+ children.put("", new LinkedList<>());
+ for (ParserElement child : element.getChildren()) {
+ String slotName = child.getAttributeValue("slot");
+ if (slotName == null) slotName = "";
+
+ if (!children.containsKey(slotName))
+ children.put(slotName, new LinkedList<>());
+ children.get(slotName).add(child);
+ }
+
+ for (Map.Entry<String, List<ParserElement>> stringListEntry : children.entrySet()) {
+ if (boundSlots.contains("_"+stringListEntry.getKey())) continue;
+ BindableAttribute attribute = getExportedAttribute(partial, "_"+stringListEntry.getKey());
+ if (attribute == null) {
+ // ???
+ } else {
+ List<ParserElement> elements = stringListEntry.getValue();
+ if (attribute.getType() == ParserElement.class) {
+ if (elements.size() > 1) throw new IllegalArgumentException("More than 1 for single parser element: "+stringListEntry.getKey());
+ if (elements.size() == 1)
+ attribute.setValue(elements.get(0));
+ else
+ attribute.setValue(null);
+ } else if (attribute.getType() == Widget.class) {
+ if (elements.size() > 1) throw new IllegalArgumentException("More than 1 for single widget: "+stringListEntry.getKey());
+ if (elements.size() == 1)
+ attribute.setValue(DomElementRegistry.obtainConverter(elements.get(0).getNodeName())
+ .convert(rootWidget, elements.get(0)));
+ else attribute.setValue(null);
+ } else if (attribute.getType() == ParserElementList.class) {
+ attribute.setValue(elements);
+ } else if (attribute.getType() == WidgetList.class) {
+ attribute.setValue(
+ elements.stream()
+ .map(a -> DomElementRegistry.obtainConverter(a.getNodeName()).convert(rootWidget, a))
+ .collect(Collectors.toList()));
+ }
+ }
+ }
+
+ return partial;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/StringConversions.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/StringConversions.java
new file mode 100644
index 00000000..3d3a3639
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/StringConversions.java
@@ -0,0 +1,49 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml;
+
+public final class StringConversions {
+ public static <T> T convert(Class<T> clazz, String val) {
+ if (clazz== Float.class) {
+ return (T) Float.valueOf(val);
+ } else if (clazz== Double.class) {
+ return (T) Double.valueOf(val);
+ } else if (clazz== Integer.class) {
+ if (val.startsWith("#"))
+ return (T) Integer.valueOf(Integer.parseUnsignedInt(val.substring(1), 16));
+ if (val.startsWith("0x"))
+ return (T) Integer.valueOf(Integer.parseUnsignedInt(val.substring(2), 16));
+ return (T) Integer.valueOf(val);
+ } else if (clazz== Short.class) {
+ if (val.startsWith("0x"))
+ return (T) Short.valueOf((short) Integer.parseUnsignedInt(val.substring(2), 16));
+ return (T) Short.valueOf(val);
+ } else if (clazz== String.class) {
+ return (T) val;
+ } else if (clazz.isEnum()) {
+ for (Object enumConstant : clazz.getEnumConstants()) {
+ if (val.equalsIgnoreCase(enumConstant.toString()))
+ return (T) enumConstant;
+ }
+ } else if (clazz== Boolean.class) {
+ return (T) Boolean.valueOf(val);
+ }
+ throw new UnsupportedOperationException("cant convert to "+clazz.getName()+": "+val);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Bind.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Bind.java
new file mode 100644
index 00000000..b4fff104
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Bind.java
@@ -0,0 +1,30 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Bind {
+ String variableName();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Export.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Export.java
new file mode 100644
index 00000000..76858878
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Export.java
@@ -0,0 +1,30 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Export {
+ String attributeName();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/On.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/On.java
new file mode 100644
index 00000000..89e125d9
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/On.java
@@ -0,0 +1,30 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface On {
+ String functionName();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthrough.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthrough.java
new file mode 100644
index 00000000..31cc557e
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthrough.java
@@ -0,0 +1,32 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Repeatable(Passthroughs.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Passthrough {
+
+ String exportName();
+ Class type();
+
+ String bindName();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthroughs.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthroughs.java
new file mode 100644
index 00000000..d7888981
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/annotations/Passthroughs.java
@@ -0,0 +1,30 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Passthroughs {
+ Passthrough[] value();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/Parser.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/Parser.java
new file mode 100644
index 00000000..ad91be35
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/Parser.java
@@ -0,0 +1,25 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+import java.io.Closeable;
+
+public interface Parser extends AutoCloseable, Closeable {
+ ParserElement getRootNode();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElement.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElement.java
new file mode 100644
index 00000000..2e2259e7
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElement.java
@@ -0,0 +1,34 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+import java.util.List;
+import java.util.Set;
+
+public interface ParserElement {
+ String getNodeName();
+
+ String getAttributeValue(String attribute);
+
+ Set<String> getAttributes();
+
+ <T> T getConvertedAttributeValue(Class<T> clazz, String attribute);
+
+ List<ParserElement> getChildren();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElementList.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElementList.java
new file mode 100644
index 00000000..af5e224c
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserElementList.java
@@ -0,0 +1,24 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+import java.util.List;
+
+public interface ParserElementList extends List<ParserElement> {
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserException.java
new file mode 100644
index 00000000..9364f907
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/ParserException.java
@@ -0,0 +1,38 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+public class ParserException extends RuntimeException {
+ public ParserException(Throwable cause) {super(cause);}
+
+ public ParserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public ParserException() {
+ }
+
+ public ParserException(String message) {
+ super(message);
+ }
+
+ public ParserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParser.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParser.java
new file mode 100644
index 00000000..31d77b36
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParser.java
@@ -0,0 +1,70 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class W3CBackedParser implements Parser {
+ public static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ static {
+ factory.setIgnoringComments(true);
+ factory.setValidating(false);
+ factory.setNamespaceAware(false);
+ factory.setExpandEntityReferences(false);
+ factory.setCoalescing(false);
+ }
+
+
+ private final InputStream inputStream;
+ private final Element rootElement;
+
+ public W3CBackedParser(InputStream inputStream) throws IOException, SAXException, ParserConfigurationException {
+ this.inputStream = inputStream;
+
+ DocumentBuilder documentBuilder = factory.newDocumentBuilder();
+ Document document = documentBuilder.parse(inputStream);
+ NodeList nodeList = document.getChildNodes();
+ Element semiRoot = null;
+ for (int i = 0; i < nodeList.getLength(); i++)
+ if (nodeList.item(i) instanceof Element) {
+ semiRoot = (Element) nodeList.item(i);
+ break;
+ }
+ rootElement = semiRoot;
+ }
+
+ @Override
+ public ParserElement getRootNode() {
+ return new W3CBackedParserElement(rootElement);
+ }
+
+ @Override
+ public void close() throws IOException {
+ inputStream.close();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParserElement.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParserElement.java
new file mode 100644
index 00000000..4f4e19dd
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/W3CBackedParserElement.java
@@ -0,0 +1,74 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.xml.StringConversions;
+import org.w3c.dom.*;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class W3CBackedParserElement implements ParserElement {
+ private final Element backingElement;
+
+ public W3CBackedParserElement(Element element) {
+ this.backingElement = element;
+ }
+
+ @Override
+ public String getNodeName() {
+ return backingElement.getTagName();
+ }
+
+ @Override
+ public String getAttributeValue(String attribute) {
+ return backingElement.getAttribute(attribute);
+ }
+
+ @Override
+ public Set<String> getAttributes() {
+ NamedNodeMap nodeList = backingElement.getAttributes();
+ Set<String> list = new HashSet<>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node n = nodeList.item(i);
+ if (!(n instanceof Attr)) continue;
+ list.add(((Attr) n).getName());
+ }
+ return list;
+ }
+
+ @Override
+ public <T> T getConvertedAttributeValue(Class<T> clazz, String attribute) {
+ return StringConversions.convert(clazz, getAttributeValue(attribute));
+ }
+
+ @Override
+ public List<ParserElement> getChildren() {
+ NodeList nodeList = backingElement.getChildNodes();
+ List<ParserElement> list = new LinkedList<>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node n = nodeList.item(i);
+ if (!(n instanceof Element)) continue;
+ list.add(new W3CBackedParserElement((Element) n));
+ }
+ return list;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/WidgetList.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/WidgetList.java
new file mode 100644
index 00000000..418e9b48
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/guiv2/xml/data/WidgetList.java
@@ -0,0 +1,26 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2023 cyoung06 (syeyoung)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.guiv2.xml.data;
+
+import kr.syeyoung.dungeonsguide.launcher.guiv2.Widget;
+
+import java.util.List;
+
+public interface WidgetList extends List<Widget> {
+}