diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java index 130c8fbb948..a20c4ebedf2 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java @@ -1,7 +1,7 @@ package com.gregtechceu.gtceu.api.mui; import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import lombok.Getter; import org.apache.logging.log4j.Level; @@ -10,7 +10,7 @@ public class GuiError { - public static void throwNew(IGuiElement guiElement, Type type, String msg) { + public static void throwNew(IWidget guiElement, Type type, String msg) { if (GTCEu.isClientSide()) { GuiErrorHandler.INSTANCE.pushError(guiElement, type, msg); } @@ -21,11 +21,11 @@ public static void throwNew(IGuiElement guiElement, Type type, String msg) { @Getter private final String msg; @Getter - private final IGuiElement reference; + private final IWidget reference; @Getter private final Type type; - protected GuiError(String msg, IGuiElement reference, Type type) { + protected GuiError(String msg, IWidget reference, Type type) { this.msg = msg; this.reference = reference; this.type = type; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java index 185344fc6c1..4fb450f38ed 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java @@ -1,7 +1,7 @@ package com.gregtechceu.gtceu.api.mui; import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -28,7 +28,7 @@ public void clear() { this.errors.clear(); } - void pushError(IGuiElement reference, GuiError.Type type, String msg) { + void pushError(IWidget reference, GuiError.Type type, String msg) { GuiError error = new GuiError(msg, reference, type); if (this.errorSet.add(error)) { GTCEu.LOGGER.log(error.getLevel(), error); diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java index 039e3c3fd89..1f5a6fe2c50 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java @@ -3,6 +3,7 @@ import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; import com.gregtechceu.gtceu.api.mui.drawable.Scrollbar; import com.gregtechceu.gtceu.api.mui.theme.*; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.serialization.json.JsonBuilder; @@ -149,7 +150,35 @@ default void registerTheme(ThemeBuilder themeBuilder) { * @param defaultTheme default theme if no theme was found * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} */ - ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme); + default ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme, + @Nullable String fallbackTheme) { + return getThemeForScreen(owner, name, null, defaultTheme, fallbackTheme); + } + + /** + * Gets the appropriate theme for a screen. + * + * @param owner owner of the screen + * @param name name of the screen + * @param panel the name + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + ITheme getThemeForScreen(String owner, String name, @Nullable String panel, @Nullable String defaultTheme, + @Nullable String fallbackTheme); + + /** + * Gets the appropriate theme for a specific panel. + * + * @param panel the panel to find a theme for + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + default ITheme getThemeForScreen(ModularPanel panel, @Nullable String defaultTheme) { + ModularScreen screen = panel.getScreen(); + return getThemeForScreen(screen.getOwner(), screen.getName(), panel.getName(), defaultTheme, + screen.getThemeOverride()); + } /** * Gets the appropriate theme for a screen. @@ -159,7 +188,7 @@ default void registerTheme(ThemeBuilder themeBuilder) { * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} */ default ITheme getThemeForScreen(ModularScreen screen, @Nullable String defaultTheme) { - return getThemeForScreen(screen.getOwner(), screen.getName(), defaultTheme); + return getThemeForScreen(screen.getOwner(), screen.getName(), defaultTheme, null); } /** diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITreeNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITreeNode.java new file mode 100644 index 00000000000..734788304e9 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITreeNode.java @@ -0,0 +1,18 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import java.util.List; + +public interface ITreeNode> { + + T getParent(); + + default boolean hasParent() { + return getParent() != null; + } + + List getChildren(); + + default boolean hasChildren() { + return !getChildren().isEmpty(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java index 58a9ca3923e..ab921d34285 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.mui.base; +import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.mui.factory.GuiData; import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; @@ -25,7 +26,10 @@ public interface IUIHolder { */ @OnlyIn(Dist.CLIENT) default ModularScreen createScreen(T data, ModularPanel mainPanel) { - return new ModularScreen(mainPanel); + GTCEu.LOGGER + .warn("IGuiHolder.createScreen() should be overridden to pass your own mod id to the ModularScreen. " + + "In future versions this method must be overridden or else it will crash!"); + return new ModularScreen(GTCEu.MOD_ID, mainPanel); } /** diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeParent.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeParent.java new file mode 100644 index 00000000000..df16e1125cb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeParent.java @@ -0,0 +1,75 @@ +package com.gregtechceu.gtceu.api.mui.base.layout; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; + +public interface IResizeParent { + + /** + * @return area of the element + */ + Area getArea(); + + /** + * @return true if the relative x position is calculated + */ + boolean isXCalculated(); + + /** + * @return true if the relative y position is calculated + */ + boolean isYCalculated(); + + /** + * @return true if the width is calculated + */ + boolean isWidthCalculated(); + + /** + * @return true if the height is calculated + */ + boolean isHeightCalculated(); + + boolean areChildrenCalculated(); + + boolean isLayoutDone(); + + boolean canRelayout(boolean isParentLayout); + + default boolean isSizeCalculated(GuiAxis axis) { + return axis.isHorizontal() ? isWidthCalculated() : isHeightCalculated(); + } + + default boolean isPosCalculated(GuiAxis axis) { + return axis.isHorizontal() ? isXCalculated() : isYCalculated(); + } + + /** + * @return true if the relative position and size are fully calculated + */ + default boolean isSelfFullyCalculated(boolean isParentLayout) { + return isSelfFullyCalculated() && !canRelayout(isParentLayout); + } + + default boolean isSelfFullyCalculated() { + return isXCalculated() && isYCalculated() && isWidthCalculated() && isHeightCalculated(); + } + + default boolean isFullyCalculated() { + return isSelfFullyCalculated() && areChildrenCalculated() && isLayoutDone(); + } + + default boolean isFullyCalculated(boolean isParentLayout) { + return isFullyCalculated() && !canRelayout(isParentLayout); + } + + /** + * @return true if margin and padding are applied on the x-axis + */ + boolean isXMarginPaddingApplied(); + + /** + * @return true if margin and padding are applied on the y-axis + */ + boolean isYMarginPaddingApplied(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java index 91c2827bb58..d1a7b947c71 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java @@ -1,103 +1,43 @@ package com.gregtechceu.gtceu.api.mui.base.layout; import com.gregtechceu.gtceu.api.mui.base.GuiAxis; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; /** * An interface that handles resizing of widgets. * Usually this interface is not implemented by the users of this library or will even interact with it. */ -public interface IResizeable { +public interface IResizeable extends IResizeParent { /** * Called once before resizing */ - void initResizing(); + void initResizing(boolean onOpen); /** * Resizes the given element * - * @param guiElement element to resize * @param isParentLayout if the parent is a layout widget * @return true if element is fully resized */ - boolean resize(IGuiElement guiElement, boolean isParentLayout); + boolean resize(boolean isParentLayout); /** - * Called if {@link #resize(IGuiElement, boolean)} returned false after children have been resized. + * Called if {@link #resize(boolean)} returned false after children have been resized. * - * @param guiElement element to resize * @return if element is fully resized */ - boolean postResize(IGuiElement guiElement); + boolean postResize(); /** * Called after all elements in the tree are resized and the absolute positions needs to be calculated from the * relative position. - * - * @param guiElement element that was resized - */ - default void applyPos(IGuiElement guiElement) {} - - /** - * @return area of the element - */ - // TODO doesnt fit with the other api methods in this interface - Area getArea(); - - /** - * @return true if the relative x position is calculated - */ - boolean isXCalculated(); - - /** - * @return true if the relative y position is calculated */ - boolean isYCalculated(); - - /** - * @return true if the width is calculated - */ - boolean isWidthCalculated(); - - /** - * @return true if the height is calculated - */ - boolean isHeightCalculated(); - - boolean areChildrenCalculated(); - - boolean isLayoutDone(); - - default boolean isSizeCalculated(GuiAxis axis) { - return axis.isHorizontal() ? isWidthCalculated() : isHeightCalculated(); - } - - default boolean isPosCalculated(GuiAxis axis) { - return axis.isHorizontal() ? isXCalculated() : isYCalculated(); - } + default void preApplyPos() {} /** - * @return true if the relative position and size are fully calculated + * This converts the relative pos to resizer parent to relative pos to widget parent. */ - default boolean isSelfFullyCalculated(boolean isParentLayout) { - return isSelfFullyCalculated() && !canRelayout(isParentLayout); - } - - default boolean isSelfFullyCalculated() { - return isXCalculated() && isYCalculated() && isWidthCalculated() && isHeightCalculated(); - } - - default boolean isFullyCalculated() { - return isSelfFullyCalculated() && areChildrenCalculated() && isLayoutDone(); - } - - default boolean isFullyCalculated(boolean isParentLayout) { - return isSelfFullyCalculated(isParentLayout) && areChildrenCalculated() && isLayoutDone(); - } - - boolean canRelayout(boolean isParentLayout); + default void applyPos() {} void setChildrenResized(boolean resized); @@ -182,14 +122,4 @@ default void setMarginPaddingApplied(GuiAxis axis, boolean b) { setYMarginPaddingApplied(b); } } - - /** - * @return true if margin and padding are applied on the x-axis - */ - boolean isXMarginPaddingApplied(); - - /** - * @return true if margin and padding are applied on the y-axis - */ - boolean isYMarginPaddingApplied(); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDelegatingWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDelegatingWidget.java new file mode 100644 index 00000000000..2e7713861c2 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDelegatingWidget.java @@ -0,0 +1,18 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import net.minecraft.world.inventory.Slot; + +public interface IDelegatingWidget extends IWidget, IVanillaSlot { + + IWidget getDelegate(); + + @Override + default Slot getVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot ? vanillaSlot.getVanillaSlot() : null; + } + + @Override + default boolean handleAsVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java index 5f323b224f0..aea715db3fe 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java @@ -88,14 +88,14 @@ static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea if (dragArea.left) { int s = startArea.width - dx * keepPosFactor; if (s >= resizeable.getMinDragWidth()) { - widget.flex().left(startArea.rx + dx); - widget.flex().width(s); + widget.resizer().left(startArea.rx + dx); + widget.resizer().width(s); } } else if (dragArea.right) { int s = startArea.width + dx * keepPosFactor; if (s >= resizeable.getMinDragWidth()) { - widget.flex().left(startArea.rx - dx * (keepPosFactor - 1)); - widget.flex().width(s); + widget.resizer().left(startArea.rx - dx * (keepPosFactor - 1)); + widget.resizer().width(s); } } } @@ -103,14 +103,14 @@ static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea if (dragArea.top) { int s = startArea.height - dy * keepPosFactor; if (s >= resizeable.getMinDragHeight()) { - widget.flex().top(startArea.ry + dy); - widget.flex().height(s); + widget.resizer().top(startArea.ry + dy); + widget.resizer().height(s); } } else if (dragArea.bottom) { int s = startArea.height + dy * keepPosFactor; if (s >= resizeable.getMinDragHeight()) { - widget.flex().top(startArea.ry - dy * (keepPosFactor - 1)); - widget.flex().height(s); + widget.resizer().top(startArea.ry - dy * (keepPosFactor - 1)); + widget.resizer().height(s); } } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java index 42df66f763e..721bcb0bd9f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java @@ -51,7 +51,7 @@ public interface IDraggable extends IViewport { * @param widget current top most widget below the mouse * @return if the location is valid */ - default boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + default boolean canDropHere(int x, int y, @Nullable IWidget widget) { return true; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiElement.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiElement.java index e8aaa5b0bf4..9a44adc3501 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiElement.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiElement.java @@ -1,13 +1,16 @@ package com.gregtechceu.gtceu.api.mui.base.widget; -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.api.mui.widget.sizer.ResizeNode; import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; -import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; + +import org.jetbrains.annotations.ApiStatus; /** * Base interface for gui elements. For example widgets. */ +@ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") +@Deprecated public interface IGuiElement { /** @@ -25,7 +28,7 @@ public interface IGuiElement { */ boolean hasParent(); - IResizeable resizer(); + ResizeNode resizer(); /** * @return the area this element occupies @@ -41,13 +44,6 @@ default Area getParentArea() { return getParent().getArea(); } - /** - * Draws this element - * - * @param context gui context - */ - void draw(ModularGuiContext context); - /** * Called when the mouse hovers this element. This means this element is directly below the mouse or there are * widgets in between which all allow to pass hover through. This is not called when the element is at any point diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java index 05c0e871397..d15d36627bf 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java @@ -1,9 +1,9 @@ package com.gregtechceu.gtceu.api.mui.base.widget; import com.gregtechceu.gtceu.api.mui.utils.Alignment; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Flex; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Unit; +import com.gregtechceu.gtceu.api.mui.widget.sizer.*; + +import org.jetbrains.annotations.ApiStatus; import java.util.function.Consumer; import java.util.function.DoubleSupplier; @@ -16,7 +16,7 @@ @SuppressWarnings({ "unused", "UnusedReturnValue" }) public interface IPositioned> { - Flex flex(); + StandardResizer resizer(); Area getArea(); @@ -30,12 +30,12 @@ default W getThis() { } default W coverChildrenWidth() { - flex().coverChildrenWidth(); + resizer().coverChildrenWidth(); return getThis(); } default W coverChildrenHeight() { - flex().coverChildrenHeight(); + resizer().coverChildrenHeight(); return getThis(); } @@ -44,281 +44,287 @@ default W coverChildren() { } default W expanded() { - flex().expanded(); + resizer().expanded(); return getThis(); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") default W relative(IGuiElement guiElement) { return relative(guiElement.getArea()); } - default W relative(Area guiElement) { - flex().relative(guiElement); - return getThis(); + @Deprecated + default W relative(Area area) { + return relative(new AreaResizer(area)); } - default W relativeToScreen() { - flex().relativeToScreen(); + default W relative(ResizeNode resizeNode) { + resizer().relative(resizeNode); return getThis(); } - default W relativeToParent() { - flex().relativeToParent(); + default W relative(IWidget widget) { + return relative(widget.resizer()); + } + + default W relativeToScreen() { + resizer().relativeToScreen(); return getThis(); } - default W bypassLayerRestriction() { - flex().bypassLayerRestriction(); + default W relativeToParent() { + resizer().relativeToParent(); return getThis(); } default W left(int val) { - flex().left(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().left(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W leftRel(float val) { - flex().left(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().left(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W leftRelOffset(float val, int offset) { - flex().left(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().left(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W leftRelAnchor(float val, float anchor) { - flex().left(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W leftRel(float val, int offset, float anchor) { - flex().left(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W left(float val, int offset, float anchor, Unit.Measure measure) { - flex().left(val, offset, anchor, measure, false); + resizer().left(val, offset, anchor, measure, false); return getThis(); } default W left(DoubleSupplier val, Unit.Measure measure) { - flex().left(val, 0, 0, measure, true); + resizer().left(val, 0, 0, measure, true); return getThis(); } default W leftRelOffset(DoubleSupplier val, int offset) { - flex().left(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().left(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W leftRelAnchor(DoubleSupplier val, float anchor) { - flex().left(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W leftRel(DoubleSupplier val, int offset, float anchor) { - flex().left(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W right(int val) { - flex().right(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().right(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W rightRel(float val) { - flex().right(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().right(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W rightRelOffset(float val, int offset) { - flex().right(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().right(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W rightRelAnchor(float val, float anchor) { - flex().right(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W rightRel(float val, int offset, float anchor) { - flex().right(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W right(float val, int offset, float anchor, Unit.Measure measure) { - flex().right(val, offset, anchor, measure, false); + resizer().right(val, offset, anchor, measure, false); return getThis(); } default W right(DoubleSupplier val, Unit.Measure measure) { - flex().right(val, 0, 0, measure, true); + resizer().right(val, 0, 0, measure, true); return getThis(); } default W rightRelOffset(DoubleSupplier val, int offset) { - flex().right(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().right(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W rightRelAnchor(DoubleSupplier val, float anchor) { - flex().right(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W rightRel(DoubleSupplier val, int offset, float anchor) { - flex().right(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W top(int val) { - flex().top(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().top(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W topRel(float val) { - flex().top(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().top(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W topRelOffset(float val, int offset) { - flex().top(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().top(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W topRelAnchor(float val, float anchor) { - flex().top(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W topRel(float val, int offset, float anchor) { - flex().top(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W top(float val, int offset, float anchor, Unit.Measure measure) { - flex().top(val, offset, anchor, measure, false); + resizer().top(val, offset, anchor, measure, false); return getThis(); } default W top(DoubleSupplier val, Unit.Measure measure) { - flex().top(val, 0, 0, measure, true); + resizer().top(val, 0, 0, measure, true); return getThis(); } default W topRelOffset(DoubleSupplier val, int offset) { - flex().top(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().top(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W topRelAnchor(DoubleSupplier val, float anchor) { - flex().top(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W topRel(DoubleSupplier val, int offset, float anchor) { - flex().top(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottom(int val) { - flex().bottom(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().bottom(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W bottomRel(float val) { - flex().bottom(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().bottom(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W bottomRelOffset(float val, int offset) { - flex().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W bottomRelAnchor(float val, float anchor) { - flex().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottomRel(float val, int offset, float anchor) { - flex().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottom(float val, int offset, float anchor, Unit.Measure measure) { - flex().bottom(val, offset, anchor, measure, false); + resizer().bottom(val, offset, anchor, measure, false); return getThis(); } default W bottom(DoubleSupplier val, Unit.Measure measure) { - flex().bottom(val, 0, 0, measure, true); + resizer().bottom(val, 0, 0, measure, true); return getThis(); } default W bottomRelOffset(DoubleSupplier val, int offset) { - flex().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W bottomRelAnchor(DoubleSupplier val, float anchor) { - flex().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottomRel(DoubleSupplier val, int offset, float anchor) { - flex().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W width(int val) { - flex().width(val, 0, Unit.Measure.PIXEL); + resizer().width(val, 0, Unit.Measure.PIXEL); return getThis(); } default W widthRel(float val) { - flex().width(val, 0, Unit.Measure.RELATIVE); + resizer().width(val, 0, Unit.Measure.RELATIVE); return getThis(); } default W widthRelOffset(float val, int offset) { - flex().width(val, offset, Unit.Measure.RELATIVE); + resizer().width(val, offset, Unit.Measure.RELATIVE); return getThis(); } default W width(float val, Unit.Measure measure) { - flex().width(val, 0, measure); + resizer().width(val, 0, measure); return getThis(); } default W width(DoubleSupplier val, Unit.Measure measure) { - flex().width(val, 0, measure); + resizer().width(val, 0, measure); return getThis(); } default W height(int val) { - flex().height(val, 0, Unit.Measure.PIXEL); + resizer().height(val, 0, Unit.Measure.PIXEL); return getThis(); } default W heightRel(float val) { - flex().height(val, 0, Unit.Measure.RELATIVE); + resizer().height(val, 0, Unit.Measure.RELATIVE); return getThis(); } default W height(float val, Unit.Measure measure) { - flex().height(val, 0, measure); + resizer().height(val, 0, measure); return getThis(); } default W heightRelOffset(DoubleSupplier val, int offset) { - flex().height(val, offset, Unit.Measure.RELATIVE); + resizer().height(val, offset, Unit.Measure.RELATIVE); return getThis(); } default W height(DoubleSupplier val, Unit.Measure measure) { - flex().height(val, 0, measure); + resizer().height(val, 0, measure); return getThis(); } @@ -363,27 +369,27 @@ default W full() { } default W anchorLeft(float val) { - flex().anchorLeft(val); + resizer().anchorLeft(val); return getThis(); } default W anchorRight(float val) { - flex().anchorRight(val); + resizer().anchorRight(val); return getThis(); } default W anchorTop(float val) { - flex().anchorTop(val); + resizer().anchorTop(val); return getThis(); } default W anchorBottom(float val) { - flex().anchorBottom(val); + resizer().anchorBottom(val); return getThis(); } default W anchor(Alignment alignment) { - flex().anchor(alignment); + resizer().anchor(alignment); return getThis(); } @@ -421,8 +427,8 @@ default W center() { return align(Alignment.Center); } - default W flex(Consumer flexConsumer) { - flexConsumer.accept(flex()); + default W resizer(Consumer resizerConsumer) { + resizerConsumer.accept(resizer()); return getThis(); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java index a9af3f54904..9ed5dd4da39 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java @@ -1,14 +1,14 @@ package com.gregtechceu.gtceu.api.mui.base.widget; import com.gregtechceu.gtceu.api.mui.base.ITheme; -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; +import com.gregtechceu.gtceu.api.mui.base.ITreeNode; import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; -import com.gregtechceu.gtceu.api.mui.utils.Point; import com.gregtechceu.gtceu.api.mui.utils.Stencil; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Flex; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; import com.gregtechceu.gtceu.utils.FormattingUtil; @@ -22,9 +22,9 @@ import java.util.function.Consumer; /** - * A widget in a GUI + * A widget in a GUI. */ -public interface IWidget extends IGuiElement { +public interface IWidget extends IGuiElement, ITreeNode { String WIDGET_TRANSLATION_KEY_FORMAT = "widget.%s.name"; /** @@ -36,92 +36,39 @@ public interface IWidget extends IGuiElement { .or(CharMatcher.anyOf("-_.")) .negate(); - /** - * Validates and initialises this element. - * This element now becomes valid - * - * @param parent the parent this element belongs to - * @param late true if this is called some time after the widget tree of the parent has been initialised - */ - void initialise(@NotNull IWidget parent, boolean late); - - /** - * Invalidates this element. - */ - void dispose(); + default String getTranslationId() { + String className = FormattingUtil.toLowerCaseUnderscore(this.getClass().getSimpleName()); + className = DISALLOWED_TRANSLATION_KEY_CHARS.removeFrom(className); + return WIDGET_TRANSLATION_KEY_FORMAT.formatted(className); + } /** - * Determines if this element exist in an active gui. - * - * @return if this is in a valid gui + * @return the screen this element is in */ - boolean isValid(); + ModularScreen getScreen(); /** - * Draws the background of this widget. - * - * @param context gui context - * @param widgetTheme widget theme of this widget + * @return the parent of this widget */ - void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme); + @NotNull + @Override + IWidget getParent(); - /** - * Draws additional stuff in this widget. - * x = 0 and y = 0 is now in the top left corner of this widget. - * Do NOT override this method, it is never called. Use {@link #draw(ModularGuiContext, WidgetThemeEntry)} instead. - * - * @param context gui context - */ - @ApiStatus.NonExtendable - @Deprecated @Override - default void draw(ModularGuiContext context) { - draw(context, getWidgetTheme(context.getTheme())); + default boolean hasParent() { + return isValid(); } /** - * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetThemeEntry)} - * and - * before - * {@link #drawOverlay(ModularGuiContext, WidgetThemeEntry)} - * - * @param context gui context - * @param widgetTheme widget theme + * @return the context the current screen */ - void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme); - - /** - * Draws the overlay of this widget. - * - * @param context gui context - * @param widgetTheme widget theme - */ - void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme); - - /** - * Draws foreground elements of this widget. For example tooltips. - * No transformations are applied here. - * - * @param context gui context - */ - void drawForeground(ModularGuiContext context); - - default void transform(IViewportStack stack) { - stack.translate(getArea().rx, getArea().ry, 0); - } - - default Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { - return null; - } - - default WidgetThemeEntry getWidgetTheme(ITheme theme) { - return theme.getFallback(); - } + ModularGuiContext getContext(); /** - * Called 20 times per second. + * @return the panel this widget is in */ - void onUpdate(); + @NotNull + ModularPanel getPanel(); /** * @return the area this widget occupies @@ -129,10 +76,13 @@ default WidgetThemeEntry getWidgetTheme(ITheme theme) { @Override Area getArea(); - default String getTranslationId() { - String className = FormattingUtil.toLowerCaseUnderscore(this.getClass().getSimpleName()); - className = DISALLOWED_TRANSLATION_KEY_CHARS.removeFrom(className); - return WIDGET_TRANSLATION_KEY_FORMAT.formatted(className); + /** + * Shortcut to get the area of the parent + * + * @return parent area + */ + default Area getParentArea() { + return getParent().getArea(); } /** @@ -169,62 +119,50 @@ default boolean isInside(IViewportStack stack, int mx, int my, boolean absolute) } /** - * Calculates if a given pos is inside this widgets area. - * This should be used over {@link Area#isInside(int, int)}, since this accounts for transformations. - * - * @param stack viewport stack - * @param point position - * @return if pos is inside this widgets area + * Called when the mouse hovers this element. This means this element is directly below the mouse or there are + * widgets in between which + * all allow to pass hover through. This is not called when the element is at any point below the mouse. */ - default boolean isInside(IViewportStack stack, Point point) { - return isInside(stack, point.x, point.y); - } + default void onMouseStartHover() {} /** - * @return all children of this widget + * Called when the mouse no longer hovers this element. This widget can still be below the mouse on some level. */ - @NotNull - default List getChildren() { - return Collections.emptyList(); - } + default void onMouseEndHover() {} /** - * @return if this widget has any children + * Called when the mouse enters this elements area with any amount of widgets above it from the current panel. */ - default boolean hasChildren() { - return !getChildren().isEmpty(); - } + default void onMouseEnterArea() {} /** - * @return the panel this widget is in + * Called when the mouse leaves the area, or it started hovering a different panel. */ - @NotNull - ModularPanel getPanel(); + default void onMouseLeaveArea() {} /** - * Returns if this element is enabled. Disabled elements are not drawn and can not be interacted with. If this is - * disabled, the children - * will be considered disabled to without actually being disabled. - * - * @return if this element is enabled + * @return if this widget is currently right below the mouse */ - @Override - boolean isEnabled(); - - void setEnabled(boolean enabled); + default boolean isHovering() { + return isHoveringFor(0); + } /** - * Checks if all ancestors are enabled. Only then this widget is visible and interactable. + * Returns if this element is right blow the mouse for a certain amount of time * - * @return if all ancestors are enabled. + * @param ticks time in ticks + * @return if this element is right blow the mouse for a certain amount of time */ - default boolean areAncestorsEnabled() { - IWidget parent = this; - do { - if (!parent.isEnabled()) return false; - parent = parent.getParent(); - } while (parent.hasParent()); - return true; + default boolean isHoveringFor(int ticks) { + return false; + } + + default boolean isBelowMouse() { + return isBelowMouseFor(0); + } + + default boolean isBelowMouseFor(int ticks) { + return false; } /** @@ -267,40 +205,142 @@ default boolean canHoverThrough() { } /** - * Marks tooltip for this widget as dirty. + * @return default width if it can't be calculated */ - void markTooltipDirty(); + default int getDefaultWidth() { + return 18; + } /** - * @return the parent of this widget + * @return default height if it can't be calculated + */ + default int getDefaultHeight() { + return 18; + } + + /** + * Validates and initialises this element. + * This element now becomes valid + * + * @param parent the parent this element belongs to + * @param late true if this is called some time after the widget tree of the parent has been initialised + */ + void initialise(@NotNull IWidget parent, boolean late); + + /** + * Invalidates this element. + */ + void dispose(); + + /** + * Determines if this element exist in an active gui. + * + * @return if this is in a valid gui + */ + boolean isValid(); + + /** + * Called 20 times per second. + */ + default void onUpdate() {} + + /** + * Draws the background of this widget. + * + * @param context gui context + * @param widgetTheme widget theme of this widget + */ + default void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetThemeEntry)} + * and before + * {@link #drawOverlay(ModularGuiContext, WidgetThemeEntry)} + * + * @param context gui context + * @param widgetTheme widget theme + */ + default void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws the overlay of this theme. + * + * @param context gui context + * @param widgetTheme widget theme + */ + default void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws foreground elements of this widget. For example tooltips. + * No transformations are applied here. + * + * @param context gui context + */ + default void drawForeground(ModularGuiContext context) {} + + default void transform(IViewportStack stack) { + stack.translate(getArea().rx, getArea().ry, 0); + } + + default Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { + return null; + } + + default WidgetThemeEntry getWidgetTheme(ITheme theme) { + return theme.getFallback(); + } + + /** + * @return all children of this widget */ @NotNull - IWidget getParent(); + @Override + default List getChildren() { + return Collections.emptyList(); + } + /** + * @return if this widget has any children + */ @Override - default boolean hasParent() { - return isValid(); + default boolean hasChildren() { + return !getChildren().isEmpty(); } + void scheduleResize(); + + boolean requiresResize(); + /** - * @return the context of the current screen + * @return flex of this widget */ - ModularGuiContext getContext(); + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + @Nullable + default StandardResizer getFlex() { + return resizer(); + } /** * @return flex of this widget. Creates a new one if it doesn't already have one. */ - Flex flex(); + @NotNull + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + default StandardResizer flex() { + return resizer(); + } /** - * Does the same as {@link IPositioned#flex(Consumer)} + * Does the same as {@link IPositioned#resizer(Consumer)} * * @param builder function to build flex * @return this */ - default IWidget flexBuilder(Consumer builder) { - builder.accept(flex()); - return this; + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + default IWidget flexBuilder(Consumer builder) { + return resizerBuilder(builder); } /** @@ -308,14 +348,12 @@ default IWidget flexBuilder(Consumer builder) { */ @NotNull @Override - IResizeable resizer(); + StandardResizer resizer(); - /** - * Sets the resizer of this widget. - * - * @param resizer resizer - */ - void resizer(IResizeable resizer); + default IWidget resizerBuilder(Consumer builder) { + builder.accept(resizer()); + return this; + } /** * Called before a widget is resized. @@ -333,13 +371,29 @@ default void onResized() {} default void postResize() {} /** - * @return flex of this widget + * Returns if this element is enabled. Disabled elements are not drawn and can not be interacted with. If this is + * disabled, the children + * will be considered disabled to without actually being disabled. + * + * @return if this element is enabled */ - Flex getFlex(); + @Override + boolean isEnabled(); + + void setEnabled(boolean enabled); - default boolean isExpanded() { - Flex flex = getFlex(); - return flex != null && flex.isExpanded(); + /** + * Checks if all ancestors are enabled. Only then this widget is visible and interactable. + * + * @return if all ancestors are enabled. + */ + default boolean areAncestorsEnabled() { + IWidget parent = this; + do { + if (!parent.isEnabled()) return false; + parent = parent.getParent(); + } while (parent.hasParent()); + return true; } @Nullable diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java index 410a9d7588b..63dad61c32f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java @@ -4,6 +4,7 @@ import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.utils.ObjectList; import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; import net.minecraft.network.chat.Component; @@ -172,7 +173,7 @@ private static IKey parseText(JsonObject json) throws JsonParseException { } return JsonHelper.getBoolean(json, false, "lang", "translate") ? IKey.lang(s) : IKey.str(s); } else if (element.isJsonArray()) { - ObjectArrayList strings = new ObjectArrayList<>(); + ObjectList strings = ObjectList.create(); for (JsonElement element1 : element.getAsJsonArray()) { strings.add(parseText(element1)); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java index cd91d64ada9..e929ae98619 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java @@ -43,7 +43,6 @@ public void redraw() { } private void redraw(GraphView view) { - long time = System.nanoTime(); float dHalf = thickness * 0.5f; int n = xs.length * 4; @@ -145,9 +144,6 @@ private void redraw(GraphView view) { lpoy = py * dHalf; storePoints(vertexIndex, view, x1, y1, lpox, lpoy); - time = System.nanoTime() - time; - // GTCEu.LOGGER.error("Calculating vertices from {} data points took {}s", xs.length, - // FormattingUtil.formatNumberReadable(time)); } private int storePoints(int index, GraphView view, float sx, float sy, float ox, float oy) { @@ -177,12 +173,8 @@ public void draw(GuiGraphics graphics, GraphView view) { RenderSystem.setShader(GameRenderer::getPositionColorShader); var pose = graphics.pose().last().pose(); var buffer = graphics.bufferSource().getBuffer(GTRenderTypes.guiTriangleStrip()); - long time = System.nanoTime(); for (int i = 0; i < this.vertexBuffer.length; i += 2) { buffer.vertex(pose, this.vertexBuffer[i], this.vertexBuffer[i + 1], 0).color(r, g, b, a).endVertex(); - time = System.nanoTime() - time; - // GTCEu.LOGGER.error("Drawing plot with {} points took {}s", xs.length, - // FormattingUtil.formatNumberReadable(time)); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/DebugOverlay.java b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/DebugOverlay.java new file mode 100644 index 00000000000..1db715f8ffe --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/DebugOverlay.java @@ -0,0 +1,143 @@ +package com.gregtechceu.gtceu.api.mui.overlay; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.IMuiScreen; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.drawable.NamedDrawableRow; +import com.gregtechceu.gtceu.api.mui.drawable.Rectangle; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.utils.TreeUtil; +import com.gregtechceu.gtceu.api.mui.value.BoolValue; +import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; +import com.gregtechceu.gtceu.api.mui.widgets.ButtonWidget; +import com.gregtechceu.gtceu.api.mui.widgets.ListWidget; +import com.gregtechceu.gtceu.api.mui.widgets.ToggleButton; +import com.gregtechceu.gtceu.api.mui.widgets.menu.ContextMenuButton; +import com.gregtechceu.gtceu.api.mui.widgets.menu.Menu; +import com.gregtechceu.gtceu.client.mui.screen.CustomModularScreen; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; +import com.gregtechceu.gtceu.config.ConfigHolder; + +import org.jetbrains.annotations.NotNull; + +public class DebugOverlay extends CustomModularScreen { + + private static final IIcon CHECKMARK = GTGuiTextures.CHECK_BOX.asIcon().size(8); + + private final IMuiScreen parent; + + public DebugOverlay(IMuiScreen screen) { + super(GTCEu.MOD_ID); + this.parent = screen; + } + + @Override + public @NotNull ModularPanel buildUI(ModularGuiContext context) { + return new ModularPanel("debug") + .fullScreenInvisible() + .child(new ContextMenuButton<>("menu_debug_options") + .horizontalCenter() + .bottom(0) + .height(12) + .width(160) + .background( + new Rectangle() + .color(Color.withAlpha( + Long.decode(ConfigHolder.INSTANCE.dev.mui.outlineColor).intValue(), + 0.4f))) + .disableHoverBackground() + .overlay(IKey.str("Debug Options")) + .openUp() + .menuList(l1 -> l1 + .name("menu_list") + .maxSize(100) + .widthRel(1f) + .child(new ButtonWidget<>().name("print_widget_tree_button") + .height(12) + .widthRel(1f) + .invisible() + .overlay(IKey.str("Print widget trees")) + .onMousePressed((x, y, b) -> this.logWidgetTrees(b))) + .child(new ButtonWidget<>().name("print_resizer_tree_button") + .height(12) + .widthRel(1f) + .invisible() + .overlay(IKey.str("Print resizer tree")) + .onMousePressed((x, y, b) -> { + TreeUtil.print(parent.getScreen().getResizeNode()); + return true; + })) + .child(new ContextMenuButton<>("menu_hover_info") + .height(10) + .widthRel(1f) + .overlay(IKey.str("Widget hover info")) + .openRightUp() + .menu(new Menu<>() + .width(100) + .coverChildrenHeight() + .padding(2) + .child(new ListWidget<>() + .maxSize(100) + .widthRel(1f) + .child(toggleOption(0, "Any", + ConfigHolder.INSTANCE.dev.mui.showHovered)) + .child(toggleOption(1, "Pos", + ConfigHolder.INSTANCE.dev.mui.showPos)) + .child(toggleOption(2, "Size", + ConfigHolder.INSTANCE.dev.mui.showSize)) + .child(toggleOption(3, "Widget Theme", + ConfigHolder.INSTANCE.dev.mui.showWidgetTheme)) + .child(toggleOption(4, "Extra info", + ConfigHolder.INSTANCE.dev.mui.showExtra)) + .child(toggleOption(5, "Outline", + ConfigHolder.INSTANCE.dev.mui.showOutline))))) + .child(new ContextMenuButton<>("menu_parent_hover_info") + .name("menu_button_parent_hover_info") + .height(10) + .widthRel(1f) + .overlay(IKey.str("Parent widget hover info")) + .openRightUp() + .menu(new Menu<>() + .width(100) + .coverChildrenHeight() + .padding(2) + .child(new ListWidget<>() + .maxSize(100) + .widthRel(1f) + .child(toggleOption(10, "Any", + ConfigHolder.INSTANCE.dev.mui.showParent)) + .child(toggleOption(11, "Pos", + ConfigHolder.INSTANCE.dev.mui.showParentPos)) + .child(toggleOption(12, "Size", + ConfigHolder.INSTANCE.dev.mui.showParentSize)) + .child(toggleOption(13, "Widget Theme", + ConfigHolder.INSTANCE.dev.mui.showParentWidgetTheme)) + .child(toggleOption(14, "Outline", + ConfigHolder.INSTANCE.dev.mui.showParentOutline))))))); + } + + public static IWidget toggleOption(int i, String name, boolean boolValue) { + return new ToggleButton() + .name("hover_info_toggle" + i) + .invisible() + .widthRel(1f) + .height(12) + .value(new BoolValue(boolValue)) + .overlay(true, new NamedDrawableRow() + .name(IKey.str(name)) + .drawable(CHECKMARK)) + .overlay(false, new NamedDrawableRow() + .name(IKey.str(name))); + } + + private boolean logWidgetTrees(int b) { + for (ModularPanel panel : parent.getScreen().getPanelManager().getOpenPanels()) { + WidgetTree.print(panel); + } + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayHandler.java index 18f5f44b2f3..5dc8a02b908 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayHandler.java @@ -10,7 +10,8 @@ import java.util.function.Function; import java.util.function.Predicate; -@ApiStatus.Experimental +@Deprecated +@ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public class OverlayHandler implements Comparable { private final Predicate test; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayManager.java b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayManager.java index 96bcd06050f..5ee7d296c0e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayManager.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayManager.java @@ -1,22 +1,17 @@ package com.gregtechceu.gtceu.api.mui.overlay; import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; -import net.minecraft.client.gui.screens.Screen; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.ScreenEvent; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import org.jetbrains.annotations.ApiStatus; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -@ApiStatus.Experimental +@Deprecated +@ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") @Mod.EventBusSubscriber(modid = GTCEu.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) public class OverlayManager { @@ -28,22 +23,4 @@ public static void register(OverlayHandler handler) { overlays.sort(OverlayHandler::compareTo); } } - - public static void onOpenScreen(Screen newScreen) { - // if (newScreen == event.getCurrentScreen()) return; - OverlayStack.closeAll(); - for (OverlayHandler handler : overlays) { - if (handler.isValidFor(newScreen)) { - ModularScreen overlay = Objects.requireNonNull(handler.createOverlay(newScreen), - "Overlays must not be null!"); - overlay.constructOverlay(newScreen); - OverlayStack.open(overlay); - } - } - } - - @SubscribeEvent(priority = EventPriority.LOWEST) - public static void onCloseScreen(ScreenEvent.Closing event) { - OverlayStack.closeAll(); - } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayStack.java b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayStack.java index 3dd2ba3cfae..819cb07fbc5 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayStack.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/overlay/OverlayStack.java @@ -1,10 +1,15 @@ package com.gregtechceu.gtceu.api.mui.overlay; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; +import com.gregtechceu.gtceu.api.mui.base.IMuiScreen; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.client.mui.screen.ClientScreenHandler; import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.client.mui.screen.OpenScreenEvent; +import com.gregtechceu.gtceu.config.ConfigHolder; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.common.MinecraftForge; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.systems.RenderSystem; @@ -13,10 +18,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; -@ApiStatus.Experimental +@ApiStatus.Internal public class OverlayStack { private static final List overlay = new ArrayList<>(); @@ -101,10 +107,10 @@ public static void onTick() { } @Nullable - public static IGuiElement getHoveredElement() { + public static IWidget getHoveredElement() { for (int i = overlay.size() - 1; i >= 0; i--) { ModularScreen screen = overlay.get(i); - IGuiElement hovered = screen.getContext().getTopHovered(); + IWidget hovered = screen.getContext().getTopHovered(); if (hovered == null) continue; return hovered; } @@ -114,4 +120,31 @@ public static IGuiElement getHoveredElement() { public static boolean isHoveringOverlay() { return getHoveredElement() != null; } + + public static void onOpenScreen(Screen newScreen) { + closeAll(); + if (newScreen != null) { + // backwards compat + for (OverlayHandler handler : OverlayManager.overlays) { + if (handler.isValidFor(newScreen)) { + ModularScreen overlay = Objects.requireNonNull(handler.createOverlay(newScreen), + "Overlays must not be null!"); + overlay.constructOverlay(newScreen); + OverlayStack.open(overlay); + } + } + + OpenScreenEvent event = new OpenScreenEvent(newScreen); + MinecraftForge.EVENT_BUS.post(event); + for (ModularScreen overlay : event.getOverlays()) { + overlay.constructOverlay(newScreen); + open(overlay); + } + if (ConfigHolder.INSTANCE.dev.debugUI && newScreen instanceof IMuiScreen muiScreen) { + ModularScreen overlay = new DebugOverlay(muiScreen); + overlay.constructOverlay(newScreen); + open(overlay); + } + } + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/theme/ThemeAPI.java b/src/main/java/com/gregtechceu/gtceu/api/mui/theme/ThemeAPI.java index f58ccfd144e..926bcbc9e7b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/theme/ThemeAPI.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/theme/ThemeAPI.java @@ -61,19 +61,32 @@ public List getJavaDefaultThemes(String id) { } @Override - public ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme) { - String theme = getThemeIdForScreen(owner, name); + public ITheme getThemeForScreen(String owner, String name, @Nullable String panel, @Nullable String defaultTheme, + @Nullable String fallbackTheme) { + String theme = getThemeIdForScreen(owner, name, panel); if (theme != null) return getTheme(theme); if (defaultTheme != null) return getTheme(defaultTheme); + if (fallbackTheme != null) return getTheme(fallbackTheme); return getTheme(ConfigHolder.INSTANCE.client.ui.useDarkThemeByDefault ? "vanilla_dark" : "vanilla"); } - private String getThemeIdForScreen(String mod, String name) { + private String getThemeIdForScreen(String mod, String name, String panelName) { String fullName = mod + ":" + name; - String theme = this.jsonScreenThemes.get(fullName); + String fullPanelName = null; + if (panelName != null) fullPanelName = fullName + ":" + panelName; + String theme = null; + if (fullPanelName != null) { + theme = this.jsonScreenThemes.get(fullPanelName); + if (theme != null) return theme; + } + theme = this.jsonScreenThemes.get(fullName); if (theme != null) return theme; theme = this.jsonScreenThemes.get(mod); if (theme != null) return theme; + if (fullPanelName != null) { + theme = this.screenThemes.get(fullPanelName); + if (theme != null) return theme; + } theme = this.screenThemes.get(fullName); return theme != null ? theme : this.screenThemes.get(mod); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/HoveredWidgetList.java b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/HoveredWidgetList.java index 32e72983bbd..530f29dd72a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/HoveredWidgetList.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/HoveredWidgetList.java @@ -4,7 +4,6 @@ import com.gregtechceu.gtceu.client.mui.screen.viewport.LocatedWidget; import com.gregtechceu.gtceu.client.mui.screen.viewport.TransformationMatrix; -import it.unimi.dsi.fastutil.objects.ObjectList; import org.jetbrains.annotations.Nullable; public class HoveredWidgetList { diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/MutableSingletonList.java b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/MutableSingletonList.java new file mode 100644 index 00000000000..b707fa9784a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/MutableSingletonList.java @@ -0,0 +1,280 @@ +package com.gregtechceu.gtceu.api.mui.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class MutableSingletonList implements List { + + private boolean hasValue; + private T value; + + public MutableSingletonList() { + remove(); + } + + public MutableSingletonList(T value) { + set(value); + } + + public T get() { + if (!this.hasValue) throw new IndexOutOfBoundsException("List is empty but tried to access index 0!"); + return value; + } + + public T getOrNull() { + return hasValue ? value : null; + } + + public void set(T t) { + this.value = t; + this.hasValue = true; + } + + public void remove() { + this.value = null; + this.hasValue = false; + } + + @Override + public int size() { + return hasValue ? 1 : 0; + } + + @Override + public boolean isEmpty() { + return !hasValue; + } + + public boolean hasValue() { + return hasValue; + } + + @Override + public boolean contains(Object o) { + return this.hasValue && Objects.equals(this.value, o); + } + + @Override + public @NotNull Iterator iterator() { + return new Iterator<>() { + + private byte cursor = 0; + + @Override + public boolean hasNext() { + return MutableSingletonList.this.hasValue && this.cursor == 0; + } + + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + this.cursor++; + return MutableSingletonList.this.value; + } + + @Override + public void remove() { + if (this.cursor < 1) throw new IllegalStateException(); + MutableSingletonList.this.remove(); + this.cursor--; + } + }; + } + + @Override + public @NotNull Object @NotNull [] toArray() { + if (!this.hasValue) return new Object[0]; + Object[] o = new Object[1]; + o[0] = this.value; + return o; + } + + @Override + public @NotNull T1 @NotNull [] toArray(@NotNull T1 @NotNull [] a) { + if (!this.hasValue) return a; + if (a.length == 0) a = Arrays.copyOf(a, 1); + a[0] = (T1) this.value; + return a; + } + + @Override + public boolean add(T t) { + if (this.hasValue) throw new IllegalStateException( + "MutableSingletonList can only have one value, but it already has a value!"); + set(t); + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + remove(); + return true; + } + return false; + } + + @Override + public boolean containsAll(@NotNull Collection c) { + int s = c.size(); + if (s > 1 || (s == 1 != this.hasValue)) return false; + if (!this.hasValue) return true; + if (c instanceof List l) return Objects.equals(this.value, l.get(0)); + return Objects.equals(this.value, c.iterator().next()); + } + + @Override + public boolean addAll(@NotNull Collection c) { + if (this.hasValue || c.isEmpty()) return false; + if (c instanceof List l) { + add((T) l.get(0)); + } else { + add(c.iterator().next()); + } + return true; + } + + private void verifyIndex(int i, boolean checkEmpty) { + if (i != 0) throw new IndexOutOfBoundsException("MutableSingletonList only accepts index 0!"); + if (checkEmpty && !this.hasValue) + throw new IndexOutOfBoundsException("Tried to access index 0, but MutableSingletonList has no element!"); + } + + @Override + public boolean addAll(int index, @NotNull Collection c) { + verifyIndex(index, false); + return addAll(c); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + if (!this.hasValue || c.isEmpty()) return false; + if (c instanceof List l) { + return remove(l.get(0)); + } + return remove(c.iterator().next()); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + if (!this.hasValue || c.contains(this.value)) return false; + remove(); + return true; + } + + @Override + public void clear() { + remove(); + } + + @Override + public T get(int index) { + verifyIndex(index, true); + return null; + } + + @Override + public T set(int index, T element) { + verifyIndex(index, true); + T t = this.value; + this.value = element; + return t; + } + + @Override + public void add(int index, T element) { + verifyIndex(index, false); + add(element); + } + + @Override + public T remove(int index) { + verifyIndex(index, true); + T t = this.value; + remove(); + return t; + } + + @Override + public int indexOf(Object o) { + return contains(o) ? 0 : -1; + } + + @Override + public int lastIndexOf(Object o) { + return indexOf(o); + } + + @Override + public @NotNull ListIterator listIterator() { + return new ListIterator() { + + private byte cursor = 0; + + @Override + public boolean hasNext() { + return MutableSingletonList.this.hasValue && (this.cursor == 0 || this.cursor == -1); + } + + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + this.cursor = 1; + return MutableSingletonList.this.value; + } + + @Override + public boolean hasPrevious() { + return MutableSingletonList.this.hasValue && (this.cursor == 0 || this.cursor == 1); + } + + @Override + public T previous() { + if (!hasNext()) throw new NoSuchElementException(); + this.cursor = -1; + return MutableSingletonList.this.value; + } + + @Override + public int nextIndex() { + return cursor == 0 ? 0 : 1; + } + + @Override + public int previousIndex() { + return cursor == 0 ? 0 : -1; + } + + @Override + public void remove() { + if (this.cursor == 0) throw new IllegalStateException(); + MutableSingletonList.this.remove(); + this.cursor = 0; + } + + @Override + public void set(T t) { + if (this.cursor == 0) throw new IllegalStateException(); + MutableSingletonList.this.set(t); + } + + @Override + public void add(T t) { + MutableSingletonList.this.add(t); + } + }; + } + + @Override + public @NotNull ListIterator listIterator(int index) { + verifyIndex(index, false); + return listIterator(); + } + + @Override + public @NotNull List subList(int fromIndex, int toIndex) { + if (fromIndex < 0 || toIndex > 1 || toIndex < fromIndex) throw new IndexOutOfBoundsException(); + return new MutableSingletonList<>(this.value); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ObjectList.java b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ObjectList.java new file mode 100644 index 00000000000..aa739ef0511 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ObjectList.java @@ -0,0 +1,170 @@ +package com.gregtechceu.gtceu.api.mui.utils; + +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Iterator; + +public interface ObjectList extends it.unimi.dsi.fastutil.objects.ObjectList { + + static ObjectArrayList create() { + return new ObjectArrayList<>(); + } + + static ObjectArrayList create(int size) { + return new ObjectArrayList<>(size); + } + + static ObjectArrayList of(Collection c) { + return new ObjectArrayList<>(c); + } + + static ObjectArrayList of(ObjectCollection c) { + return new ObjectArrayList<>(c); + } + + static ObjectArrayList of(it.unimi.dsi.fastutil.objects.ObjectList l) { + return new ObjectArrayList<>(l); + } + + static ObjectArrayList of(V[] a) { + return new ObjectArrayList<>(a); + } + + static ObjectArrayList of(V[] a, int offset, int length) { + return new ObjectArrayList<>(a, offset, length); + } + + static ObjectArrayList of(Iterator i) { + return new ObjectArrayList<>(i); + } + + static ObjectArrayList of(ObjectIterator i) { + return new ObjectArrayList<>(i); + } + + void addFirst(V v); + + void addLast(V v); + + @NotNull + V getFirst(); + + @NotNull + V getLast(); + + @NotNull + V removeFirst(); + + @NotNull + V removeLast(); + + @Nullable + V peekFirst(); + + @Nullable + V pollFirst(); + + @Nullable + V peekLast(); + + @Nullable + V pollLast(); + + void trim(); + + @NotNull + V[] elements(); + + void ensureCapacity(int minCapacity); + + class ObjectArrayList extends it.unimi.dsi.fastutil.objects.ObjectArrayList implements ObjectList { + + public ObjectArrayList(int capacity) { + super(capacity); + } + + public ObjectArrayList() {} + + public ObjectArrayList(Collection c) { + super(c); + } + + public ObjectArrayList(ObjectCollection c) { + super(c); + } + + public ObjectArrayList(it.unimi.dsi.fastutil.objects.ObjectList l) { + super(l); + } + + public ObjectArrayList(V[] a) { + super(a); + } + + public ObjectArrayList(V[] a, int offset, int length) { + super(a, offset, length); + } + + public ObjectArrayList(Iterator i) { + super(i); + } + + public ObjectArrayList(ObjectIterator i) { + super(i); + } + + @Override + public void addFirst(V v) { + add(0, v); + } + + @Override + public void addLast(V v) { + add(v); + } + + @Override + public @NotNull V getFirst() { + return get(0); + } + + @Override + public @NotNull V getLast() { + return get(size() - 1); + } + + @Override + public @NotNull V removeFirst() { + return remove(0); + } + + @Override + public @NotNull V removeLast() { + return remove(size() - 1); + } + + @Override + public V peekFirst() { + return isEmpty() ? null : getFirst(); + } + + @Override + public V pollFirst() { + return isEmpty() ? null : removeFirst(); + } + + @Override + public V peekLast() { + return isEmpty() ? null : getLast(); + } + + @Override + public V pollLast() { + return isEmpty() ? null : removeLast(); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/TreeUtil.java b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/TreeUtil.java new file mode 100644 index 00000000000..6298c6686da --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/TreeUtil.java @@ -0,0 +1,525 @@ +package com.gregtechceu.gtceu.api.mui.utils; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.ITreeNode; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.widget.sizer.ResizeNode; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.Streams; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class TreeUtil { + + public static boolean allowUnicode = true; + + private static final String U_T = "✓"; + private static final String U_F = "✘"; + private static final String T = "T"; + private static final String F = "F"; + private static final String U_PIPE = "│"; + private static final String U_PIPE_MID = "├"; + private static final String U_PIPE_END = "└"; + private static final String PIPE = "|"; + private static final String PIPE_MID = "+"; + private static final String PIPE_END = "-"; + + public static final NodeInfo RESIZE_NODE_INFO_FULLY_RESIZED = (root, node, builder) -> builder + .append("Fully resized: ") + .append(str(node.isFullyCalculated(node.hasParent() && node.getParent().isLayout()))); + public static final NodeInfo RESIZE_NODE_INFO_RESIZED_DETAILED = (root, node, builder) -> { + builder.append("XYWH: ") + .append(str(node.isXCalculated())) + .append(str(node.isYCalculated())) + .append(str(node.isWidthCalculated())) + .append(str(node.isHeightCalculated())) + .append(", Children resized: ").append(str(node.areChildrenCalculated())) + .append(", Self layout done: ").append(str(node.isLayoutDone())) + .append(", Parent layout done: ") + .append(str(!node.canRelayout(node.hasParent() && node.getParent().isLayout()))); + /* + * if (!self) { + * builder.append(", Self detail: ("); + * RESIZE_NODE_INFO_SELF_RESIZED_DETAIL.addInfo(root, node, builder); + * builder.append(")"); + * } + */ + }; + public static final NodeInfo RESIZE_NODE_INFO_RESIZED_COLLAPSED = (root, node, builder) -> { + if (node.isFullyCalculated(node.hasParent() && node.getParent().isLayout())) { + RESIZE_NODE_INFO_FULLY_RESIZED.addInfo(root, node, builder); + } else { + RESIZE_NODE_INFO_RESIZED_DETAILED.addInfo(root, node, builder); + } + }; + + private static String str(boolean b) { + if (TreeUtil.allowUnicode) return b ? U_T : U_F; + return b ? T : F; + } + + public static > boolean foreachChildBFS(T parent, Predicate consumer) { + return foreachChildBFS(parent, consumer, false); + } + + /** + * Iterates through the whole sub widget tree by using breath-first-search. + *

+ * This method delivers good performance and can outperform {@link #foreachChild(T, Predicate, boolean)} in certain + * small widget + * trees. + * + * @param parent starting point + * @param consumer Operation on each child. Return false to terminate the iteration. + * @param includeSelf true if the consumer should also consume the parent + * @return true if the iteration was not terminated by the consumer + */ + public static > boolean foreachChildBFS(T parent, Predicate consumer, + boolean includeSelf) { + if (includeSelf && !consumer.test(parent)) return false; + ObjectList parents = ObjectList.create(); + parents.add(parent); + while (!parents.isEmpty()) { + for (T child : parents.removeFirst().getChildren()) { + if (child.hasChildren()) { + parents.addLast(child); + } + if (!consumer.test(child)) return false; + } + } + return true; + } + + /** + * @see #foreachChild(T, Predicate, boolean) + */ + public static > boolean foreachChild(T parent, Predicate consumer) { + return foreachChild(parent, consumer, false); + } + + /** + * Iterates through the whole sub widget tree recursively. + *

+ * This method has the best performance in most cases, but can be outperformed on certain small widget trees. + * + * @param parent starting point + * @param consumer Operation on each child. Return false to terminate the iteration. + * @param includeSelf true if the consumer should also consume the parent + * @return true if the iteration was not terminated by the consumer + */ + public static > boolean foreachChild(T parent, Predicate consumer, boolean includeSelf) { + if (includeSelf && !consumer.test(parent)) return false; + if (!parent.hasChildren()) return true; + for (T widget : parent.getChildren()) { + if (!consumer.test(widget)) return false; + if (widget.hasChildren() && !foreachChild(widget, consumer, false)) { + return false; + } + } + return true; + } + + /** + * Iterates through the whole sub widget tree recursively. Unlike {@link #foreachChild(T, Predicate, boolean)}, + * which can only + * return a boolean, this method can return any type. Once the consumer returns a non-null value, the iteration is + * terminated and the + * value will be returned. + * + * @param parent starting point + * @param consumer Operation on each child. Return a non-null value to terminate the iteration and to return the + * value. + * @param includeSelf true if the consumer should also consume the parent + * @return the first resulting value of the consumer or null of it always returned null + */ + public static , V> @Nullable V foreachChildWithResult(T parent, Function consumer, + boolean includeSelf) { + if (includeSelf) { + V t = consumer.apply(parent); + if (t != null) return t; + } + if (!parent.hasChildren()) return null; + for (T widget : parent.getChildren()) { + V t = consumer.apply(widget); + if (t != null) return t; + if (widget.hasChildren()) { + t = foreachChildWithResult(widget, consumer, false); + if (t != null) return t; + } + } + return null; + } + + public static > boolean foreachChildReverse(T parent, Predicate consumer, + boolean includeSelf) { + if (parent.getChildren().isEmpty()) { + return !includeSelf || consumer.test(parent); + } + for (T widget : parent.getChildren()) { + if (!widget.getChildren().isEmpty() && foreachChildReverse(widget, consumer, false)) { + return false; + } + if (!consumer.test(widget)) return false; + } + return !includeSelf || consumer.test(parent); + } + + /** + * Creates a flat stream of the whole sub widget tree. + *

+ * {@link Stream#forEach(Consumer)} on this has slightly worse performance than + * {@link #foreachChildBFS(T, Predicate, boolean)} on + * small widget trees and has similar performance on large widget trees. The performance is significantly better + * than + * {@link #iteratorBFS(T)} even though this method uses it. + * + * @param parent starting point. + * @return stream of the sub widget tree + */ + @SuppressWarnings("UnstableApiUsage") + public static > Stream flatStreamBFS(T parent) { + if (!parent.hasChildren()) return Stream.of(parent); + return Streams.stream(iteratorBFS(parent)); + } + + public static > @UnmodifiableView Iterable iterableBFS(T parent) { + return () -> iteratorBFS(parent); + } + + /** + * Creates an unmodifiable iterator of the whole sub widget tree. + *

+ * This method of iterating has the worst performance in every case. It's roughly 4 times worse than + * {@link #foreachChildBFS(T, Predicate, boolean)}. If not used extensively the performance is still nothing to + * worry about. + * + * @param parent starting point + * @return an unmodifiable iterator of the sub widget tree + */ + public static > @UnmodifiableView Iterator iteratorBFS(T parent) { + return new AbstractIterator<>() { + + private final ObjectList queue = ObjectList.create(); + private Iterator currentIt; + + @Override + protected T computeNext() { + if (currentIt == null) { + currentIt = parent.getChildren().iterator(); + return parent; + } + if (currentIt.hasNext()) return handleWidget(currentIt.next()); + while (!queue.isEmpty()) { + currentIt = queue.removeFirst().getChildren().iterator(); + if (currentIt.hasNext()) return handleWidget(currentIt.next()); + } + return endOfData(); + } + + private T handleWidget(T widget) { + if (widget.hasChildren()) { + queue.add(widget); + } + return widget; + } + }; + } + + /** + * Finds all children in the sub widget tree which match the test and puts them in a list. + * + * @param parent starting point + * @param test test which the target children have to pass + * @return a list of matching children + */ + public static > List flatList(T parent, Predicate test) { + List widgets = new ArrayList<>(); + foreachChild(parent, w -> { + if (test.test(w)) widgets.add(w); + return true; + }, true); + return widgets; + } + + /** + * Finds all children in the sub widget tree which match the test and puts them in a list. + * + * @param parent starting point + * @param test test which the target children have to pass + * @return a list of matching children + */ + public static > List flatListBFS(T parent, Predicate test) { + List widgets = new ArrayList<>(); + foreachChildBFS(parent, w -> { + if (test.test(w)) widgets.add(w); + return true; + }, true); + return widgets; + } + + public static , R extends ITreeNode> List flatListByType(T parent, Class type) { + return flatListByType(parent, type, null); + } + + /** + * Finds all widgets in the sub widget tree which match the given type and additional test. + * + * @param parent starting point + * @param type type of the target widgets + * @param test test which the target widgets have to pass + * @param type of the target widgets + * @return a list of matching widgets + */ + @SuppressWarnings("unchecked") + public static , R extends ITreeNode> List flatListByType(T parent, Class type, + @Nullable Predicate test) { + List widgets = new ArrayList<>(); + foreachChild(parent, w -> { + if (type.isAssignableFrom(w.getClass())) { + if (test == null || test.test((R) w)) widgets.add((R) w); + } + return true; + }, true); + return widgets; + } + + /** + * Finds the first widget in the sub widget tree, for which the test returns true. + * + * @param parent starting point + * @param test test which the widget has to pass + * @return the first matching widget + */ + public static > T findFirst(T parent, @NotNull Predicate test) { + return foreachChildWithResult(parent, w -> { + if (test.test(w)) { + return w; + } + return null; + }, true); + } + + /** + * Finds the first widget in the sub widget tree with the given type, for which the test returns true. + * + * @param parent starting point + * @param type type of the target widget + * @param test test which the widget has to pass + * @return the first matching widget + */ + @SuppressWarnings("unchecked") + public static , R extends ITreeNode> R findFirst(T parent, Class type, + @Nullable Predicate test) { + return foreachChildWithResult(parent, t -> { + if (type.isAssignableFrom(t.getClass())) { + if (test == null || test.test((R) t)) { + return (R) t; + } + } + return null; + }, true); + } + + public static > T findParent(T parent, Predicate filter) { + if (parent == null) return null; + while (!(parent instanceof ModularPanel)) { + if (filter.test(parent)) { + return parent; + } + parent = parent.getParent(); + } + return filter.test(parent) ? parent : null; + } + + public static , R extends ITreeNode> R findParent(T parent, Class type) { + return findParent(parent, type, null); + } + + @SuppressWarnings("unchecked") + public static , R extends ITreeNode> R findParent(T parent, Class type, + @Nullable Predicate test) { + if (parent == null) return null; + while (!(parent instanceof ModularPanel)) { + if (type.isAssignableFrom(parent.getClass()) && (test == null || test.test((R) parent))) { + return (R) parent; + } + parent = parent.getParent(); + } + return type.isAssignableFrom(parent.getClass()) && (test == null || test.test((R) parent)) ? (R) parent : null; + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need + * to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + */ + public static > void print(T parent) { + print(parent, w -> true, null); + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need + * to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + * @param additionalInfo additional info function which is executed for each widget + */ + public static > void print(T parent, NodeInfo additionalInfo) { + print(parent, w -> true, additionalInfo); + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need + * to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + * @param test test widgets have to pass to be added to the string builder + */ + public static > void print(T parent, Predicate test) { + print(parent, test, null); + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need + * to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + * @param test test widgets have to pass to be added to the string builder + * @param additionalInfo additional info function which is executed for each widget + */ + public static > void print(T parent, Predicate test, NodeInfo additionalInfo) { + StringBuilder builder = new StringBuilder(); + if (parent instanceof IWidget) builder.append("Widget"); + else if (parent instanceof ResizeNode) builder.append("ResizeNode"); + else builder.append(parent.getClass()); + builder.append(" tree of ").append(parent).append('\n'); + GTCEu.LOGGER.info(toString(builder, parent, test, additionalInfo)); + } + + public static > String toString(T parent) { + return toString(parent, w -> true, null); + } + + public static > String toString(T parent, NodeInfo additionalInfo) { + return toString(parent, w -> true, additionalInfo); + } + + public static > String toString(T parent, Predicate test) { + return toString(parent, test, null); + } + + public static > String toString(T parent, Predicate test, NodeInfo additionalInfo) { + return toString(null, parent, test, additionalInfo).toString(); + } + + /** + * Writes the sub widget tree into a human-readable tree graph with Unicode characters. + * + * @param builder the string builder to add the tree to or null for a new builder + * @param parent starting point + * @param test test widgets have to pass to be added to the string builder + * @param additionalInfo additional info function which is executed for each widget + * @return the string builder which was used to build the graph + */ + public static > StringBuilder toString(StringBuilder builder, T parent, Predicate test, + NodeInfo additionalInfo) { + if (builder == null) builder = new StringBuilder(); + getTree(parent, parent, test, builder, additionalInfo, "", false, null); + return builder; + } + + protected static > void getTree(T root, T parent, Predicate test, StringBuilder builder, + NodeInfo additionalInfo, String indent, + boolean hasNextSibling, Set visited) { + if (!indent.isEmpty()) { + builder.append(indent); + if (TreeUtil.allowUnicode) { + builder.append(hasNextSibling ? U_PIPE_MID : U_PIPE_END); + } else { + builder.append(hasNextSibling ? PIPE_MID : PIPE_END); + } + builder.append(' '); + } + if (visited == null) visited = new ReferenceOpenHashSet<>(); + if (visited.contains(parent)) { + builder.append("CYCLING TREE FOUND (").append(parent).append(")\n"); + return; + } + visited.add(parent); + builder.append(parent); + if (additionalInfo != null) { + builder.append(" {"); + additionalInfo.addInfo(root, parent, builder); + builder.append("}"); + } + builder.append('\n'); + if (parent.hasChildren()) { + List children = parent.getChildren(); + for (int i = 0; i < children.size(); i++) { + T child = children.get(i); + if (test.test(child)) { + String nextIndent = indent; + if (hasNextSibling) { + nextIndent += (TreeUtil.allowUnicode ? U_PIPE : PIPE) + ' '; + } else { + nextIndent += " "; + } + getTree(root, child, test, builder, additionalInfo, nextIndent, i < children.size() - 1, visited); + } + } + } + } + + public interface NodeInfo> { + + void addInfo(T root, T widget, StringBuilder builder); + + default NodeInfo combine(NodeInfo other, String joiner) { + return (root, widget, builder) -> { + addInfo(root, widget, builder); + builder.append(joiner); + other.addInfo(root, widget, builder); + }; + } + + default NodeInfo combine(NodeInfo other) { + return combine(other, " | "); + } + + @SafeVarargs + static > NodeInfo of(String joiner, NodeInfo... infos) { + return (root, widget, builder) -> { + for (int i = 0; i < infos.length; i++) { + NodeInfo info = infos[i]; + info.addInfo(root, widget, builder); + if (i < infos.length - 1) { + builder.append(joiner); + } + } + }; + } + + @SafeVarargs + static > NodeInfo of(NodeInfo... infos) { + return of(" | ", infos); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/DynamicSyncHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/DynamicSyncHandler.java index 6c4335fb65d..423b15f7f08 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/DynamicSyncHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/DynamicSyncHandler.java @@ -22,6 +22,7 @@ * The widget provider as ran on both sides. Inside the provider sync handlers can be registered with variants of * {@link ISyncRegistrar#getOrCreateSyncHandler(String, int, Class, Supplier)}. */ +@ApiStatus.Obsolete public class DynamicSyncHandler extends SyncHandler implements IDynamicSyncNotifiable { private IWidgetProvider widgetProvider; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericListSyncHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericListSyncHandler.java index a6a95772a8f..487df9ccb23 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericListSyncHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericListSyncHandler.java @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.mui.value.sync; +import com.gregtechceu.gtceu.api.mui.utils.ObjectList; import com.gregtechceu.gtceu.utils.EqualityTest; import com.gregtechceu.gtceu.utils.ICopy; import com.gregtechceu.gtceu.utils.serialization.network.IByteBufDeserializer; @@ -7,8 +8,6 @@ import net.minecraft.network.FriendlyByteBuf; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,7 +20,7 @@ public class GenericListSyncHandler extends GenericCollectionSyncHandler> { - private final ObjectList cache = new ObjectArrayList<>(); + private final ObjectList cache = ObjectList.create(); public GenericListSyncHandler(@NotNull Supplier> getter, @Nullable Consumer> setter, @NotNull IByteBufDeserializer deserializer, diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractParentWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractParentWidget.java index 658e5fb468f..220063d3cd3 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractParentWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractParentWidget.java @@ -60,7 +60,7 @@ public boolean canHover() { IDrawable.isVisible(getHoverOverlay()) || getTooltip() != null) return true; - WidgetThemeEntry widgetTheme = getWidgetTheme(getContext().getTheme()); + WidgetThemeEntry widgetTheme = getWidgetTheme(getPanel().getTheme()); if (getBackground() == null && IDrawable.isVisible(widgetTheme.getTheme().getBackground())) return true; return getHoverBackground() == null && IDrawable.isVisible(widgetTheme.getHoverTheme().getBackground()); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractScrollWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractScrollWidget.java index 6289b64e5a4..ec19b55fc77 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractScrollWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractScrollWidget.java @@ -70,7 +70,7 @@ public void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, public void beforeResize(boolean onOpen) { super.beforeResize(onOpen); - this.scroll.applyWidgetTheme(getContext().getTheme().getScrollbarTheme().getTheme(isHovering())); + this.scroll.applyWidgetTheme(getPanel().getTheme().getScrollbarTheme().getTheme(isHovering())); if (onOpen) checkScrollbarActive(true); getScrollArea().getScrollPadding().scrollPaddingAll(0); applyAdditionalOffset(this.scroll.getScrollX()); @@ -139,7 +139,7 @@ public void preDraw(ModularGuiContext context, boolean transformed) { public void postDraw(ModularGuiContext context, boolean transformed) { if (!transformed) { context.getStencil().pop(); - WidgetThemeEntry scrollbarTheme = context.getTheme().getScrollbarTheme(); + WidgetThemeEntry scrollbarTheme = getPanel().getTheme().getScrollbarTheme(); this.scroll.drawScrollbar(context, scrollbarTheme.getTheme(isHovering()), scrollbarTheme.getTheme().getBackground()); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractWidget.java new file mode 100644 index 00000000000..384adf8d16c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractWidget.java @@ -0,0 +1,347 @@ +package com.gregtechceu.gtceu.api.mui.widget; + +import com.gregtechceu.gtceu.api.mui.base.widget.IDelegatingWidget; +import com.gregtechceu.gtceu.api.mui.base.widget.INotifyEnabled; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.MustBeInvokedByOverriders; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * Very basic implementation of {@link IWidget}. + */ +public abstract class AbstractWidget implements IWidget { + + // gui context + private boolean valid = false; + private IWidget parent = null; + private ModularPanel panel = null; + private ModularGuiContext context = null; + + @Nullable + private String name; + private boolean enabled = true; + private int timeHovered = -1; + private int timeBelowMouse = -1; + + private final Area area = new Area(); + private StandardResizer resizer; + + /** + * Returns the screen of the panel of this widget is being opened in. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public ModularScreen getScreen() { + return getPanel().getScreen(); + } + + @Override + public void scheduleResize() { + this.resizer.markDirty(); + } + + @Override + public boolean requiresResize() { + return this.resizer.requiresResize(); + } + + /** + * Called when a panel is opened. Use {@link #onInit()} and {@link #afterInit()} for custom logic. + * + * @param parent the parent this element belongs to + * @param late true if this is called some time after the widget tree of the parent has been initialised + */ + @ApiStatus.Internal + @Override + public final void initialise(@NotNull IWidget parent, boolean late) { + this.timeHovered = -1; + this.timeBelowMouse = -1; + if (this.resizer == null) { + throw new IllegalStateException( + "Resizer must be set before the widget initializes! Affected widget: " + this); + } + if (!(this instanceof ModularPanel)) { + this.parent = parent; + this.panel = parent.getPanel(); + this.context = parent.getContext(); + getArea().z(parent.getArea().z() + 1); + if (parent instanceof AbstractWidget aw) { + this.resizer.initialize(aw.resizer, parent.getScreen().getResizeNode()); + } else { + this.resizer.initialize(parent.resizer(), parent.getScreen().getResizeNode()); + } + } + this.valid = true; + onInitInternal(late); + onInit(); + if (hasChildren()) { + for (IWidget child : getChildren()) { + child.initialise(this, false); + } + } + afterInit(); + onResized(); + } + + void onInitInternal(boolean late) {} + + /** + * Called after this widget is initialised and before the children are initialised. + */ + @ApiStatus.OverrideOnly + public void onInit() {} + + /** + * Called after this widget is initialised and after the children are initialised. + */ + @ApiStatus.OverrideOnly + public void afterInit() {} + + /** + * Called when this widget is removed from the widget tree or after the panel is closed. + * Overriding this is fine, but super must be called. + */ + @MustBeInvokedByOverriders + @Override + public void dispose() { + if (hasChildren()) { + for (IWidget child : getChildren()) { + child.dispose(); + } + } + if (!(this instanceof ModularPanel)) { + this.panel = null; + this.parent = null; + this.context = null; + } + resizer().dispose(); + this.timeHovered = -1; + this.timeBelowMouse = -1; + this.valid = false; + } + + // ------------------- + // === Gui context === + // ------------------- + + /** + * Returns if this widget is currently part of an open panel. Only if this is true information about parent, panel + * and gui context can + * be obtained. + * + * @return true if this widget is part of an open panel + */ + @Override + public boolean isValid() { + return valid; + } + + @Override + public void onUpdate() { + if (isHovering()) this.timeHovered++; + if (isBelowMouse()) this.timeBelowMouse++; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseStartHover() { + this.timeHovered = 0; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseEndHover() { + this.timeHovered = -1; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseEnterArea() { + this.timeBelowMouse = 0; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseLeaveArea() { + this.timeBelowMouse = -1; + } + + @Override + public boolean isHoveringFor(int ticks) { + return timeHovered >= ticks; + } + + @Override + public boolean isBelowMouseFor(int ticks) { + return timeBelowMouse >= ticks; + } + + public int getTicksHovered() { + return timeHovered; + } + + public int getTicksBelowMouse() { + return timeBelowMouse; + } + + /** + * Returns the area of this widget. This contains information such as position, size, relative position to parent, + * padding and margin. + * Even tho this is a mutable object, you should refrain from modifying the values. + * + * @return area of this widget + */ + @Override + public Area getArea() { + return area; + } + + /** + * Shortcut to get the area of the parent + * + * @return parent area + */ + public Area getParentArea() { + IWidget parent = getParent(); + while (parent instanceof IDelegatingWidget dw) { + parent = dw.getParent(); + } + return parent.getArea(); + } + + /** + * Returns if this widget is currently enabled. Disabled widgets (and all its children) are not rendered and can't + * be interacted with. + * + * @return true if this widget is enabled. + */ + @Override + public boolean isEnabled() { + return this.enabled; + } + + /** + * Sets enabled state. Disabled widgets (and all its children) are not rendered and can't be interacted with. + * + * @param enabled enabled state + */ + @Override + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + this.enabled = enabled; + if (isValid() && getParent() instanceof INotifyEnabled notifyEnabled) { + notifyEnabled.onChildChangeEnabled(this, enabled); + } + } + } + + /** + * Returns the parent of this widget. If this is a {@link ModularPanel} this will always return null contrary to the + * annotation. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public @NotNull IWidget getParent() { + if (!isValid()) { + throw new IllegalStateException(this + " is not in a valid state!"); + } + return parent; + } + + /** + * Returns the gui context of the screen this widget is part of. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public ModularGuiContext getContext() { + if (!isValid()) { + throw new IllegalStateException(this + " is not in a valid state!"); + } + return context; + } + + /** + * Used to set the gui context on panels internally. + */ + @ApiStatus.Internal + protected final void setContext(ModularGuiContext context) { + this.context = context; + } + + /** + * Returns the panel of this widget is being opened in. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public @NotNull ModularPanel getPanel() { + if (!isValid()) { + throw new IllegalStateException(this + " is not in a valid state!"); + } + return panel; + } + + @Override + public @NotNull StandardResizer resizer() { + return this.resizer; + } + + protected final StandardResizer rawResizer() { + return this.resizer; + } + + protected void resizer(StandardResizer resizer) { + Objects.requireNonNull(resizer); + if (this.resizer == resizer) return; + if (isValid() && this.resizer != null) { + resizer.replacementOf(this.resizer); + } + this.resizer = resizer; + } + + @Override + public @Nullable String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + /** + * This is only used in {@link #toString()}. + * + * @return the simple class name or other fitting name + */ + protected String getTypeName() { + return getClass().getSimpleName(); + } + + /** + * @return the simple class plus the debug name if set + */ + @Override + public String toString() { + if (getName() != null) { + return getTypeName() + "#" + getName(); + } + return getTypeName(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DelegatingWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DelegatingWidget.java new file mode 100644 index 00000000000..2e56eb8c750 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DelegatingWidget.java @@ -0,0 +1,108 @@ +package com.gregtechceu.gtceu.api.mui.widget; + +import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; +import com.gregtechceu.gtceu.api.mui.base.widget.IDelegatingWidget; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.MutableSingletonList; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class DelegatingWidget extends AbstractWidget implements IDelegatingWidget { + + private final MutableSingletonList delegate = new MutableSingletonList<>(); + + public DelegatingWidget(IWidget delegate) { + this.delegate.set(delegate); + resizer(new StandardResizer(this)); + } + + protected void setDelegate(IWidget delegate) { + if (!this.delegate.isEmpty()) { + this.delegate.get().dispose(); + this.delegate.remove(); + } + if (delegate != null) { + this.delegate.set(delegate); + if (isValid()) { + initialise(getParent(), true); + delegate.scheduleResize(); + } + onChangeDelegate(delegate); + } + } + + protected void onChangeDelegate(IWidget delegate) {} + + @Override + public @NotNull List getChildren() { + return this.delegate; + } + + @Override + public void afterInit() { + super.resizer().setDefaultParent(null); // remove this widget from the resize node tree + if (hasChildren()) { + getDelegate().resizer().setDefaultParentIsDelegating(true); + getDelegate().resizer().relative(getParent()); // add the delegated widget at the place of this widget on + // the resize node tree + } + } + + @Override + public void postResize() { + super.postResize(); + if (getDelegate() != null) { + Area childArea = getDelegate().getArea(); + Area area = super.getArea(); + area.set(childArea); + area.rx = childArea.rx; + area.ry = childArea.ry; + childArea.rx = 0; + childArea.ry = 0; + } + } + + @Override + public @NotNull StandardResizer resizer() { + return getDelegate() != null ? getDelegate().resizer() : super.resizer(); + } + + @Override + public Area getArea() { + return getDelegate() != null ? getDelegate().getArea() : super.getArea(); + } + + @Override + public void transform(IViewportStack stack) { + stack.translate(super.getArea().rx, super.getArea().ry, 0); + } + + @Override + public boolean canBeSeen(IViewportStack stack) { + return false; + } + + @Override + public boolean requiresResize() { + return getDelegate() != null && getDelegate().requiresResize(); + } + + @Override + public int getDefaultWidth() { + return getDelegate() != null ? getDelegate().getDefaultWidth() : super.getDefaultWidth(); + } + + @Override + public int getDefaultHeight() { + return getDelegate() != null ? getDelegate().getDefaultHeight() : super.getDefaultHeight(); + } + + @Override + public IWidget getDelegate() { + return delegate.getOrNull(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DragHandle.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DragHandle.java index 38e3f065886..9063eda9ab2 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DragHandle.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DragHandle.java @@ -2,7 +2,6 @@ import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; import com.gregtechceu.gtceu.api.mui.base.widget.IDraggable; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.utils.HoveredWidgetList; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; @@ -60,7 +59,7 @@ public void onDrag(int mouseButton, double timeSinceLastClick) { } @Override - public boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + public boolean canDropHere(int x, int y, @Nullable IWidget widget) { return this.parentDraggable != null && this.parentDraggable.canDropHere(x, y, widget); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DraggableWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DraggableWidget.java index 99d26087bb3..8f172cc7392 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DraggableWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/DraggableWidget.java @@ -50,7 +50,7 @@ public boolean onDragStart(int mouseButton) { @Override public void onDragEnd(boolean successful) { if (successful) { - flex().top(getContext().getAbsMouseY() - this.relativeClickY) + resizer().top(getContext().getAbsMouseY() - this.relativeClickY) .left(getContext().getAbsMouseX() - this.relativeClickX); this.movingArea.x = getArea().x; this.movingArea.y = getArea().y; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/EmptyWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/EmptyWidget.java index 2e36cb5849e..6151afdf30e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/EmptyWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/EmptyWidget.java @@ -1,11 +1,10 @@ package com.gregtechceu.gtceu.api.mui.widget; -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Flex; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; @@ -20,7 +19,7 @@ public class EmptyWidget implements IWidget { @Getter private final Area area = new Area(); @Getter - private final Flex flex = new Flex(this); + private final StandardResizer resizer = new StandardResizer(this); private boolean requiresResize = false; @Setter @Getter @@ -99,28 +98,16 @@ public boolean canHoverThrough() { return true; } - @Override - public void markTooltipDirty() {} - @Override public ModularGuiContext getContext() { return this.parent.getContext(); } @Override - public Flex flex() { - return this.flex; + public @NotNull StandardResizer resizer() { + return this.resizer; } - @NotNull - @Override - public IResizeable resizer() { - return this.flex; - } - - @Override - public void resizer(IResizeable resizer) {} - @Nullable @Override public String getName() { diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/InternalWidgetTree.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/InternalWidgetTree.java index 69d9beca067..ffaca03dcee 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/InternalWidgetTree.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/InternalWidgetTree.java @@ -1,11 +1,10 @@ package com.gregtechceu.gtceu.api.mui.widget; import com.gregtechceu.gtceu.api.mui.base.GuiAxis; -import com.gregtechceu.gtceu.api.mui.base.layout.ILayoutWidget; -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; import com.gregtechceu.gtceu.api.mui.base.layout.IViewport; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; +import com.gregtechceu.gtceu.api.mui.widget.sizer.ResizeNode; import com.gregtechceu.gtceu.api.mui.widgets.layout.IExpander; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; @@ -16,7 +15,6 @@ import org.jetbrains.annotations.Contract; import java.util.*; -import java.util.function.Predicate; public class InternalWidgetTree { @@ -51,7 +49,7 @@ static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEn boolean shouldDrawBackground) { if (!parent.isEnabled() && !ignoreEnabled) return; if (parent.requiresResize()) { - WidgetTree.resizeInternal(parent, false); + WidgetTree.resizeInternal(parent.resizer(), false); } GuiGraphics graphics = context.getGraphics(); @@ -71,7 +69,7 @@ static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEn if (canBeSeen) { // draw widget graphics.setColor(1f, 1f, 1f, alpha); - WidgetThemeEntry widgetTheme = parent.getWidgetTheme(context.getTheme()); + WidgetThemeEntry widgetTheme = parent.getWidgetTheme(parent.getPanel().getTheme()); if (shouldDrawBackground) parent.drawBackground(context, widgetTheme); parent.draw(context, widgetTheme); parent.drawOverlay(context, widgetTheme); @@ -160,7 +158,7 @@ static void drawBackground(IWidget parent, ModularGuiContext context, boolean ig // draw widget graphics.setColor(1f, 1f, 1f, alpha); - WidgetThemeEntry widgetTheme = parent.getWidgetTheme(context.getTheme()); + WidgetThemeEntry widgetTheme = parent.getWidgetTheme(parent.getPanel().getTheme()); parent.drawBackground(context, widgetTheme); graphics.pose().popPose(); @@ -188,30 +186,27 @@ static void drawTreeForeground(IWidget parent, ModularGuiContext context) { context.popMatrix(); } - static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolean isParentLayout) { + static boolean resize(ResizeNode resizer, boolean init, boolean onOpen, boolean isParentLayout) { boolean alreadyCalculated = false; // first try to resize this widget - IResizeable resizer = widget.resizer(); - ILayoutWidget layout = widget instanceof ILayoutWidget layoutWidget ? layoutWidget : null; - boolean isLayout = layout != null; + boolean isLayout = resizer.isLayout(); if (init) { - widget.beforeResize(onOpen); - resizer.initResizing(); + resizer.initResizing(onOpen); if (!isLayout) resizer.setLayoutDone(true); } else { // if this is not the first time check if this widget is already resized alreadyCalculated = resizer.isFullyCalculated(isParentLayout); } - boolean selfFullyCalculated = resizer.isSelfFullyCalculated() || resizer.resize(widget, isParentLayout); + boolean selfFullyCalculated = resizer.isSelfFullyCalculated() || resizer.resize(isParentLayout); - GuiAxis expandAxis = widget instanceof IExpander expander ? expander.getExpandAxis() : null; + GuiAxis expandAxis = resizer instanceof IExpander expander ? expander.getExpandAxis() : null; // now resize all children and collect children which could not be fully calculated - List anotherResize = Collections.emptyList(); - if (!resizer.areChildrenCalculated() && widget.hasChildren()) { + List anotherResize = Collections.emptyList(); + if (!resizer.areChildrenCalculated() && !resizer.getChildren().isEmpty()) { anotherResize = new ArrayList<>(); - for (IWidget child : widget.getChildren()) { - if (init) child.flex().checkExpanded(expandAxis); - if (!resizeWidget(child, init, onOpen, isLayout)) { + for (ResizeNode child : resizer.getChildren()) { + if (init) child.checkExpanded(expandAxis); + if (!resize(child, init, onOpen, isLayout)) { anotherResize.add(child); } } @@ -223,15 +218,15 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea // we need to keep track of which widgets are not yet fully calculated, so we can call onResized on those // which later are fully calculated BitSet state = getCalculatedState(anotherResize, isLayout); - if (layout != null && shouldLayout) { - layoutSuccessful = layout.layoutWidgets(); + if (isLayout && shouldLayout) { + layoutSuccessful = resizer.layoutChildren(); } // post resize this widget if possible - resizer.postResize(widget); + resizer.postResize(); - if (layout != null && shouldLayout) { - layoutSuccessful &= layout.postLayoutWidgets(); + if (isLayout && shouldLayout) { + layoutSuccessful &= resizer.postLayoutChildren(); } if (shouldLayout) resizer.setLayoutDone(layoutSuccessful); checkFullyCalculated(anotherResize, state, isLayout); @@ -240,7 +235,7 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea // now fully resize all children which needs it if (!anotherResize.isEmpty()) { for (int i = 0; i < anotherResize.size(); i++) { - if (resizeWidget(anotherResize.get(i), false, onOpen, isLayout)) { + if (resize(anotherResize.get(i), false, onOpen, isLayout)) { anotherResize.remove(i--); } } @@ -248,29 +243,29 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea resizer.setChildrenResized(anotherResize.isEmpty()); selfFullyCalculated = resizer.isFullyCalculated(isParentLayout); - if (selfFullyCalculated && !alreadyCalculated) widget.onResized(); + if (selfFullyCalculated && !alreadyCalculated) resizer.onResized(); return selfFullyCalculated; } - private static BitSet getCalculatedState(List children, boolean isLayout) { + private static BitSet getCalculatedState(List children, boolean isLayout) { if (children.isEmpty()) return null; BitSet state = new BitSet(); for (int i = 0; i < children.size(); i++) { - IWidget widget = children.get(i); - if (widget.resizer().isFullyCalculated(isLayout)) { + ResizeNode widget = children.get(i); + if (widget.isFullyCalculated(isLayout)) { state.set(i); } } return state; } - private static void checkFullyCalculated(List children, BitSet state, boolean isLayout) { + private static void checkFullyCalculated(List children, BitSet state, boolean isLayout) { if (children.isEmpty() || state == null) return; int j = 0; for (int i = 0; i < children.size(); i++) { - IWidget widget = children.get(i); - if (!state.get(j) && widget.resizer().isFullyCalculated(isLayout)) { + ResizeNode widget = children.get(i); + if (!state.get(j) && widget.isFullyCalculated(isLayout)) { widget.onResized(); state.set(j); children.remove(i--); @@ -278,28 +273,4 @@ private static void checkFullyCalculated(List children, BitSet state, b j++; } } - - static void getTree(IWidget root, IWidget parent, Predicate test, StringBuilder builder, - WidgetTree.WidgetInfo additionalInfo, String indent, boolean hasNextSibling) { - if (!indent.isEmpty()) { - builder.append(indent).append(hasNextSibling ? "├ " : "└ "); - } - builder.append(parent); - if (additionalInfo != null) { - builder.append(" {"); - additionalInfo.addInfo(root, parent, builder); - builder.append("}"); - } - builder.append('\n'); - if (parent.hasChildren()) { - List children = parent.getChildren(); - for (int i = 0; i < children.size(); i++) { - IWidget child = children.get(i); - if (test.test(child)) { - getTree(root, child, test, builder, additionalInfo, indent + (hasNextSibling ? "│ " : " "), - i < children.size() - 1); - } - } - } - } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/RenderNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/RenderNode.java new file mode 100644 index 00000000000..e47a79d90c5 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/RenderNode.java @@ -0,0 +1,27 @@ +package com.gregtechceu.gtceu.api.mui.widget; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +import java.util.List; + +public class RenderNode implements WidgetNode { + + private IWidget linkedWidget; + private RenderNode parent; + private List children; + + @Override + public IWidget getWidget() { + return linkedWidget; + } + + @Override + public RenderNode getParent() { + return parent; + } + + @Override + public List getChildren() { + return children; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/Widget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/Widget.java index 67afa08df53..fdbfc1776fe 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/Widget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/Widget.java @@ -4,7 +4,6 @@ import com.gregtechceu.gtceu.api.mui.base.IThemeApi; import com.gregtechceu.gtceu.api.mui.base.IUIHolder; import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; import com.gregtechceu.gtceu.api.mui.base.value.ISyncOrValue; import com.gregtechceu.gtceu.api.mui.base.value.IValue; @@ -17,11 +16,8 @@ import com.gregtechceu.gtceu.api.mui.value.sync.ModularSyncManager; import com.gregtechceu.gtceu.api.mui.value.sync.SyncHandler; import com.gregtechceu.gtceu.api.mui.value.sync.ValueSyncHandler; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; -import com.gregtechceu.gtceu.api.mui.widget.sizer.Flex; -import com.gregtechceu.gtceu.api.mui.widget.sizer.IUnResizeable; -import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; -import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Bounds; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; @@ -48,52 +44,13 @@ * * @param the type of this widget. This is used for proper return types in builder like methodsY */ -public class Widget> implements IWidget, IPositioned, ITooltip, ISynced { +public class Widget> extends AbstractWidget implements IPositioned, ITooltip, ISynced { // other - @Nullable - @Getter - private String name; - /** - * Returns if this widget is currently enabled. Disabled widgets (and all its children) are not rendered and can't - * be interacted with. - */ - @Getter - private boolean enabled = true; - private int timeHovered = -1; - private int timeBelowMouse = -1; @Getter private boolean excludeAreaInXei = false; - // gui context - /** - * Returns if this widget is currently part of an open panel. Only if this is true information about parent, panel - * and gui context can - * be obtained. - */ - @Getter - private boolean valid = false; - private IWidget parent = null; - private ModularPanel panel = null; - private ModularGuiContext context = null; // sizing - /** - * Returns the area of this widget. This contains information such as position, size, relative position to parent, - * padding and margin. - * Even tho this is a mutable object, you should refrain from modifying the values. - */ - @Getter - private final Area area = new Area(); - /** - * Returns the flex of this widget. This is responsible for calculating size, pos and relative pos. - * Originally this was intended to be modular for custom flex class. May come back to this in the future. - * Same as {@link #flex()}. - */ - @Getter - private final Flex flex = new Flex(this); - private IResizeable resizer = this.flex; - private BiConsumer transform; - private boolean requiresResize = false; // syncing /** * Returns the value handler of this widget. Value handlers can provide and update any kind of objects like numbers @@ -113,6 +70,8 @@ public class Widget> implements IWidget, IPositioned, ITo @Nullable private SyncHandler syncHandler; // rendering + @Nullable + private IDrawable shadow = null; /** * The current set background. This is not an accurate representation of what is actually being displayed currently. * Usually background is handled by the theme, which is when this is null. @@ -153,66 +112,34 @@ public class Widget> implements IWidget, IPositioned, ITo @Nullable private Consumer onUpdateListener; + public Widget() { + resizer(new StandardResizer(this)); + } + // ----------------- // === Lifecycle === // ----------------- - /** - * Called when a panel is opened. Use {@link #onInit()} and {@link #afterInit()} for custom logic. - * - * @param parent the parent this element belongs to - * @param late true if this is called some time after the widget tree of the parent has been initialised - */ - @ApiStatus.Internal @Override - public void initialise(@NotNull IWidget parent, boolean late) { - this.timeHovered = -1; - this.timeBelowMouse = -1; - if (!(this instanceof ModularPanel)) { - this.parent = parent; - this.panel = parent.getPanel(); - this.context = parent.getContext(); - getArea().z(parent.getArea().z() + 1); - if (this.guiActionListeners != null) { - for (IGuiAction action : this.guiActionListeners) { - this.context.getScreen().registerGuiActionListener(action); - } + void onInitInternal(boolean late) { + if (this.guiActionListeners != null) { + for (IGuiAction action : this.guiActionListeners) { + getContext().getScreen().registerGuiActionListener(action); } } + if (this.value != null && this.syncKey != null) { throw new IllegalStateException( "Widget has a value and a sync key for a synced value. This is not allowed!"); } - this.valid = true; if (!getScreen().isClientOnly()) { initialiseSyncHandler(getScreen().getSyncManager(), late); } if (isExcludeAreaInXei()) { getContext().getXeiSettings().addExclusionArea(this); } - onInit(); - if (hasChildren()) { - for (IWidget child : getChildren()) { - child.initialise(this, false); - } - } - afterInit(); - onUpdate(); - this.requiresResize = false; } - /** - * Called after this widget is initialised and before the children are initialised. - */ - @ApiStatus.OverrideOnly - public void onInit() {} - - /** - * Called after this widget is initialised and after the children are initialised. - */ - @ApiStatus.OverrideOnly - public void afterInit() {} - /** * Retrieves, verifies and initialises a linked sync handler. * Custom logic should be handled in {@link #setSyncOrValue(ISyncOrValue)}. @@ -244,26 +171,14 @@ public void dispose() { if (isValid()) { if (this.guiActionListeners != null) { for (IGuiAction action : this.guiActionListeners) { - this.context.getScreen().removeGuiActionListener(action); + getScreen().removeGuiActionListener(action); } } if (isExcludeAreaInXei()) { getContext().getXeiSettings().removeExclusionArea(this); } } - if (hasChildren()) { - for (IWidget child : getChildren()) { - child.dispose(); - } - } - if (!(this instanceof ModularPanel)) { - this.panel = null; - this.parent = null; - this.context = null; - } - this.timeHovered = -1; - this.timeBelowMouse = -1; - this.valid = false; + super.dispose(); } // ----------------- @@ -281,7 +196,11 @@ public void dispose() { */ @Override public void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - IDrawable bg = getCurrentBackground(context.getTheme(), widgetTheme); + if (this.shadow != null) { + this.shadow.drawAtZero(context, getArea().width, getArea().height, + getActiveWidgetTheme(widgetTheme, isHovering())); + } + IDrawable bg = getCurrentBackground(getPanel().getTheme(), widgetTheme); if (bg != null) { bg.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); } @@ -311,7 +230,7 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} */ @Override public void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - IDrawable bg = getCurrentOverlay(context.getTheme(), widgetTheme); + IDrawable bg = getCurrentOverlay(getPanel().getTheme(), widgetTheme); if (bg != null) { bg.drawAtZeroPadded(context, getArea(), getActiveWidgetTheme(widgetTheme, isHovering())); } @@ -395,7 +314,6 @@ public W invisible() { * {@link ITooltip#tooltipDynamic(Consumer)}. * It will invalidate the current tooltip and be caused to rebuild. */ - @Override public void markTooltipDirty() { if (this.tooltip != null) { this.tooltip.markDirty(); @@ -582,8 +500,7 @@ public W widgetTheme(WidgetThemeKey s) { @MustBeInvokedByOverriders @Override public void onUpdate() { - if (isHovering()) this.timeHovered++; - if (isBelowMouse()) this.timeBelowMouse++; + super.onUpdate(); if (this.onUpdateListener != null) { this.onUpdateListener.accept(getThis()); } @@ -608,7 +525,7 @@ public W listenGuiAction(IGuiAction action) { } this.guiActionListeners.add(action); if (isValid()) { - this.context.getScreen().registerGuiActionListener(action); + getScreen().registerGuiActionListener(action); } return getThis(); } @@ -662,73 +579,21 @@ public W setEnabledIf(Predicate condition) { // === Resizing === // ---------------- + public void estimateSize(Bounds bounds) {} + @Override public int getDefaultWidth() { - return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultWidth() : 18; + return isValid() ? getWidgetTheme(getPanel().getTheme()).getTheme().getDefaultWidth() : 18; } @Override public int getDefaultHeight() { - return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultHeight() : 18; - } - - @Override - public void scheduleResize() { - this.requiresResize = true; - } - - @Override - public boolean requiresResize() { - return this.requiresResize; - } - - @MustBeInvokedByOverriders - @Override - public void onResized() { - this.requiresResize = false; - } - - /** - * Returns the flex of this widget. This is responsible for calculating size, pos and relative pos. - * Originally this was intended to be modular for custom flex class. May come back to this in the future. - * Same as {@link #getFlex()}. - * - * @return flex of this widget - */ - @Override - public Flex flex() { - return getFlex(); - } - - /** - * Returns the resizer of this widget. This is actually the field responsible for resizing this widget. - * Within MUI this is always the same as {@link #flex()}. Custom resizer have not been tested. - * The relevance of separating flex and resizer is left to be investigated in the future. - * - * @return the resizer of this widget - */ - @NotNull - @Override - public IResizeable resizer() { - return this.resizer; - } - - /** - * Sets the resizer of this widget, which is responsible for resizing this widget. - * Within MUI this setter is never used. Custom resizer have not been tested. - * The relevance of separating flex and resizer is left to be investigated in the future. - * - * @param resizer resizer - */ - @ApiStatus.Experimental - @Override - public void resizer(IResizeable resizer) { - this.resizer = resizer != null ? resizer : IUnResizeable.INSTANCE; + return isValid() ? getWidgetTheme(getPanel().getTheme()).getTheme().getDefaultHeight() : 18; } @Override public void transform(IViewportStack stack) { - IWidget.super.transform(stack); + super.transform(stack); if (this.transform != null) { this.transform.accept(getThis(), stack); } @@ -739,72 +604,6 @@ public W transform(BiConsumer transform) { return getThis(); } - // ------------------- - // === Gui context === - // ------------------- - - /** - * Returns the screen of the panel of this widget is being opened in. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public ModularScreen getScreen() { - return getPanel().getScreen(); - } - - /** - * Returns the panel of this widget is being opened in. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public @NotNull ModularPanel getPanel() { - if (!isValid()) { - throw new IllegalStateException(this + " is not in a valid state!"); - } - return this.panel; - } - - /** - * Returns the parent of this widget. If this is a {@link ModularPanel} this will always return null contrary to the - * annotation. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public @NotNull IWidget getParent() { - if (!isValid()) { - throw new IllegalStateException(this + " is not in a valid state!"); - } - return this.parent; - } - - /** - * Returns the gui context of the screen this widget is part of. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public ModularGuiContext getContext() { - if (!isValid()) { - throw new IllegalStateException(this + " is not in a valid state!"); - } - return this.context; - } - - /** - * Used to set the gui context on panels internally. - */ - @ApiStatus.Internal - protected final void setContext(ModularGuiContext context) { - this.context = context; - } - // --------------- // === Syncing === // -------------- @@ -846,16 +645,6 @@ public W syncHandler(String name, int id) { return getThis(); } - @Override - public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - this.enabled = enabled; - if (this.isValid() && getParent() instanceof INotifyEnabled notifyEnabled) { - notifyEnabled.onChildChangeEnabled(this, enabled); - } - } - } - /** * Used for widgets to set a value handler.
* Will also call {@link #setSyncHandler(SyncHandler)} if it is a SyncHandler @@ -914,48 +703,6 @@ public W disabled() { return getThis(); } - @MustBeInvokedByOverriders - @Override - public void onMouseStartHover() { - this.timeHovered = 0; - } - - @MustBeInvokedByOverriders - @Override - public void onMouseEndHover() { - this.timeHovered = -1; - } - - @MustBeInvokedByOverriders - @Override - public void onMouseEnterArea() { - this.timeBelowMouse = 0; - } - - @MustBeInvokedByOverriders - @Override - public void onMouseLeaveArea() { - this.timeBelowMouse = -1; - } - - @Override - public boolean isHoveringFor(int ticks) { - return timeHovered >= ticks; - } - - @Override - public boolean isBelowMouseFor(int ticks) { - return timeBelowMouse >= ticks; - } - - public int getTicksHovered() { - return timeHovered; - } - - public int getTicksBelowMouse() { - return timeBelowMouse; - } - @Override public Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { if (this instanceof IDragResizeable dragResizeable) { @@ -981,7 +728,7 @@ public W debugName(String name) { * @return this */ public W name(String name) { - this.name = name; + setName(name); return getThis(); } @@ -995,24 +742,4 @@ public W name(String name) { public W getThis() { return (W) this; } - - /** - * This is only used in {@link #toString()}. - * - * @return the simple class name or other fitting name - */ - protected String getTypeName() { - return getClass().getSimpleName(); - } - - /** - * @return the simple class plus the debug name, if set - */ - @Override - public String toString() { - if (getName() != null) { - return getTypeName() + "#" + getName(); - } - return getTypeName(); - } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetNode.java new file mode 100644 index 00000000000..d56584b7a36 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetNode.java @@ -0,0 +1,14 @@ +package com.gregtechceu.gtceu.api.mui.widget; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +import java.util.List; + +public interface WidgetNode { + + IWidget getWidget(); + + T getParent(); + + List getChildren(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetTree.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetTree.java index 5cff22196ca..a1caa48768c 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetTree.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetTree.java @@ -2,11 +2,12 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.mui.base.MCHelper; -import com.gregtechceu.gtceu.api.mui.base.layout.ILayoutWidget; import com.gregtechceu.gtceu.api.mui.base.widget.ISynced; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.TreeUtil; import com.gregtechceu.gtceu.api.mui.value.sync.ModularSyncManager; import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widget.sizer.ResizeNode; import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; import com.gregtechceu.gtceu.utils.FormattingUtil; @@ -14,27 +15,18 @@ import net.minecraft.Util; import net.minecraft.network.chat.Component; -import com.google.common.collect.AbstractIterator; -import com.google.common.collect.Streams; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; import org.apache.commons.lang3.mutable.MutableInt; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnmodifiableView; import java.util.*; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; /** * Helper class to perform operations on widget trees such as traversing, drawing, resizing, finding widgets and * printing it. */ -public class WidgetTree { +public class WidgetTree extends TreeUtil { /** * If this variable is true, the time it takes to resize a sub widget tree is logged each time. @@ -42,7 +34,7 @@ public class WidgetTree { */ public static boolean logResizeTime = false; - public static final WidgetInfo INFO_AREA = (root, widget, builder) -> builder + public static final WidgetInfo WIDGET_INFO_AREA = (root, widget, builder) -> builder .append("Area xywh:") .append(widget.getArea().x - root.getArea().x) .append(", ") @@ -50,305 +42,18 @@ public class WidgetTree { .append(" | ") .append(widget.getArea().width) .append(", ") - .append(widget.getArea().height); - - public static final WidgetInfo INFO_ENABLED = (root, widget, builder) -> builder - .append("Enabled: ").append(widget.isEnabled()); - - public static final WidgetInfo INFO_FULLY_RESIZED = (root, widget, builder) -> builder - .append("Fully resized: ") - .append(widget.resizer() - .isFullyCalculated(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)); - - public static final WidgetInfo INFO_RESIZED_DETAILED = (root, widget, builder) -> builder - .append("Self resized: ") - .append(widget.resizer() - .isSelfFullyCalculated(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)) - .append(", Is pos final: ") - .append(!widget.resizer().canRelayout(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)) - .append(", Children resized: ").append(widget.resizer().areChildrenCalculated()) - .append(", Layout done: ").append(widget.resizer().isLayoutDone()); - - public static final WidgetInfo INFO_RESIZED_COLLAPSED = (root, widget, builder) -> { - if (widget.resizer().isFullyCalculated(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)) { - INFO_FULLY_RESIZED.addInfo(root, widget, builder); - } else { - INFO_RESIZED_DETAILED.addInfo(root, widget, builder); - } - }; + .append(widget.getArea().height) + .append(", rx: ").append(widget.getArea().rx) + .append(", ry: ").append(widget.getArea().ry); - public static final WidgetInfo INFO_WIDGET_THEME = ((root, widget, builder) -> builder - .append(widget.getWidgetTheme(widget.getContext().getTheme()).getKey().getFullName())); + public static final WidgetInfo WIDGET_INFO_ENABLED = (root, widget, builder) -> builder.append("Enabled: ") + .append(widget.isEnabled()); + public static final WidgetInfo WIDGET_INFO_WIDGET_THEME = (root, widget, builder) -> builder + .append("Widget theme: ") + .append(widget.getWidgetTheme(widget.getPanel().getTheme()).getKey().getFullName()); private WidgetTree() {} - public static List getAllChildrenByLayer(IWidget parent) { - return getAllChildrenByLayer(parent, false); - } - - public static List getAllChildrenByLayer(IWidget parent, boolean includeSelf) { - List children = new ArrayList<>(); - if (includeSelf) children.add(parent); - ObjectList parents = new ObjectArrayList<>(); - parents.add(parent); - while (!parents.isEmpty()) { - for (IWidget child : parents.remove(0).getChildren()) { - if (!child.getChildren().isEmpty()) { - parents.add(child); - } - children.add(child); - } - } - return children; - } - - public static boolean foreachChildBFS(IWidget parent, Predicate consumer) { - return foreachChildBFS(parent, consumer, false); - } - - /** - * Iterates through the whole sub widget tree by using breath-first-search. - *

- * This method delivers good performance and can outperform {@link #foreachChild(IWidget, Predicate, boolean)} in - * certain small widget - * trees. - * - * @param parent starting point - * @param consumer Operation on each child. Return false to terminate the iteration. - * @param includeSelf true if the consumer should also consume the parent - * @return true if the iteration was not terminated by the consumer - */ - public static boolean foreachChildBFS(IWidget parent, Predicate consumer, boolean includeSelf) { - if (includeSelf && !consumer.test(parent)) return false; - ObjectList parents = new ObjectArrayList<>(); - parents.add(parent); - while (!parents.isEmpty()) { - for (IWidget child : parents.remove(0).getChildren()) { - if (child.hasChildren()) { - parents.add(child); - } - if (!consumer.test(child)) return false; - } - } - return true; - } - - /** - * @see #foreachChild(IWidget, Predicate, boolean) - */ - public static boolean foreachChild(IWidget parent, Predicate consumer) { - return foreachChild(parent, consumer, false); - } - - /** - * Iterates through the whole sub widget tree recursively. - *

- * This method has the best performance in most cases, but can be outperformed on certain small widget trees. - * - * @param parent starting point - * @param consumer Operation on each child. Return false to terminate the iteration. - * @param includeSelf true if the consumer should also consume the parent - * @return true if the iteration was not terminated by the consumer - */ - public static boolean foreachChild(IWidget parent, Predicate consumer, boolean includeSelf) { - if (includeSelf && !consumer.test(parent)) return false; - if (!parent.hasChildren()) return true; - for (IWidget widget : parent.getChildren()) { - if (!consumer.test(widget)) return false; - if (widget.hasChildren() && !foreachChild(widget, consumer, false)) { - return false; - } - } - return true; - } - - /** - * Iterates through the whole sub widget tree recursively. Unlike - * {@link #foreachChild(IWidget, Predicate, boolean)}, which can only - * return a boolean, this method can return any type. Once the consumer returns a non-null value, the iteration is - * terminated and the - * value will be returned. - * - * @param parent starting point - * @param consumer Operation on each child. Return a non-null value to terminate the iteration and to return the - * value. - * @param includeSelf true if the consumer should also consume the parent - * @return the first resulting value of the consumer or null of it always returned null - */ - public static @Nullable T foreachChildWithResult(IWidget parent, Function consumer, - boolean includeSelf) { - if (includeSelf) { - T t = consumer.apply(parent); - if (t != null) return t; - } - if (!parent.hasChildren()) return null; - for (IWidget widget : parent.getChildren()) { - T t = consumer.apply(widget); - if (t != null) return t; - if (widget.hasChildren()) { - t = foreachChildWithResult(widget, consumer, false); - if (t != null) return t; - } - } - return null; - } - - public static boolean foreachChildReverse(IWidget parent, Predicate consumer, boolean includeSelf) { - if (parent.getChildren().isEmpty()) { - return !includeSelf || consumer.test(parent); - } - for (IWidget widget : parent.getChildren()) { - if (!widget.getChildren().isEmpty() && foreachChildReverse(widget, consumer, false)) { - return false; - } - if (!consumer.test(widget)) return false; - } - return !includeSelf || consumer.test(parent); - } - - /** - * Creates a flat stream of the whole sub widget tree. - *

- * {@link Stream#forEach(Consumer)} on this has slightly worse performance than - * {@link #foreachChildBFS(IWidget, Predicate, boolean)} on - * small widget trees and has similar performance on large widget trees. The performance is significantly better - * than - * {@link #iteratorBFS(IWidget)} even though this method uses it. - * - * @param parent starting point. - * @return stream of the sub widget tree - */ - @SuppressWarnings("UnstableApiUsage") - public static Stream flatStream(IWidget parent) { - if (!parent.hasChildren()) return Stream.of(parent); - return Streams.stream(iteratorBFS(parent)); - } - - @UnmodifiableView - public static Iterable iterableBFS(IWidget parent) { - return () -> iteratorBFS(parent); - } - - /** - * Creates an unmodifiable iterator of the whole sub widget tree. - *

- * This method of iterating has the worst performance in every case. It's roughly 4 times worse than - * {@link #foreachChildBFS(IWidget, Predicate, boolean)}. If not used extensively the performance is still nothing - * to worry about. - * - * @param parent starting point - * @return an unmodifiable iterator of the sub widget tree - */ - @UnmodifiableView - public static Iterator iteratorBFS(IWidget parent) { - return new AbstractIterator<>() { - - private final ObjectList queue = ObjectList.of(); - private Iterator currentIt; - - @Override - protected IWidget computeNext() { - if (currentIt == null) { - currentIt = parent.getChildren().iterator(); - return parent; - } - if (currentIt.hasNext()) return handleWidget(currentIt.next()); - while (!queue.isEmpty()) { - currentIt = queue.remove(0).getChildren().iterator(); - if (currentIt.hasNext()) return handleWidget(currentIt.next()); - } - return endOfData(); - } - - private IWidget handleWidget(IWidget widget) { - if (widget.hasChildren()) { - queue.add(widget); - } - return widget; - } - }; - } - - /** - * Finds all widgets in the sub widget tree which match the test. - * - * @param parent starting point - * @param test test which the target widgets have to pass - * @return a list of matching widgets - */ - public static List collectWidgets(IWidget parent, Predicate test) { - List widgets = new ArrayList<>(); - foreachChild(parent, w -> { - if (test.test(w)) widgets.add(w); - return true; - }, true); - return widgets; - } - - public static List collectWidgetsByType(IWidget parent, Class type) { - return collectWidgetsByType(parent, type, null); - } - - /** - * Finds all widgets in the sub widget tree which match the given type and additional test. - * - * @param parent starting point - * @param type type of the target widgets - * @param test test which the target widgets have to pass - * @param type of the target widgets - * @return a list of matching widgets - */ - @SuppressWarnings("unchecked") - public static List collectWidgetsByType(IWidget parent, Class type, - @Nullable Predicate test) { - List widgets = new ArrayList<>(); - foreachChild(parent, w -> { - if (w.isType(type)) { - T t = (T) w; - if (test == null || test.test(t)) widgets.add(t); - } - return true; - }, true); - return widgets; - } - - /** - * Finds the first widget in the sub widget tree, for which the test returns true. - * - * @param parent starting point - * @param test test which the widget has to pass - * @return the first matching widget - */ - public static IWidget findFirst(IWidget parent, @NotNull Predicate test) { - return foreachChildWithResult(parent, w -> { - if (test.test(w)) { - return w; - } - return null; - }, true); - } - - /** - * Finds the first widget in the sub widget tree with the given type, for which the test returns true. - * - * @param parent starting point - * @param type type of the target widget - * @param test test which the widget has to pass - * @return the first matching widget - */ - @SuppressWarnings("unchecked") - public static T findFirst(IWidget parent, Class type, @Nullable Predicate test) { - return foreachChildWithResult(parent, w -> { - if (w.isType(type)) { - T t = (T) w; - if (test == null || test.test(t)) { - return t; - } - } - return null; - }, true); - } - /** * Finds the first widget in the sub widget tree that matches the given name. * @@ -480,36 +185,6 @@ public static T findFirst(IWidget parent, Class type, @Nu return InternalWidgetTree.findChildAt(parent, type, path, 0, false); } - public static void applyPos(IWidget parent) { - WidgetTree.foreachChildBFS(parent, child -> { - child.resizer().applyPos(child); - return true; - }, true); - } - - public static IWidget findParent(IWidget parent, Predicate filter) { - if (parent == null) return null; - while (!(parent instanceof ModularPanel)) { - if (filter.test(parent)) { - return parent; - } - parent = parent.getParent(); - } - return filter.test(parent) ? parent : null; - } - - @SuppressWarnings("unchecked") - public static T findParent(IWidget parent, Class type) { - if (parent == null) return null; - while (!(parent instanceof ModularPanel)) { - if (type.isAssignableFrom(parent.getClass())) { - return (T) parent; - } - parent = parent.getParent(); - } - return type.isAssignableFrom(parent.getClass()) ? (T) parent : null; - } - public static boolean hasSyncedValues(ModularPanel panel) { return !foreachChild(panel, widget -> !(widget instanceof ISynced synced) || !synced.isSynced(), true); } @@ -577,157 +252,80 @@ public static void resize(IWidget parent) { } @ApiStatus.Internal - public static void resizeInternal(IWidget parent, boolean onOpen) { + public static void resizeInternal(ResizeNode parent, boolean onOpen) { if (!GTCEu.isClientThread()) return; - long fullTime = Util.getNanos(); + long time = Util.getNanos(); - while (!(parent instanceof ModularPanel) && (parent.getParent() instanceof ILayoutWidget || - parent.getParent().flex().dependsOnChildren())) { + while (parent.getParent() != null && + (parent.getParent().dependsOnChildren() || parent.getParent().isLayout())) { parent = parent.getParent(); } - long rawTime = Util.getNanos(); // resize each widget and calculate their relative pos - if (!InternalWidgetTree.resizeWidget(parent, true, onOpen, false) && - !InternalWidgetTree.resizeWidget(parent, false, onOpen, false)) { - if (MCHelper.getPlayer() != null) { - MCHelper.getPlayer().sendSystemMessage(Component.literal("MUI: Failed to resize sub tree of widget " + - parent + " of screen " + parent.getScreen().toString() + " '. See log for more info.")); + try { + if (!InternalWidgetTree.resize(parent, true, onOpen, false) && + !InternalWidgetTree.resize(parent, false, onOpen, false)) { + if (MCHelper.getPlayer() != null) { + MCHelper.getPlayer().sendSystemMessage(Component.literal("MUI: Failed to resize sub tree of " + + parent.getDebugDisplayName() + ". See log for more info.")); + } + GTCEu.LOGGER.error("Failed to resize widget. Affected resize node tree:"); + print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); } - GTCEu.LOGGER.error("Failed to resize widget. Affected widget tree:"); - printTree(parent, INFO_RESIZED_COLLAPSED); + // now apply the calculated pos + preApplyPos(parent); + applyPos(parent); + postFullResize(parent); + } catch (Throwable e) { + GTCEu.LOGGER.fatal("An exception was thrown while resizing widgets. Exception:"); + GTCEu.LOGGER.catching(e); + GTCEu.LOGGER.fatal("Affected node tree:"); + print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); } - rawTime = Util.getNanos() - rawTime; - // now apply the calculated pos - applyPos(parent); - WidgetTree.foreachChildBFS(parent, child -> { - child.postResize(); - return true; - }, true); if (WidgetTree.logResizeTime) { - fullTime = Util.getNanos() - fullTime; + time = Util.getNanos() - time; GTCEu.LOGGER.info("Resized widget tree in {} ns and {} ns for full resize.", - FormattingUtil.formatNumbers(rawTime), - FormattingUtil.formatNumbers(fullTime)); + FormattingUtil.formatNumbers(time), + FormattingUtil.formatNumbers(time)); } } - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need - * to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - */ - public static void printTree(IWidget parent) { - printTree(parent, w -> true, null); - } - - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need - * to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - * @param additionalInfo additional info function which is executed for each widget - */ - public static void printTree(IWidget parent, WidgetInfo additionalInfo) { - printTree(parent, w -> true, additionalInfo); - } - - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need - * to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - * @param test test widgets have to pass to be added to the string builder - */ - public static void printTree(IWidget parent, Predicate test) { - printTree(parent, test, null); - } - - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need - * to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - * @param test test widgets have to pass to be added to the string builder - * @param additionalInfo additional info function which is executed for each widget - */ - public static void printTree(IWidget parent, Predicate test, WidgetInfo additionalInfo) { - StringBuilder builder = new StringBuilder("Widget tree of ") - .append(parent).append('\n'); - GTCEu.LOGGER.info(widgetTreeToString(builder, parent, test, additionalInfo)); - } - - public static String widgetTreeToString(IWidget parent) { - return widgetTreeToString(parent, w -> true, null); - } - - public static String widgetTreeToString(IWidget parent, WidgetInfo additionalInfo) { - return widgetTreeToString(parent, w -> true, additionalInfo); + public static void preApplyPos(ResizeNode parent) { + parent.preApplyPos(); + for (ResizeNode resizeNode : parent.getChildren()) { + preApplyPos(resizeNode); + } } - public static String widgetTreeToString(IWidget parent, Predicate test) { - return widgetTreeToString(parent, test, null); + public static void applyPos(ResizeNode parent) { + parent.applyPos(); + for (ResizeNode resizeNode : parent.getChildren()) { + applyPos(resizeNode); + } } - public static String widgetTreeToString(IWidget parent, Predicate test, WidgetInfo additionalInfo) { - return widgetTreeToString(null, parent, test, additionalInfo).toString(); + public static void postFullResize(ResizeNode parent) { + parent.postFullResize(); + for (ResizeNode resizeNode : parent.getChildren()) { + postFullResize(resizeNode); + } } - /** - * Writes the sub widget tree into a human-readable tree graph with unicode characters. - * - * @param builder the string builder to add the tree to or null for a new builder - * @param parent starting point - * @param test test widgets have to pass to be added to the string builder - * @param additionalInfo additional info function which is executed for each widget - * @return the string builder which was used to build the graph - */ - public static StringBuilder widgetTreeToString(StringBuilder builder, IWidget parent, Predicate test, - WidgetInfo additionalInfo) { - if (builder == null) builder = new StringBuilder(); - InternalWidgetTree.getTree(parent, parent, test, builder, additionalInfo, "", false); - return builder; + public static void verifyTree(ResizeNode parent, Set visited) { + if (visited.contains(parent)) { + throw new IllegalStateException("Found cycling resize node dependencies!"); + } + visited.add(parent); + if (!parent.getChildren().isEmpty()) { + for (ResizeNode child : parent.getChildren()) { + verifyTree(child, visited); + } + } } /** * An interface to add information of a widget to a string builder. */ - public interface WidgetInfo { - - void addInfo(IWidget root, IWidget widget, StringBuilder builder); - - default WidgetInfo combine(WidgetInfo other, String joiner) { - return (root, widget, builder) -> { - addInfo(root, widget, builder); - builder.append(joiner); - other.addInfo(root, widget, builder); - }; - } - - default WidgetInfo combine(WidgetInfo other) { - return combine(other, " | "); - } - - static WidgetInfo of(String joiner, WidgetInfo... infos) { - return (root, widget, builder) -> { - for (int i = 0; i < infos.length; i++) { - WidgetInfo info = infos[i]; - info.addInfo(root, widget, builder); - if (i < infos.length - 1) { - builder.append(joiner); - } - } - }; - } - - static WidgetInfo of(WidgetInfo... infos) { - return of(" | ", infos); - } - } + public interface WidgetInfo extends NodeInfo {} } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Area.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Area.java index f9ed20e3146..f7f4f6b9ad0 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Area.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Area.java @@ -3,7 +3,6 @@ import com.gregtechceu.gtceu.api.mui.animation.IAnimatable; import com.gregtechceu.gtceu.api.mui.base.GuiAxis; import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; import com.gregtechceu.gtceu.api.mui.utils.Interpolations; import com.gregtechceu.gtceu.api.mui.utils.Point; import com.gregtechceu.gtceu.api.mui.utils.Rectangle; @@ -19,7 +18,7 @@ * A rectangular widget area, composed of a position and a size. * Also has fields for a relative position, a layer and margin & padding. */ -public class Area extends Rectangle implements IUnResizeable, IAnimatable { +public class Area extends Rectangle implements IAnimatable { public static boolean isInside(int x, int y, int w, int h, int px, int py) { SHARED.set(x, y, w, h); @@ -33,7 +32,8 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) { /** * relative position (in most cases the direct parent) */ - public int rx, ry; + public int rx; + public int ry; /** * the widget layer within this panel */ @@ -250,7 +250,7 @@ public boolean isInside(int x, int y) { /** * Check whether given point is inside the rect. - * Use {@link com.gregtechceu.gtceu.api.mui.base.widget.IWidget#isInside(IViewportStack, Point)} rather than + * Use {@link com.gregtechceu.gtceu.api.mui.base.widget.IWidget#isInside(IViewportStack, int, int)} rather than * this! */ public boolean isInside(Point point) { @@ -491,7 +491,7 @@ public void set(Rectangle area) { /** * Transforms the four corners of this rectangle with the given pose stack. The new rectangle can be rotated. - * Then a min fit rectangle, which is not rotated and aligned with the screen, is put around the corners. + * Then a min fit rectangle, which is aligned with the screen axis, is put around the corners. * * @param stack pose stack */ @@ -507,17 +507,6 @@ public void transformAndRectanglerize(IViewportStack stack) { setPos(x0, y0, x1, y1); } - @Override - public boolean resize(IGuiElement guiElement, boolean isParentLayout) { - guiElement.getArea().set(this); - return true; - } - - @Override - public Area getArea() { - return this; - } - /** * This creates a copy with size, pos, margin padding and z layer. * @@ -532,8 +521,10 @@ public String toString() { return "Area{" + "x=" + this.x + ", y=" + this.y + - ", width=" + this.width + - ", height=" + this.height + + ", w=" + this.width + + ", h=" + this.height + + ", rx=" + this.rx + + ", ry=" + this.ry + '}'; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/AreaResizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/AreaResizer.java new file mode 100644 index 00000000000..f11d4645566 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/AreaResizer.java @@ -0,0 +1,25 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +public class AreaResizer extends StaticResizer { + + private final Area area; + + public AreaResizer(Area area) { + this.area = area; + } + + @Override + public Area getArea() { + return area; + } + + @Override + public String getDebugDisplayName() { + return ""; + } + + @Override + public String toString() { + return "AreaResizer(" + this.area + ")"; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Bounds.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Bounds.java new file mode 100644 index 00000000000..67629178666 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Bounds.java @@ -0,0 +1,37 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import lombok.Getter; + +@Getter +public class Bounds { + + public static final int UNLIMITED_MAX = Integer.MAX_VALUE; + public static final int UNLIMITED_MIN = Integer.MIN_VALUE; + + private int minWidth = UNLIMITED_MIN, minHeight = UNLIMITED_MIN; + private int maxWidth = UNLIMITED_MAX, maxHeight = UNLIMITED_MAX; + + public Bounds set(int minWidth, int minHeight, int maxWidth, int maxHeight) { + this.minWidth = minWidth; + this.minHeight = minHeight; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + return this; + } + + public Bounds max(int width, int height) { + this.maxWidth = width; + this.maxHeight = height; + return this; + } + + public Bounds min(int width, int height) { + this.minWidth = width; + this.minHeight = height; + return this; + } + + public Bounds exact(int w, int h) { + return set(w, h, w, h); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DelegatingResizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DelegatingResizer.java new file mode 100644 index 00000000000..642bbe9311d --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DelegatingResizer.java @@ -0,0 +1,10 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +public class DelegatingResizer extends StandardResizer { + + public DelegatingResizer(IWidget widget) { + super(widget); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DimensionSizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DimensionSizer.java index 450d9055dda..5492e412be9 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DimensionSizer.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DimensionSizer.java @@ -3,8 +3,7 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.mui.GuiError; import com.gregtechceu.gtceu.api.mui.base.GuiAxis; -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.config.ConfigHolder; import lombok.Getter; @@ -20,6 +19,7 @@ @ApiStatus.Internal public class DimensionSizer { + private final ResizeNode resizer; private final GuiAxis axis; private final Unit p1 = new Unit(), p2 = new Unit(); @@ -38,7 +38,8 @@ public class DimensionSizer { private boolean marginPaddingApplied = false; private boolean canRelayout = false; - public DimensionSizer(GuiAxis axis) { + public DimensionSizer(ResizeNode resizer, GuiAxis axis) { + this.resizer = resizer; this.axis = axis; } @@ -79,7 +80,7 @@ public void resetSize() { } } - public void setCoverChildren(boolean coverChildren, IGuiElement widget) { + public void setCoverChildren(boolean coverChildren, IWidget widget) { getSize(widget); this.coverChildren = coverChildren; } @@ -131,9 +132,13 @@ public boolean dependsOnChildren() { } public boolean dependsOnParent() { - return !this.coverChildren && (this.end != null || + if (this.coverChildren) { + // if we cover children we ignore size config + return this.end != null || (this.start != null && this.start.isRelative()); + } + return this.end != null || (this.start != null && this.start.isRelative()) || - (this.size != null && this.size.isRelative())); + (this.size != null && this.size.isRelative()); } public void setResized(boolean all) { @@ -150,15 +155,20 @@ private boolean needsSize(Unit unit) { return unit.isRelative() && unit.getAnchor() != 0; } - public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { - // is already calculated - if (this.sizeCalculated && this.posCalculated) return; + public boolean test() { + return resizer != null && axis.isVertical() && resizer.toString().contains("menu_list"); + } + + public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { + boolean sizeCalculated = isSizeCalculated(); + boolean posCalculated = isPosCalculated(); + if (sizeCalculated && posCalculated) return; int p, s; int parentSize = relativeTo.getArea().getSize(this.axis); boolean calcParent = relativeTo.isSizeCalculated(this.axis); Box padding = relativeTo.getArea().getPadding(); - if (this.sizeCalculated && !this.posCalculated) { + if (sizeCalculated) { // pos not calculated // size was calculated before s = area.getSize(this.axis); if (this.start != null) { @@ -166,9 +176,10 @@ public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { } else if (this.end != null) { p = calcPoint(this.end, padding, s, parentSize, calcParent) - s; } else { - throw new IllegalStateException(); + p = 0; + this.posCalculated = true; } - } else if (!this.sizeCalculated && this.posCalculated) { + } else if (posCalculated) { // pos was calculated before p = area.getRelativePoint(this.axis); if (this.size != null) { @@ -177,7 +188,7 @@ public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { s = defaultSize.getAsInt(); this.sizeCalculated = s > 0; } - } else { + } else { // pos and size not calculated // calc start, end and size if (this.start == null && this.end == null) { p = 0; @@ -234,7 +245,7 @@ public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { s = Math.min(s, parentSize /*- padding.getTotal(this.axis)*/ - margin.getTotal(this.axis)); } area.setRelativePoint(this.axis, p); - area.setPoint(this.axis, p + relativeTo.getArea().x); // temporary + area.setPoint(this.axis, p + relativeTo.getArea().getPoint(this.axis)); // temporary area.setSize(this.axis, s); } @@ -280,41 +291,41 @@ public void coverChildrenForEmpty(Area area, Area relativeTo) { } } - public void applyMarginAndPaddingToPos(IGuiElement parent, Area area, Area relativeTo) { + public void applyMarginAndPaddingToPos(IWidget parent, Area area, Area relativeTo) { // apply self margin and parent padding if not done yet if (isMarginPaddingApplied()) return; setMarginPaddingApplied(true); - int left = area.getMargin().getStart(this.axis) + relativeTo.getPadding().getStart(this.axis); - int right = area.getMargin().getEnd(this.axis) + relativeTo.getPadding().getEnd(this.axis); - if (left > 0 && ((this.start != null && !this.start.isRelative()) || + int start = area.getMargin().getStart(this.axis) + relativeTo.getPadding().getStart(this.axis); + int end = area.getMargin().getEnd(this.axis) + relativeTo.getPadding().getEnd(this.axis); + if (start > 0 && ((this.start != null && !this.start.isRelative()) || (this.end != null && !this.end.isRelative() && (this.size == null || !this.size.isRelative())))) { - left = 0; + start = 0; } - if (right > 0 && ((this.end != null && !this.end.isRelative()) || + if (end > 0 && ((this.end != null && !this.end.isRelative()) || (this.start != null && !this.start.isRelative() && (this.size == null || !this.size.isRelative())))) { - right = 0; + end = 0; } - if (left == 0 && right == 0) return; + if (start == 0 && end == 0) return; int parentS = relativeTo.getSize(this.axis); int s = area.getSize(this.axis); int rp = area.getRelativePoint(this.axis); // relative pos - if (left > 0) { - if (right > 0) { - if (left + right + s > parentS) { + if (start > 0) { + if (end > 0) { + if (start + end + s > parentS) { // widget and margin + padding is larger than available space - area.setRelativePoint(this.axis, left); + area.setRelativePoint(this.axis, start); GuiError.throwNew(parent, GuiError.Type.SIZING, "Margin/padding is set on both sides on axis " + this.axis + ", but total size exceeds parent size."); return; } - if (right > parentS - s - rp) area.setRelativePoint(this.axis, parentS - right - s); - else if (left > rp) area.setRelativePoint(this.axis, left); + if (end > parentS - s - rp) area.setRelativePoint(this.axis, parentS - end - s); + else if (start > rp) area.setRelativePoint(this.axis, start); return; } - if (left > rp) area.setRelativePoint(this.axis, left); - } else if (right > 0) { - if (right > parentS - s - rp) area.setRelativePoint(this.axis, parentS - right - s); + if (start > rp) area.setRelativePoint(this.axis, start); + } else if (end > 0) { + if (end > parentS - s - rp) area.setRelativePoint(this.axis, parentS - end - s); } } @@ -356,7 +367,7 @@ public int calcPoint(Unit p, Box padding, int width, int parentSize, boolean par * @param newState the new unit type for the found unit * @return a used or unused unit. */ - private Unit getNext(IGuiElement widget, Unit.State newState) { + private Unit getNext(IWidget widget, Unit.State newState) { Unit ret = this.next; Unit other = ret == this.p1 ? this.p2 : this.p1; if (ret.state != Unit.State.UNUSED) { @@ -377,21 +388,21 @@ private Unit getNext(IGuiElement widget, Unit.State newState) { return ret; } - protected Unit getStart(IGuiElement widget) { + protected Unit getStart(IWidget widget) { if (this.start == null) { this.start = getNext(widget, Unit.State.START); } return this.start; } - protected Unit getEnd(IGuiElement widget) { + protected Unit getEnd(IWidget widget) { if (this.end == null) { this.end = getNext(widget, Unit.State.END); } return this.end; } - protected Unit getSize(IGuiElement widget) { + protected Unit getSize(IWidget widget) { if (this.size == null) { this.size = getNext(widget, Unit.State.SIZE); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ExpanderResizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ExpanderResizer.java new file mode 100644 index 00000000000..e7ccf8a398c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ExpanderResizer.java @@ -0,0 +1,20 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.widgets.layout.IExpander; + +public class ExpanderResizer extends StandardResizer implements IExpander { + + private final GuiAxis axis; + + public ExpanderResizer(IWidget widget, GuiAxis axis) { + super(widget); + this.axis = axis; + } + + @Override + public GuiAxis getExpandAxis() { + return axis; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/IUnResizeable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/IUnResizeable.java deleted file mode 100644 index 17ddc964a0b..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/IUnResizeable.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.gregtechceu.gtceu.api.mui.widget.sizer; - -import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; - -/** - * A variation of {@link IResizeable} with default implementations which don't do anything - */ -public interface IUnResizeable extends IResizeable { - - IUnResizeable INSTANCE = new IUnResizeable() { - - @Override - public boolean resize(IGuiElement guiElement, boolean isParentLayout) { - return true; - } - - @Override - public Area getArea() { - Area.SHARED.set(0, 0, 0, 0); - return Area.SHARED; - } - }; - - @Override - default void initResizing() {} - - @Override - default boolean postResize(IGuiElement guiElement) { - return true; - } - - @Override - default boolean isXCalculated() { - return true; - } - - @Override - default boolean isYCalculated() { - return true; - } - - @Override - default boolean isWidthCalculated() { - return true; - } - - @Override - default boolean isHeightCalculated() { - return true; - } - - @Override - default boolean areChildrenCalculated() { - return true; - } - - @Override - default boolean isLayoutDone() { - return true; - } - - @Override - default boolean canRelayout(boolean isParentLayout) { - return false; - } - - @Override - default void setChildrenResized(boolean resized) {} - - @Override - default void setLayoutDone(boolean done) {} - - @Override - default void setResized(boolean x, boolean y, boolean w, boolean h) {} - - @Override - default void setXMarginPaddingApplied(boolean b) {} - - @Override - default void setYMarginPaddingApplied(boolean b) {} - - @Override - default boolean isXMarginPaddingApplied() { - return true; - } - - @Override - default boolean isYMarginPaddingApplied() { - return true; - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ResizeNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ResizeNode.java new file mode 100644 index 00000000000..6911d182393 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ResizeNode.java @@ -0,0 +1,251 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.ITreeNode; +import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public abstract class ResizeNode implements IResizeable, ITreeNode { + + private ResizeNode defaultParent; + private ResizeNode parentOverride; + private final List children = new ArrayList<>(); + private boolean defaultParentIsDelegating = false; + private boolean requiresResize = true; + + @ApiStatus.Internal + @Override + public List getChildren() { + return children; + } + + @Override + public ResizeNode getParent() { + return parentOverride != null ? parentOverride : defaultParent; + } + + @ApiStatus.Internal + public void replacementOf(ResizeNode node) { + if (this == node) return; + // GTCEu.LOGGER.info("Replacing resizer node {} with node {}", node, this); + // remove this node from the tree by removing itself from the parents and removing the children + if (this.defaultParent != null) this.defaultParent.children.remove(this); + if (this.parentOverride != null) this.parentOverride.children.remove(this); + for (ResizeNode n : this.children) { + if (n.parentOverride == this) { + n.setParentOverride(null); + } + if (n.defaultParent == this) { + n.setDefaultParent(null); + } + } + this.children.clear(); + + // remove the node to replace from its tree and remember the exact position + int defI = -1; + int ovrI = -1; + if (node.defaultParent != null) { + defI = node.defaultParent.children.indexOf(node); + if (defI >= 0) node.defaultParent.children.remove(defI); + } + if (node.parentOverride != null) { + ovrI = node.parentOverride.children.indexOf(node); + if (ovrI >= 0) node.parentOverride.children.remove(ovrI); + } + // take over the parent and ourselves to the new parents with the remembered position + this.defaultParent = node.defaultParent; + this.parentOverride = node.parentOverride; + if (this.parentOverride != null) { + if (ovrI < 0) throw new IllegalStateException(); + this.parentOverride.children.add(ovrI, this); + } + if (this.defaultParent != null) { + if (defI < 0) throw new IllegalStateException(); + this.defaultParent.children.add(ovrI, this); + } + // take all children and update their parent to ourselves + this.children.addAll(node.children); + for (ResizeNode n : this.children) { + if (n.parentOverride == node) n.parentOverride = this; + if (n.defaultParent == node) n.defaultParent = this; + } + // finally invalidate replaced node + node.dispose(); + } + + public void dispose() { + if (getParent() != null) getParent().children.remove(this); + this.defaultParent = null; + this.parentOverride = null; + this.children.clear(); + } + + private boolean removeFromParent(ResizeNode parent, ResizeNode parent2, ResizeNode replacement) { + if (parent != null) { + if (parent == replacement) return true; + parent.children.remove(this); + } else if (parent2 != null) { + if (parent2 == replacement) return true; + parent2.children.remove(this); + } + return false; + } + + @ApiStatus.Internal + public void initialize(ResizeNode defaultParent, ResizeNode root) { + setDefaultParent(defaultParent); + } + + @ApiStatus.Internal + public void setDefaultParent(ResizeNode resizeNode) { + // GTCEu.LOGGER.info("Set default parent of {} to {}. Current: default: {}, override: {}", this, resizeNode, + // this.defaultParent, this.parentOverride); + if (resizeNode == this) throw new IllegalArgumentException("Tried to set itself as default parent in " + this); + if (removeFromParent(this.defaultParent, null, resizeNode)) return; + this.defaultParent = resizeNode; + if (this.parentOverride == null && resizeNode != null) { + resizeNode.children.add(this); + } + } + + protected void setParentOverride(ResizeNode resizeNode) { + // GTCEu.LOGGER.info("Set override parent of {} to {}. Current: default: {}, override: {}", this, resizeNode, + // this.defaultParent, this.parentOverride); + if (resizeNode == this) throw new IllegalArgumentException("Tried to set itself as parent override in " + this); + if (removeFromParent(this.parentOverride, this.defaultParent, resizeNode)) return; + this.parentOverride = resizeNode; + if (this.parentOverride != null) { + this.parentOverride.children.add(this); + } else if (this.defaultParent != null) { + this.defaultParent.children.add(this); + } + } + + @ApiStatus.Internal + public void setDefaultParentIsDelegating(boolean defaultParentIsDelegating) { + this.defaultParentIsDelegating = defaultParentIsDelegating; + } + + public boolean hasParentOverride() { + return this.parentOverride != null; + } + + @Override + public void initResizing(boolean onOpen) { + if (this.defaultParentIsDelegating && this.parentOverride != null) { + this.defaultParent.initResizing(onOpen); + } + } + + public void reset() {} + + public void markDirty() { + this.requiresResize = true; + } + + public void onResized() { + this.requiresResize = false; + if (this.defaultParentIsDelegating && this.parentOverride != null) { + this.defaultParent.onResized(); + } + } + + public void postFullResize() { + if (this.defaultParentIsDelegating && this.parentOverride != null) { + this.defaultParent.postFullResize(); + } + } + + public boolean requiresResize() { + return this.requiresResize; + } + + public boolean dependsOnParentX() { + return false; + } + + public boolean dependsOnParentY() { + return false; + } + + public boolean dependsOnParent() { + return dependsOnParentX() || dependsOnParentY(); + } + + public boolean dependsOnParent(GuiAxis axis) { + return axis.isHorizontal() ? dependsOnParentX() : dependsOnParentY(); + } + + public boolean dependsOnChildrenX() { + return false; + } + + public boolean dependsOnChildrenY() { + return false; + } + + public boolean dependsOnChildren() { + return dependsOnChildrenX() || dependsOnChildrenY(); + } + + public boolean dependsOnChildren(GuiAxis axis) { + return axis.isHorizontal() ? dependsOnChildrenX() : dependsOnChildrenY(); + } + + public boolean isSameResizer(ResizeNode node) { + return node == this; + } + + public boolean isLayout() { + return false; + } + + public boolean layoutChildren() { + return true; + } + + public boolean postLayoutChildren() { + return true; + } + + @ApiStatus.Internal + public void checkExpanded(@Nullable GuiAxis axis) {} + + public abstract boolean hasYPos(); + + public abstract boolean hasXPos(); + + public abstract boolean hasHeight(); + + public abstract boolean hasWidth(); + + public abstract boolean hasStartPos(GuiAxis axis); + + public abstract boolean hasEndPos(GuiAxis axis); + + public boolean hasPos(GuiAxis axis) { + return axis.isHorizontal() ? hasXPos() : hasYPos(); + } + + public boolean hasSize(GuiAxis axis) { + return axis.isHorizontal() ? hasWidth() : hasHeight(); + } + + public boolean isExpanded() { + return false; + } + + public abstract boolean isFullSize(); + + public abstract boolean hasFixedSize(); + + public abstract String getDebugDisplayName(); + + @Override + public abstract String toString(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ScreenResizeNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ScreenResizeNode.java new file mode 100644 index 00000000000..e088ce99789 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/ScreenResizeNode.java @@ -0,0 +1,31 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; + +public class ScreenResizeNode extends StaticResizer { + + private final ModularScreen screen; + + public ScreenResizeNode(ModularScreen screen) { + this.screen = screen; + } + + public ModularScreen getScreen() { + return screen; + } + + @Override + public Area getArea() { + return screen.getScreenArea(); + } + + @Override + public String getDebugDisplayName() { + return "screen '" + this.screen + "'"; + } + + @Override + public String toString() { + return "ScreenResizeNode(" + this.screen + ")"; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Flex.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java similarity index 65% rename from src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Flex.java rename to src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java index 3b7dea00919..67bb6a26312 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Flex.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java @@ -4,15 +4,10 @@ import com.gregtechceu.gtceu.api.mui.base.GuiAxis; import com.gregtechceu.gtceu.api.mui.base.layout.ILayoutWidget; import com.gregtechceu.gtceu.api.mui.base.layout.IResizeable; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; -import com.gregtechceu.gtceu.api.mui.base.widget.IPositioned; -import com.gregtechceu.gtceu.api.mui.base.widget.IVanillaSlot; -import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.base.widget.*; import com.gregtechceu.gtceu.api.mui.utils.Alignment; import com.gregtechceu.gtceu.core.mixins.client.SlotAccessor; -import lombok.Getter; -import lombok.Setter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -22,60 +17,52 @@ /** * This class handles resizing and positioning of widgets. */ -public class Flex implements IResizeable, IPositioned { +public class StandardResizer extends WidgetResizeNode implements IPositioned { private final DimensionSizer x; private final DimensionSizer y; - @Getter private boolean expanded = false; - private final IGuiElement parent; - private Area relativeTo; - private boolean relativeToParent = true; - private boolean bypassLayerRestriction = false; - - private boolean childrenCalculated = false; - @Setter - @Getter - private boolean layoutDone = true; - - public Flex(IGuiElement parent) { - this.parent = parent; + + private boolean childrenResized = false; + private boolean layoutResized = false; + private boolean relativeToScreen = false; + + public StandardResizer(IWidget widget) { + super(widget); this.x = createDimensionSizer(GuiAxis.X); this.y = createDimensionSizer(GuiAxis.Y); } protected DimensionSizer createDimensionSizer(GuiAxis axis) { - return new DimensionSizer(axis); - } - - public void reset() { - this.x.reset(); - this.y.reset(); - } - - public void resetPosition() { - this.x.resetPosition(); - this.y.resetPosition(); + return new DimensionSizer(this, axis); } @Override - public Flex flex() { - return this; + public void initialize(ResizeNode defaultParent, ResizeNode root) { + super.initialize(defaultParent, root); + if (this.relativeToScreen) { + setParentOverride(root); + } } @Override - public Area getArea() { - return this.parent.getArea(); + public void initResizing(boolean onOpen) { + setMarginPaddingApplied(false); + setResized(false); + this.childrenResized = false; + this.layoutResized = false; + super.initResizing(onOpen); } @Override - public boolean requiresResize() { - return this.parent.requiresResize(); + public void reset() { + this.x.reset(); + this.y.reset(); } - @Override - public void scheduleResize() { - this.parent.scheduleResize(); + public void resetPosition() { + this.x.resetPosition(); + this.y.resetPosition(); } @Override @@ -100,282 +87,41 @@ public boolean isHeightCalculated() { @Override public boolean areChildrenCalculated() { - return this.childrenCalculated; + return this.childrenResized; } @Override - public boolean canRelayout(boolean isParentLayout) { - return isParentLayout && (this.x.canRelayout() || this.y.canRelayout()); + public boolean isLayoutDone() { + return this.layoutResized; } @Override - public void setChildrenResized(boolean resized) { - this.childrenCalculated = resized; - } - - public Flex coverChildrenWidth() { - this.x.setCoverChildren(true, this.parent); - scheduleResize(); - return this; - } - - public Flex coverChildrenHeight() { - this.y.setCoverChildren(true, this.parent); - scheduleResize(); - return this; - } - - public Flex cancelMovementX() { - this.x.setCancelAutoMovement(true); - scheduleResize(); - return this; - } - - public Flex cancelMovementY() { - this.y.setCancelAutoMovement(true); - scheduleResize(); - return this; - } - - public Flex expanded() { - this.expanded = true; - scheduleResize(); - return this; - } - - public Flex relative(Area guiElement) { - this.relativeTo = guiElement; - this.relativeToParent = false; - scheduleResize(); - return this; - } - - public Flex relativeToScreen() { - this.relativeTo = null; - this.relativeToParent = false; - scheduleResize(); - return this; - } - - public Flex relativeToParent() { - this.relativeToParent = true; - scheduleResize(); - return this; + public boolean canRelayout(boolean isParentLayout) { + return isParentLayout && (this.x.canRelayout() || this.y.canRelayout()); } @Override - public Flex bypassLayerRestriction() { - this.bypassLayerRestriction = true; - scheduleResize(); - return this; - } - - @ApiStatus.Internal - public Flex left(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getLeft(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex left(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getLeft(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex right(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getRight(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex right(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getRight(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex top(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getTop(), y, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex top(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getTop(), y, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex bottom(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getBottom(), y, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex bottom(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getBottom(), y, offset, anchor, measure, autoAnchor); - } - - private Flex unit(Unit u, float val, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - u.setValue(val); - u.setMeasure(measure); - u.setOffset(offset); - u.setAnchor(anchor); - u.setAutoAnchor(autoAnchor); - scheduleResize(); - return this; - } - - private Flex unit(Unit u, DoubleSupplier val, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - u.setValue(val); - u.setMeasure(measure); - u.setOffset(offset); - u.setAnchor(anchor); - u.setAutoAnchor(autoAnchor); - scheduleResize(); - return this; - } - - @ApiStatus.Internal - public Flex width(float val, int offset, Unit.Measure measure) { - return unitSize(getWidth(), val, offset, measure); - } - - @ApiStatus.Internal - public Flex width(DoubleSupplier val, int offset, Unit.Measure measure) { - return unitSize(getWidth(), val, offset, measure); - } - - @ApiStatus.Internal - public Flex height(float val, int offset, Unit.Measure measure) { - return unitSize(getHeight(), val, offset, measure); - } - - @ApiStatus.Internal - public Flex height(DoubleSupplier val, int offset, Unit.Measure measure) { - return unitSize(getHeight(), val, offset, measure); - } - - private Flex unitSize(Unit u, float val, int offset, Unit.Measure measure) { - u.setValue(val); - u.setMeasure(measure); - u.setOffset(offset); - scheduleResize(); - return this; - } - - private Flex unitSize(Unit u, DoubleSupplier val, int offset, Unit.Measure measure) { - u.setValue(val); - u.setMeasure(measure); - u.setOffset(offset); - scheduleResize(); - return this; - } - - public Flex anchorLeft(float val) { - getLeft().setAnchor(val); - getLeft().setAutoAnchor(false); - scheduleResize(); - return this; - } - - public Flex anchorRight(float val) { - getRight().setAnchor(1 - val); - getRight().setAutoAnchor(false); - scheduleResize(); - return this; - } - - public Flex anchorTop(float val) { - getTop().setAnchor(val); - getTop().setAutoAnchor(false); - scheduleResize(); - return this; + public boolean isXMarginPaddingApplied() { + return this.x.isMarginPaddingApplied(); } - public Flex anchorBottom(float val) { - getBottom().setAnchor(1 - val); - getBottom().setAutoAnchor(false); - scheduleResize(); - return this; + @Override + public boolean isYMarginPaddingApplied() { + return this.y.isMarginPaddingApplied(); } - public Flex anchor(Alignment alignment) { - if (this.x.hasStart() || !this.x.hasEnd()) { - anchorLeft(alignment.x); - } else if (this.x.hasEnd()) { - anchorRight(alignment.x); - } - if (this.y.hasStart() || !this.y.hasEnd()) { - anchorTop(alignment.y); - } else if (this.y.hasEnd()) { - anchorBottom(alignment.y); - } + @Override + public StandardResizer resizer() { return this; } - public void setUnit(Unit unit, GuiAxis axis, Unit.State pos) { - (axis.isHorizontal() ? this.x : this.y).setUnit(unit, pos); - } - - private IResizeable getRelativeTo() { - IGuiElement parent = this.parent.getParent(); - IResizeable relativeTo = this.relativeToParent && parent != null ? parent.resizer() : this.relativeTo; - return relativeTo != null ? relativeTo : this.parent.getScreen().getScreenArea(); - } - - public boolean hasYPos() { - return this.y.hasPos(); - } - - public boolean hasXPos() { - return this.x.hasPos(); - } - - public boolean hasHeight() { - return this.y.hasSize(); - } - - public boolean hasWidth() { - return this.x.hasSize(); - } - - public boolean hasStartPos(GuiAxis axis) { - return axis.isHorizontal() ? this.x.hasStart() : this.y.hasStart(); - } - - public boolean hasEndPos(GuiAxis axis) { - return axis.isHorizontal() ? this.x.hasEnd() : this.y.hasEnd(); - } - - public boolean hasPos(GuiAxis axis) { - return axis.isHorizontal() ? hasXPos() : hasYPos(); - } - - public boolean hasSize(GuiAxis axis) { - return axis.isHorizontal() ? hasWidth() : hasHeight(); - } - - public boolean xAxisDependsOnChildren() { - return this.x.dependsOnChildren(); - } - - public boolean yAxisDependsOnChildren() { - return this.y.dependsOnChildren(); - } - - public boolean dependsOnChildren() { - return xAxisDependsOnChildren() || yAxisDependsOnChildren(); - } - - public boolean dependsOnChildren(GuiAxis axis) { - return axis.isHorizontal() ? xAxisDependsOnChildren() : yAxisDependsOnChildren(); - } - - public boolean hasFixedSize() { - return this.x.hasFixedSize() && this.y.hasFixedSize(); - } - - public boolean isFullSize() { - if (!hasHeight() || !hasWidth()) return false; - return this.x.hasFixedSize() && this.y.hasFixedSize(); + @Override + public void scheduleResize() { + markDirty(); } @ApiStatus.Internal + @Override public void checkExpanded(@Nullable GuiAxis axis) { this.x.setExpanded(false); this.y.setExpanded(false); @@ -386,72 +132,26 @@ public void checkExpanded(@Nullable GuiAxis axis) { } @Override - public void initResizing() { - setMarginPaddingApplied(false); - setResized(false); - this.childrenCalculated = false; - this.layoutDone = false; - } - - @Override - public void setResized(boolean x, boolean y, boolean w, boolean h) { - this.x.setResized(x, w); - this.y.setResized(y, h); - } - - @Override - public void setXMarginPaddingApplied(boolean b) { - this.x.setMarginPaddingApplied(b); - } - - @Override - public void setYMarginPaddingApplied(boolean b) { - this.y.setMarginPaddingApplied(b); - } - - @Override - public boolean isXMarginPaddingApplied() { - return this.x.isMarginPaddingApplied(); - } - - @Override - public boolean isYMarginPaddingApplied() { - return this.y.isMarginPaddingApplied(); - } - - @Override - public boolean resize(IGuiElement guiElement, boolean isParentLayout) { - IResizeable relativeTo = getRelativeTo(); - Area relativeArea = relativeTo.getArea(); - - /* - * if (!this.bypassLayerRestriction && relativeArea.z() >= this.parent.getArea().z()) { - * Area area = guiElement.getArea(); - * area.setSize(18, 18); - * area.rx = 0; - * area.ry = 0; - * guiElement.resizer().setResized(true); - * GuiError.throwNew(this.parent, GuiError.Type.SIZING, - * "Widget can't be relative to a widget at the same level or above"); - * return true; - * } - */ + public boolean resize(boolean isParentLayout) { + Area area = getArea(); + ResizeNode relativeTo = getParent(); // calculate x, y, width and height if possible - this.x.apply(guiElement.getArea(), relativeTo, guiElement::getDefaultWidth); - this.y.apply(guiElement.getArea(), relativeTo, guiElement::getDefaultHeight); + this.x.apply(area, relativeTo, () -> getWidget().getDefaultWidth()); + this.y.apply(area, relativeTo, () -> getWidget().getDefaultHeight()); return isFullyCalculated(isParentLayout); } @Override - public boolean postResize(IGuiElement guiElement) { + public boolean postResize() { boolean coverWidth = this.x.dependsOnChildren(); boolean coverHeight = this.y.dependsOnChildren(); if (!coverWidth && !coverHeight) return isSelfFullyCalculated(); - if (!(this.parent instanceof IWidget widget) || !widget.hasChildren()) { + IWidget widget = getWidget(); + if (!widget.hasChildren()) { coverChildrenForEmpty(); return isSelfFullyCalculated(); } - if (this.parent instanceof ILayoutWidget layout) { + if (getWidget() instanceof ILayoutWidget layout) { // layout widgets handle widget layout's themselves, so we only need to fit the right and bottom border coverChildrenForLayout(layout, widget); return isSelfFullyCalculated(); @@ -461,10 +161,10 @@ public boolean postResize(IGuiElement guiElement) { // this means for each edge there is at least one widget that touches it (plus padding and margin) // children are now calculated and now this area can be calculated if it requires children's area - List children = widget.getChildren(); + List children = widget.getChildren(); // TODO cover resizer children instead? int moveChildrenX = 0, moveChildrenY = 0; - Box padding = this.parent.getArea().getPadding(); + Box padding = getWidget().getArea().getPadding(); // first calculate the area the children span int x0 = Integer.MAX_VALUE, x1 = Integer.MIN_VALUE, y0 = Integer.MAX_VALUE, y1 = Integer.MIN_VALUE; int w = 0, h = 0; @@ -472,15 +172,13 @@ public boolean postResize(IGuiElement guiElement) { boolean hasIndependentChildY = false; for (IWidget child : children) { Box margin = child.getArea().getMargin(); - IResizeable resizeable = child.resizer(); + ResizeNode resizeable = child.resizer(); Area area = child.getArea(); if (coverWidth) { - if (!child.flex().x.dependsOnParent()) { + if (!resizeable.dependsOnParentX()) { hasIndependentChildX = true; - if (resizeable.isWidthCalculated() && resizeable.isXCalculated()) { w = Math.max(w, area.requestedWidth() + padding.horizontal()); - // if pos is calculated use that x0 = Math.min(x0, area.rx - padding.left() - margin.left()); x1 = Math.max(x1, area.rx + area.width + padding.right + margin.right); } else { @@ -489,13 +187,12 @@ public boolean postResize(IGuiElement guiElement) { } } if (coverHeight) { - if (!child.flex().y.dependsOnParent()) { + if (!resizeable.dependsOnParentY()) { hasIndependentChildY = true; if (resizeable.isHeightCalculated() && resizeable.isYCalculated()) { h = Math.max(h, area.requestedHeight() + padding.vertical()); y0 = Math.min(y0, area.ry - padding.top() - margin.top()); y1 = Math.max(y1, area.ry + area.height + padding.bottom + margin.bottom); - } else { return isSelfFullyCalculated(); } @@ -503,7 +200,7 @@ public boolean postResize(IGuiElement guiElement) { } } if ((coverWidth && !hasIndependentChildX) || (coverHeight && !hasIndependentChildY)) { - GuiError.throwNew(this.parent, GuiError.Type.SIZING, + GuiError.throwNew(getWidget(), GuiError.Type.SIZING, "Can't cover children when all children depend on their parent!"); return false; } @@ -511,26 +208,26 @@ public boolean postResize(IGuiElement guiElement) { if (y1 == Integer.MIN_VALUE) y1 = 0; if (x0 == Integer.MAX_VALUE) x0 = 0; if (y0 == Integer.MAX_VALUE) y0 = 0; - // we found at least one widget which was wider than what was calculated by start and end pos - if (w > x1 - x0) x1 = x0 + w; + if (w > x1 - x0) x1 = x0 + w; // we found at least one widget which was wider than what was calculated by start + // and end pos if (h > y1 - y0) y1 = y0 + h; // now calculate new x, y, width and height based on the children area - Area relativeTo = getRelativeTo().getArea(); + Area relativeTo = getParent().getArea(); if (coverWidth) { // apply the size to this widget // the return value is the amount of pixels we need to move the children - moveChildrenX = this.x.postApply(this.parent.getArea(), relativeTo, x0, x1); + moveChildrenX = this.x.postApply(getWidget().getArea(), relativeTo, x0, x1); } if (coverHeight) { - moveChildrenY = this.y.postApply(this.parent.getArea(), relativeTo, y0, y1); + moveChildrenY = this.y.postApply(getWidget().getArea(), relativeTo, y0, y1); } // since the edges might have been moved closer to the widgets, the widgets should move back into it's original // (absolute) position if (moveChildrenX != 0 || moveChildrenY != 0) { for (IWidget child : children) { Area area = child.getArea(); - IResizeable resizeable = child.resizer(); + ResizeNode resizeable = child.resizer(); if (resizeable.isXCalculated()) area.rx += moveChildrenX; if (resizeable.isYCalculated()) area.ry += moveChildrenY; } @@ -540,7 +237,7 @@ public boolean postResize(IGuiElement guiElement) { private void coverChildrenForLayout(ILayoutWidget layout, IWidget widget) { List children = widget.getChildren(); - Box padding = this.parent.getArea().getPadding(); + Box padding = getWidget().getArea().getPadding(); // first calculate the area the children span int x1 = Integer.MIN_VALUE, y1 = Integer.MIN_VALUE; int w = 0, h = 0; @@ -557,7 +254,7 @@ private void coverChildrenForLayout(ILayoutWidget layout, IWidget widget) { Box margin = area.getMargin(); IResizeable resizeable = child.resizer(); if (coverWidth) { - if (!child.flex().x.dependsOnParent()) { + if (!child.resizer().dependsOnParentX()) { hasIndependentChildX = true; if (resizeable.isWidthCalculated() && resizeable.isXCalculated()) { int s = area.requestedWidth() + padding.horizontal(); @@ -572,8 +269,9 @@ private void coverChildrenForLayout(ILayoutWidget layout, IWidget widget) { child.getDefaultWidth() + margin.horizontal() + padding.horizontal()); } } + if (coverHeight) { - if (!child.flex().y.dependsOnParent()) { + if (!child.resizer().dependsOnParentY()) { hasIndependentChildY = true; if (resizeable.isHeightCalculated() && resizeable.isYCalculated()) { int s = area.requestedHeight() + padding.vertical(); @@ -591,80 +289,369 @@ private void coverChildrenForLayout(ILayoutWidget layout, IWidget widget) { } if ((coverWidth && !hasIndependentChildX && !coverByDefaultSizeX) || (coverHeight && !hasIndependentChildY && !coverByDefaultSizeY)) { - GuiError.throwNew(this.parent, GuiError.Type.SIZING, + GuiError.throwNew(getWidget(), GuiError.Type.SIZING, "Can't cover children when all children depend on their parent!"); return; } - // only use default sizes, if no size is defined - if (w == 0) w = withDefaultW; + if (w == 0) w = withDefaultW; // only use default sizes, if no size is defined if (h == 0) h = withDefaultH; if (x1 == Integer.MIN_VALUE) x1 = 0; if (y1 == Integer.MIN_VALUE) y1 = 0; if (w > x1) x1 = w; if (h > y1) y1 = h; - Area relativeTo = getRelativeTo().getArea(); - if (coverWidth) { - this.x.postApply(getArea(), relativeTo, 0, x1); - } - if (coverHeight) { - this.y.postApply(getArea(), relativeTo, 0, y1); - } + Area relativeTo = getParent().getArea(); + if (coverWidth) this.x.postApply(getArea(), relativeTo, 0, x1); + if (coverHeight) this.y.postApply(getArea(), relativeTo, 0, y1); } private void coverChildrenForEmpty() { if (this.x.dependsOnChildren()) { - this.x.coverChildrenForEmpty(this.parent.getArea(), getRelativeTo().getArea()); + this.x.coverChildrenForEmpty(getWidget().getArea(), getParent().getArea()); } if (this.y.dependsOnChildren()) { - this.y.coverChildrenForEmpty(this.parent.getArea(), getRelativeTo().getArea()); + this.y.coverChildrenForEmpty(getWidget().getArea(), getParent().getArea()); } } @Override - public void applyPos(IGuiElement parent) { - Area relativeTo = getRelativeTo().getArea(); - Area area = parent.getArea(); + public void preApplyPos() { + IWidget widget = getWidget(); + Area relativeTo = getParent().getArea(); + Area area = widget.getArea(); // apply margin and padding if not done yet - this.x.applyMarginAndPaddingToPos(parent, area, relativeTo); - this.y.applyMarginAndPaddingToPos(parent, area, relativeTo); + this.x.applyMarginAndPaddingToPos(widget, area, relativeTo); + this.y.applyMarginAndPaddingToPos(widget, area, relativeTo); // after all widgets x, y, width and height have been calculated we can now calculate the absolute position area.applyPos(relativeTo.x, relativeTo.y); - Area parentArea = parent.getParentArea(); + } + + @Override + public void applyPos() { + IWidget widget = getWidget(); + if (widget instanceof IDelegatingWidget dw && dw.getDelegate() != null) { + super.postFullResize(); + return; + } + Area area = widget.getArea(); + // update rx and ry to be relative to the widget parent not the resize node parent + Area parentArea = widget.getParentArea(); area.rx = area.x - parentArea.x; area.ry = area.y - parentArea.y; - if (parent instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot()) { + if (widget instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot()) { // special treatment for minecraft slots SlotAccessor slot = (SlotAccessor) vanillaSlot.getVanillaSlot(); - Area mainArea = parent.getScreen().getMainPanel().getArea(); + Area mainArea = widget.getScreen().getMainPanel().getArea(); // in vanilla uis the position is relative to the gui area and size is 16 x 16 // since our slots are 18 x 18 we need to offset by 1 - slot.gtceu$setX(parent.getArea().x - mainArea.x + 1); - slot.gtceu$setY(parent.getArea().y - mainArea.y + 1); + slot.gtceu$setX(widget.getArea().x - mainArea.x + 1); + slot.gtceu$setY(widget.getArea().y - mainArea.y + 1); + } + } + + @Override + public void setChildrenResized(boolean resized) { + this.childrenResized = resized; + } + + @Override + public void setLayoutDone(boolean done) { + this.layoutResized = done; + } + + @Override + public void setResized(boolean x, boolean y, boolean w, boolean h) { + this.x.setResized(x, w); + this.y.setResized(y, h); + } + + @Override + public void setXMarginPaddingApplied(boolean b) { + this.x.setMarginPaddingApplied(b); + } + + @Override + public void setYMarginPaddingApplied(boolean b) { + this.y.setMarginPaddingApplied(b); + } + + @Override + public boolean hasYPos() { + return this.y.hasPos(); + } + + @Override + public boolean hasXPos() { + return this.x.hasPos(); + } + + @Override + public boolean hasHeight() { + return this.y.hasSize(); + } + + @Override + public boolean hasWidth() { + return this.x.hasSize(); + } + + @Override + public boolean hasStartPos(GuiAxis axis) { + return axis.isHorizontal() ? this.x.hasStart() : this.y.hasStart(); + } + + @Override + public boolean hasEndPos(GuiAxis axis) { + return axis.isHorizontal() ? this.x.hasEnd() : this.y.hasEnd(); + } + + @Override + public boolean dependsOnParentX() { + return this.x.dependsOnParent(); + } + + @Override + public boolean dependsOnParentY() { + return this.y.dependsOnParent(); + } + + @Override + public boolean dependsOnChildrenX() { + return this.x.dependsOnChildren(); + } + + @Override + public boolean dependsOnChildrenY() { + return this.y.dependsOnChildren(); + } + + public StandardResizer expanded() { + this.expanded = true; + scheduleResize(); + return this; + } + + @Override + public boolean isExpanded() { + return this.expanded; + } + + @Override + public boolean hasFixedSize() { + return this.x.hasFixedSize() && this.y.hasFixedSize(); + } + + @Override + public boolean isFullSize() { + if (!hasHeight() || !hasWidth()) return false; + return this.x.isFullSize() && this.y.isFullSize(); + } + + @Override + public StandardResizer relative(ResizeNode resizeNode) { + setParentOverride(resizeNode); + this.relativeToScreen = false; + return this; + } + + @Override + public StandardResizer relativeToParent() { + setParentOverride(null); + this.relativeToScreen = false; + return this; + } + + @Override + public StandardResizer relativeToScreen() { + this.relativeToScreen = true; + return this; + } + + @Override + public StandardResizer coverChildren() { + this.x.setCoverChildren(true, getWidget()); + this.y.setCoverChildren(true, getWidget()); + return this; + } + + @Override + public StandardResizer coverChildrenWidth() { + this.x.setCoverChildren(true, getWidget()); + return this; + } + + @Override + public StandardResizer coverChildrenHeight() { + this.y.setCoverChildren(true, getWidget()); + return this; + } + + @ApiStatus.Internal + public StandardResizer left(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getLeft(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer left(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getLeft(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer right(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getRight(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer right(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getRight(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer top(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getTop(), y, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer top(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getTop(), y, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer bottom(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getBottom(), y, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer bottom(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, + boolean autoAnchor) { + return unit(getBottom(), y, offset, anchor, measure, autoAnchor); + } + + private StandardResizer unit(Unit u, float val, int offset, float anchor, Unit.Measure measure, + boolean autoAnchor) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + u.setAnchor(anchor); + u.setAutoAnchor(autoAnchor); + scheduleResize(); + return this; + } + + private StandardResizer unit(Unit u, DoubleSupplier val, int offset, float anchor, Unit.Measure measure, + boolean autoAnchor) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + u.setAnchor(anchor); + u.setAutoAnchor(autoAnchor); + scheduleResize(); + return this; + } + + @ApiStatus.Internal + public StandardResizer width(float val, int offset, Unit.Measure measure) { + return unitSize(getWidth(), val, offset, measure); + } + + @ApiStatus.Internal + public StandardResizer width(DoubleSupplier val, int offset, Unit.Measure measure) { + return unitSize(getWidth(), val, offset, measure); + } + + @ApiStatus.Internal + public StandardResizer height(float val, int offset, Unit.Measure measure) { + return unitSize(getHeight(), val, offset, measure); + } + + @ApiStatus.Internal + public StandardResizer height(DoubleSupplier val, int offset, Unit.Measure measure) { + return unitSize(getHeight(), val, offset, measure); + } + + private StandardResizer unitSize(Unit u, float val, int offset, Unit.Measure measure) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + scheduleResize(); + return this; + } + + private StandardResizer unitSize(Unit u, DoubleSupplier val, int offset, Unit.Measure measure) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + scheduleResize(); + return this; + } + + public StandardResizer anchorLeft(float val) { + getLeft().setAnchor(val); + getLeft().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchorRight(float val) { + getRight().setAnchor(1 - val); + getRight().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchorTop(float val) { + getTop().setAnchor(val); + getTop().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchorBottom(float val) { + getBottom().setAnchor(1 - val); + getBottom().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchor(Alignment alignment) { + if (this.x.hasStart() || !this.x.hasEnd()) { + anchorLeft(alignment.x); + } else if (this.x.hasEnd()) { + anchorRight(alignment.x); + } + if (this.y.hasStart() || !this.y.hasEnd()) { + anchorTop(alignment.y); + } else if (this.y.hasEnd()) { + anchorBottom(alignment.y); } + return this; + } + + public void setUnit(Unit unit, GuiAxis axis, Unit.State pos) { + (axis.isHorizontal() ? this.x : this.y).setUnit(unit, pos); } private Unit getLeft() { - return this.x.getStart(this.parent); + return this.x.getStart(getWidget()); } private Unit getRight() { - return this.x.getEnd(this.parent); + return this.x.getEnd(getWidget()); } private Unit getTop() { - return this.y.getStart(this.parent); + return this.y.getStart(getWidget()); } private Unit getBottom() { - return this.y.getEnd(this.parent); + return this.y.getEnd(getWidget()); } private Unit getWidth() { - return this.x.getSize(this.parent); + return this.x.getSize(getWidget()); } private Unit getHeight() { - return this.y.getSize(this.parent); + return this.y.getSize(getWidget()); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StaticResizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StaticResizer.java new file mode 100644 index 00000000000..01bb91148e2 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StaticResizer.java @@ -0,0 +1,128 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; + +public abstract class StaticResizer extends ResizeNode { + + private boolean childrenCalculated = false; + + public StaticResizer() {} + + @Override + public void initResizing(boolean onOpen) { + super.initResizing(onOpen); + setChildrenResized(false); + } + + @Override + public boolean isXCalculated() { + return true; + } + + @Override + public boolean isYCalculated() { + return true; + } + + @Override + public boolean isWidthCalculated() { + return true; + } + + @Override + public boolean isHeightCalculated() { + return true; + } + + @Override + public boolean areChildrenCalculated() { + return this.childrenCalculated; + } + + @Override + public boolean isLayoutDone() { + return true; + } + + @Override + public boolean canRelayout(boolean isParentLayout) { + return false; + } + + @Override + public boolean isXMarginPaddingApplied() { + return true; + } + + @Override + public boolean isYMarginPaddingApplied() { + return true; + } + + @Override + public boolean resize(boolean isParentLayout) { + return true; + } + + @Override + public boolean postResize() { + return true; + } + + @Override + public void setChildrenResized(boolean resized) { + this.childrenCalculated = resized; + } + + @Override + public void setLayoutDone(boolean done) {} + + @Override + public void setResized(boolean x, boolean y, boolean w, boolean h) {} + + @Override + public void setXMarginPaddingApplied(boolean b) {} + + @Override + public void setYMarginPaddingApplied(boolean b) {} + + @Override + public boolean hasYPos() { + return true; + } + + @Override + public boolean hasXPos() { + return true; + } + + @Override + public boolean hasHeight() { + return true; + } + + @Override + public boolean hasWidth() { + return true; + } + + @Override + public boolean hasStartPos(GuiAxis axis) { + return true; + } + + @Override + public boolean hasEndPos(GuiAxis axis) { + return false; + } + + @Override + public boolean hasFixedSize() { + return true; + } + + @Override + public boolean isFullSize() { + return false; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/WidgetResizeNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/WidgetResizeNode.java new file mode 100644 index 00000000000..77d86e26a1a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/WidgetResizeNode.java @@ -0,0 +1,73 @@ +package com.gregtechceu.gtceu.api.mui.widget.sizer; + +import com.gregtechceu.gtceu.api.mui.base.layout.ILayoutWidget; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +import java.util.Objects; + +public abstract class WidgetResizeNode extends ResizeNode { + + private final IWidget widget; + + protected WidgetResizeNode(IWidget widget) { + this.widget = Objects.requireNonNull(widget); + } + + public IWidget getWidget() { + return widget; + } + + @Override + public Area getArea() { + return widget.getArea(); + } + + @Override + public void initResizing(boolean onOpen) { + super.initResizing(onOpen); + this.widget.beforeResize(onOpen); + } + + @Override + public void onResized() { + super.onResized(); + this.widget.onResized(); + } + + @Override + public void postFullResize() { + super.postFullResize(); + this.widget.postResize(); + } + + @Override + public boolean isLayout() { + return this.widget instanceof ILayoutWidget; + } + + @Override + public boolean layoutChildren() { + if (this.widget instanceof ILayoutWidget layoutWidget) { + return layoutWidget.layoutWidgets(); + } + return true; + } + + @Override + public boolean postLayoutChildren() { + if (this.widget instanceof ILayoutWidget layoutWidget) { + return layoutWidget.postLayoutWidgets(); + } + return true; + } + + @Override + public String getDebugDisplayName() { + return "widget '" + this.widget + "' of screen '" + this.widget.getScreen() + "'"; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.widget + ")"; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/AbstractCycleButtonWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/AbstractCycleButtonWidget.java index 33c48a9f846..f82c5e75d54 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/AbstractCycleButtonWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/AbstractCycleButtonWidget.java @@ -6,12 +6,13 @@ import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; import com.gregtechceu.gtceu.api.mui.base.drawable.ITextLine; import com.gregtechceu.gtceu.api.mui.base.value.*; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.base.widget.Interactable; import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; import com.gregtechceu.gtceu.api.mui.utils.Alignment; import com.gregtechceu.gtceu.api.mui.value.IntValue; -import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.api.mui.widget.SingleChildWidget; import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; import org.jetbrains.annotations.NotNull; @@ -20,7 +21,7 @@ import java.util.Arrays; import java.util.function.Consumer; -public class AbstractCycleButtonWidget> extends Widget +public class AbstractCycleButtonWidget> extends SingleChildWidget implements Interactable { private static final RichTooltip[] EMPTY_TOOLTIP = new RichTooltip[0]; @@ -35,12 +36,15 @@ public class AbstractCycleButtonWidget> e protected IDrawable[] overlay = null; protected IDrawable[] hoverOverlay = null; protected RichTooltip[] tooltip = EMPTY_TOOLTIP; + protected IWidget[] stateChildren = null; + protected IWidget fallbackChild = null; @Override public void onInit() { if (this.intValue == null) { this.intValue = new IntValue(0); } + updateChild(getState()); } @Override @@ -79,6 +83,9 @@ private void setStateCount(int stateCount) { this.overlay = checkArray(this.overlay, stateCount); this.hoverBackground = checkArray(this.hoverBackground, stateCount); this.hoverOverlay = checkArray(this.hoverOverlay, stateCount); + if (this.stateChildren == null) this.stateChildren = new IWidget[stateCount]; + else if (this.stateChildren.length < stateCount) + this.stateChildren = Arrays.copyOf(this.stateChildren, stateCount); } protected void expectCount() { @@ -124,6 +131,7 @@ public void setState(int state, boolean setSource) { if (state < 0 || state >= this.stateCount) { throw new IndexOutOfBoundsException("CycleButton state out of bounds"); } + updateChild(state); if (setSource) { this.intValue.setIntValue(state); } @@ -131,6 +139,16 @@ public void setState(int state, boolean setSource) { markTooltipDirty(); } + private void updateChild(int state) { + IWidget child = this.stateChildren != null && this.stateChildren.length > state ? this.stateChildren[state] : + null; + if (child != null) { + child(child); + } else if (getChild() != this.fallbackChild) { + child(this.fallbackChild); + } + } + @Override public @NotNull Result onMousePressed(double mouseX, double mouseY, int button) { switch (button) { @@ -225,11 +243,39 @@ public W disableHoverOverlay() { return getThis(); } + @Override + public W invisible() { + if (this.background != null) { + Arrays.fill(this.background, IDrawable.EMPTY); + } + if (getBackground() == null) { + super.background(IDrawable.EMPTY); + } + return disableHoverBackground(); + } + protected W value(IIntValue value) { setSyncOrValue(ISyncOrValue.orEmpty(value)); return getThis(); } + @Override + public W child(IWidget child) { + this.fallbackChild = child; + return super.child(child); + } + + public W stateChild(int state, IWidget child) { + updateStateCount(state, false); + if (this.stateChildren == null) { + this.stateChildren = new IWidget[state + 1]; + } else if (this.stateChildren.length < state + 1) { + this.stateChildren = Arrays.copyOf(this.stateChildren, state + 1); + } + this.stateChildren[state] = child; + return getThis(); + } + /** * Sets the state dependent background. The images should be vertically stacked images from top to bottom * Note: The length must be already set! diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/CategoryList.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/CategoryList.java index 7cadc30d538..a025fe60546 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/CategoryList.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/CategoryList.java @@ -142,7 +142,8 @@ public void onChildAdd(IWidget child) { private void updateHeight() { layoutWidgets(); - WidgetTree.applyPos(this); + WidgetTree.preApplyPos(resizer()); + WidgetTree.applyPos(resizer()); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/Expandable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/Expandable.java index 41c2bd9926d..48ec22979a6 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/Expandable.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/Expandable.java @@ -50,6 +50,12 @@ public void onInit() { @Override public void beforeResize(boolean onOpen) { super.beforeResize(onOpen); + if (resizer().getChildren().isEmpty() || resizer().getChildren().size() > 2) + throw new IllegalStateException("Invalid Expandable children size"); + if (resizer().getChildren().size() > 1) { + resizer().getChildren().remove(1); + } + resizer().getChildren().set(0, this.expanded ? this.expandedView.resizer() : this.normalView.resizer()); this.currentChildren = Collections.singletonList(this.expanded ? this.expandedView : this.normalView); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ListWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ListWidget.java index 04bac29426b..e39c1496c94 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ListWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ListWidget.java @@ -61,7 +61,7 @@ public void onInit() { public void beforeResize(boolean onOpen) { super.beforeResize(onOpen); if (this.mainAxisMaxSize != null) { - flex().setUnit(this.mainAxisMaxSize, getAxis(), Unit.State.SIZE); + resizer().setUnit(this.mainAxisMaxSize, getAxis(), Unit.State.SIZE); } } @@ -102,7 +102,7 @@ public boolean layoutWidgets() { widget.resizer().updateResized(); continue; } - if (widget.flex().hasPos(axis)) { + if (widget.resizer().hasPos(axis)) { // this is required when the widget has a pos on the main axis, but not on the cross axis widget.resizer().updateResized(); continue; @@ -116,9 +116,11 @@ public boolean layoutWidgets() { widget.resizer().setMarginPaddingApplied(true); this.separatorPositions.add(p); p += separatorSize; - if (isValid()) { - widget.flex().applyPos(widget); - } + /* + * if (isValid()) { + * widget.flex().applyPos(widget); + * } + */ } int size = p + getArea().getPadding().getEnd(axis); getScrollData().setScrollSize(size); diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/PopupMenu.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/PopupMenu.java deleted file mode 100644 index bdbf78a91c6..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/PopupMenu.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.gregtechceu.gtceu.api.mui.widgets; - -import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; -import com.gregtechceu.gtceu.api.mui.widget.Widget; - -import lombok.Getter; -import lombok.Setter; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.List; - -public class PopupMenu> extends Widget { - - private final MenuWrapper menu; - @Getter - private final @NotNull List children; - - public PopupMenu(IWidget child) { - this.menu = new MenuWrapper(child); - child.flex().relative(this.getArea()); - this.menu.setEnabled(false); - this.children = Collections.singletonList(this.menu); - } - - @Override - public void onMouseStartHover() { - super.onMouseStartHover(); - this.menu.setEnabled(true); - this.menu.mightClose = false; - } - - @Override - public void onMouseEndHover() { - super.onMouseEndHover(); - this.menu.mightClose = true; - } - - public static class MenuWrapper extends Widget { - - private final IWidget child; - @Getter - private final @NotNull List children; - @Setter - private boolean mightClose = false; - - private MenuWrapper(IWidget child) { - this.child = child; - this.children = Collections.singletonList(child); - flex().coverChildren().cancelMovementX().cancelMovementY(); - } - - @Override - public void onUpdate() { - super.onUpdate(); - if (this.mightClose && !isBelowMouse()) { - setEnabled(false); - } - } - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SliderWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SliderWidget.java index d102f6d0d52..243305712d0 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SliderWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SliderWidget.java @@ -103,7 +103,7 @@ public void drawBackground(ModularGuiContext context, WidgetThemeEntry widget @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { if (this.handleDrawable != null) { - this.handleDrawable.draw(context, this.sliderArea, context.getTheme().getButtonTheme().getTheme()); + this.handleDrawable.draw(context, this.sliderArea, getPanel().getTheme().getButtonTheme().getTheme()); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SlotGroupWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SlotGroupWidget.java index 286fec1596d..cdd35b13de4 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SlotGroupWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SlotGroupWidget.java @@ -253,7 +253,7 @@ public SlotGroupWidget build() { x += 18; continue; } - widget.flex().left(x).top(y); + widget.resizer().left(x).top(y); slotGroupWidget.child(widget); if (this.syncKey != null && widget instanceof ISynced synced) { synced.syncHandler(this.syncKey, syncId++); @@ -264,7 +264,7 @@ public SlotGroupWidget build() { y += 18; x = 0; } - slotGroupWidget.flex().size(maxWidth, this.matrix.size() * 18); + slotGroupWidget.resizer().size(maxWidth, this.matrix.size() * 18); return slotGroupWidget; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SortableListWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SortableListWidget.java index f107af52769..2d91b64c49d 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SortableListWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/SortableListWidget.java @@ -2,16 +2,14 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.mui.animation.Animator; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; import com.gregtechceu.gtceu.api.mui.base.widget.IValueWidget; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.ObjectList; import com.gregtechceu.gtceu.api.mui.widget.DraggableWidget; -import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.viewport.LocatedWidget; import com.gregtechceu.gtceu.common.mui.GTGuiTextures; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; import lombok.Getter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,8 +27,8 @@ public class SortableListWidget extends ListValueWidget> onRemove; private int timeSinceLastMove = 0; private boolean scheduleAnimation = false; - private final ObjectList widgetAreaSnapshots = new ObjectArrayList<>(); - private final ObjectList animators = new ObjectArrayList<>();; + private final ObjectList widgetAreaSnapshots = ObjectList.create(); + private final ObjectList animators = ObjectList.create(); public SortableListWidget() { super(Item::getWidgetValue); @@ -165,7 +163,7 @@ public static class Item extends DraggableWidget> implements IValueWi private final T value; private List children; - private Predicate dropPredicate; + private Predicate dropPredicate; private SortableListWidget listWidget; @Getter private int index = -1; @@ -173,7 +171,7 @@ public static class Item extends DraggableWidget> implements IValueWi public Item(T value) { this.value = value; - flex().widthRel(1f).height(18); + resizer().widthRel(1f).height(18); background(GTGuiTextures.BUTTON); } @@ -192,18 +190,19 @@ public List getChildren() { } @Override - public boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + public boolean canDropHere(int x, int y, @Nullable IWidget widget) { return this.dropPredicate == null || this.dropPredicate.test(widget); } @Override public void onDrag(int mouseButton, double timeSinceLastClick) { super.onDrag(mouseButton, timeSinceLastClick); - // TODO: this kind of assumes the hovered is in the bounds of the parent item, which may not be true. - IWidget hovered = getContext().getTopHovered(); - Item item = WidgetTree.findParent(hovered, Item.class); - if (item != null && item != this && item.listWidget == this.listWidget) { - this.listWidget.moveTo(this.index, item.index); + for (LocatedWidget hovering : getPanel().getAllHoveringList(false)) { + if (hovering.getElement() instanceof SortableListWidget.Item item && item != this && + item.listWidget == this.listWidget) { + this.listWidget.moveTo(this.index, item.index); + break; + } } } @@ -230,7 +229,7 @@ public Item child(Function, IWidget> widgetCreator) { return child(widgetCreator.apply(this)); } - public Item dropPredicate(Predicate dropPredicate) { + public Item dropPredicate(Predicate dropPredicate) { this.dropPredicate = dropPredicate; return this; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TextWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TextWidget.java index fa455bb3365..ec7fb8aeb28 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TextWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TextWidget.java @@ -69,7 +69,7 @@ protected Component checkString() { protected void onTextChanged(Component newText) { // scheduling it would resize it on next frame, but we need it now - WidgetTree.resizeInternal(this, false); + WidgetTree.resizeInternal(resizer(), false); } private TextRenderer simulate(float maxWidth) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ToggleButton.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ToggleButton.java index a9d820f403a..332779f416f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ToggleButton.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/ToggleButton.java @@ -3,6 +3,7 @@ import com.gregtechceu.gtceu.api.mui.base.ITheme; import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; import com.gregtechceu.gtceu.api.mui.base.value.IBoolValue; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.theme.SelectableTheme; import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; @@ -102,4 +103,8 @@ public ToggleButton tooltip(boolean selected, Consumer builder) { public ToggleButton tooltipBuilder(boolean selected, Consumer builder) { return super.tooltipBuilder(selected ? 1 : 0, builder); } + + public ToggleButton child(boolean selected, IWidget widget) { + return stateChild(selected ? 1 : 0, widget); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TransformWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TransformWidget.java index def85b6ef61..f5a8bc51ecf 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TransformWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/TransformWidget.java @@ -2,14 +2,14 @@ import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; -import com.gregtechceu.gtceu.api.mui.widget.DelegatingSingleChildWidget; +import com.gregtechceu.gtceu.api.mui.widget.DelegatingWidget; import org.joml.Matrix4f; import org.joml.Vector3f; import java.util.function.Consumer; -public class TransformWidget extends DelegatingSingleChildWidget { +public class TransformWidget extends DelegatingWidget { private static final Vector3f sharedVec = new Vector3f(); @@ -17,10 +17,8 @@ public class TransformWidget extends DelegatingSingleChildWidget transform; - public TransformWidget() {} - public TransformWidget(IWidget child) { - child(child); + super(child); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/Flow.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/Flow.java index 27d455639de..84f63095c79 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/Flow.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/Flow.java @@ -6,6 +6,7 @@ import com.gregtechceu.gtceu.api.mui.utils.Alignment; import com.gregtechceu.gtceu.api.mui.widget.ParentWidget; import com.gregtechceu.gtceu.api.mui.widget.sizer.Box; +import com.gregtechceu.gtceu.api.mui.widget.sizer.ExpanderResizer; import com.gregtechceu.gtceu.utils.ReversedList; import lombok.Getter; @@ -16,7 +17,7 @@ import java.util.function.IntFunction; @Accessors(fluent = true, chain = true) -public class Flow extends ParentWidget implements ILayoutWidget, IExpander { +public class Flow extends ParentWidget implements ILayoutWidget { public static Flow row() { return new Flow(GuiAxis.X); @@ -58,6 +59,7 @@ public static Flow column() { public Flow(GuiAxis axis) { this.axis = axis; + resizer(new ExpanderResizer(this, axis)); sizeRel(1f, 1f); } @@ -76,8 +78,8 @@ public int getDefaultMainAxisSize() { GuiAxis axis = this.axis; int total = getArea().getPadding().getTotal(axis); for (IWidget widget : getChildren()) { - if (shouldIgnoreChildSize(widget) || widget.flex().hasPos(axis)) continue; - if (widget.flex().isExpanded() || !widget.resizer().isSizeCalculated(axis)) { + if (shouldIgnoreChildSize(widget) || widget.resizer().hasPos(axis)) continue; + if (widget.resizer().isExpanded() || !widget.resizer().isSizeCalculated(axis)) { total += axis.isHorizontal() ? widget.getDefaultWidth() : widget.getDefaultHeight(); } else { total += widget.getArea().getSize(axis); @@ -112,7 +114,7 @@ public boolean layoutWidgets() { final int size = getArea().getSize(axis) - padding.getTotal(this.axis); Alignment.MainAxis maa = this.mainAxisAlignment; if (!hasSize && maa != Alignment.MainAxis.START) { - if (flex().dependsOnChildren(this.axis)) { + if (resizer().dependsOnChildren(this.axis)) { // if this flow covers the children, we can assume start maa = Alignment.MainAxis.START; } else { @@ -135,9 +137,9 @@ public boolean layoutWidgets() { continue; } // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(this.axis)) continue; + if (widget.resizer().hasPos(this.axis)) continue; amount++; - if (widget.flex().isExpanded()) { + if (widget.resizer().isExpanded()) { expandedAmount++; childrenSize += widget.getArea().getMargin().getTotal(this.axis); continue; @@ -167,8 +169,8 @@ public boolean layoutWidgets() { // ignore disabled child if configured as such if (shouldIgnoreChildSize(widget)) continue; // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(this.axis)) continue; - if (widget.flex().isExpanded()) { + if (widget.resizer().hasPos(this.axis)) continue; + if (widget.resizer().isExpanded()) { widget.getArea().setSize(this.axis, newSize); widget.resizer().setSizeResized(this.axis, true); } @@ -193,7 +195,11 @@ public boolean layoutWidgets() { continue; } // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(this.axis)) continue; + if (widget.resizer().hasPos(this.axis)) { + widget.resizer().updateResized(); // this is required when the widget has a pos on the main axis, but + // not on the cross axis + continue; + } Box margin = widget.getArea().getMargin(); // set calculated relative main axis pos and set end margin for next widget @@ -227,14 +233,10 @@ public static boolean layoutCrossAxisListLike(IWidget parent, GuiAxis axis, for (IWidget widget : childrenList) { // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(axis)) { - // this is required when the widget has a pos on the main axis, but not on the cross axis - widget.resizer().updateResized(); - continue; - } + if (widget.resizer().hasPos(axis)) continue; Box margin = widget.getArea().getMargin(); // don't align auto positioned children in cross axis - if (!widget.flex().hasPos(other) && widget.resizer().isSizeCalculated(other)) { + if (!widget.resizer().hasPos(other) && widget.resizer().isSizeCalculated(other)) { int crossAxisPos = margin.getStart(other) + padding.getStart(other); if (hasWidth) { if (crossAxisAlignment == Alignment.CrossAxis.CENTER) { @@ -249,10 +251,12 @@ public static boolean layoutCrossAxisListLike(IWidget parent, GuiAxis axis, widget.resizer().setPosResized(other, true); widget.resizer().setMarginPaddingApplied(other, true); } - if (parent.isValid()) { - // we changed relative pos, but we need to calculate the new absolute pos and other stuff - widget.flex().applyPos(widget); - } + /* + * if (parent.isValid()) { + * // we changed relative pos, but we need to calculate the new absolute pos and other stuff + * widget.resizer().applyPos(widget); + * } + */ } return true; } @@ -320,11 +324,6 @@ public Flow reverseLayout(boolean reverseLayout) { return this; } - @Override - public GuiAxis getExpandAxis() { - return this.axis; - } - @Override protected String getTypeName() { return this.axis.isHorizontal() ? "Row" : "Column"; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/AbstractMenuButton.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/AbstractMenuButton.java new file mode 100644 index 00000000000..38b50cf4f4a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/AbstractMenuButton.java @@ -0,0 +1,234 @@ +package com.gregtechceu.gtceu.api.mui.widgets.menu; + +import com.gregtechceu.gtceu.api.mui.base.IPanelHandler; +import com.gregtechceu.gtceu.api.mui.base.ITheme; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.base.widget.Interactable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; +import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; + +import net.minecraft.ChatFormatting; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * This is the base class for a button that can open a floating widget by clicking or hovering the button. In ModularUI + * this is used for + * context menus and dropdown menus. When the menu is opened a new panel is created and the {@link Menu} widget is + * added. If that menu + * contains another one of these menu buttons, it will be added to that panel. + * + * @param type of this widget + */ +public abstract class AbstractMenuButton> extends Widget + implements IMenuPart, Interactable { + + /** + * The general direction where the menu will be opened. This is just a shortcut to standard resizer calls. + * If this is null you can customize the position yourself. + */ + protected Direction direction = Direction.DOWN; + /** + * If this is true, the menu can be opened when the mouse hovers this button. The menu will automatically close, + * when the button AND + * all widgets in the menus tree are no longer hovered. + */ + protected boolean openOnHover = true; + + /** + * The current menu widget. The menu will be created with {@link #createMenu()} if the menu is null when it's + * needed. If the method + * also returns a null value, a default menu is created. The menu can be set at any time with + * {@link #setMenu(Menu)}. + */ + private Menu menu; + /** + * @return true if the menu is currently open (soft or hard) + */ + @Getter + private boolean open; + private boolean softOpen; // state, soft means opened by hovering + private IPanelHandler panelHandler; + private final String panelName; + + /** + * @param panelName the name for the panel that may be created when opening the menu + */ + public AbstractMenuButton(String panelName) { + this.panelName = Objects.requireNonNull(panelName); + name(panelName); + } + + /** + * @return true if the menu is currently soft open (opened by hovering) + */ + protected boolean isSoftOpen() { + return softOpen; + } + + protected void toggleMenu(boolean soft) { + if (this.open) { + if (this.softOpen) { + if (soft) { + closeMenu(true); + } else { + this.softOpen = false; + } + } else if (!soft) { + if (this.openOnHover) { + this.softOpen = true; + } else { + closeMenu(false); + } + } + } else { + openMenu(soft); + } + } + + protected void openMenu(boolean soft) { + if (this.open) { + if (this.softOpen && !soft) { + this.softOpen = false; + } + return; + } + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.openSubMenu(getMenu()); + } else { + getPanelHandler().openPanel(); + } + this.open = true; + this.softOpen = soft; + } + + protected void closeMenu(boolean soft) { + if (!this.open || (!this.softOpen && soft)) return; + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.remove(getMenu()); + } else { + getPanelHandler().closePanel(); + } + this.open = false; + this.softOpen = false; + } + + protected Menu getMenu() { + if (this.menu == null) { + this.menu = createMenu(); + if (this.menu == null) { + this.menu = new Menu<>() + .child(IKey.str("No Menu supplied") + .style(ChatFormatting.RED) + .asWidget() + .center()) + .widthRel(1f) + .height(16); + if (this.direction == null) Direction.DOWN.positioner.accept(this.menu.resizer()); + } + } + if (!this.menu.resizer().hasParentOverride()) { + this.menu.resizer().relative(this); + } + if (this.direction != null) { + this.direction.positioner.accept(this.menu.resizer()); + } + this.menu.setMenuSource(this); + return this.menu; + } + + protected void setMenu(Menu menu) { + this.menu = menu; + } + + protected abstract Menu createMenu(); + + private IPanelHandler getPanelHandler() { + if (this.panelHandler == null) { + this.panelHandler = IPanelHandler.simple(getPanel(), + (parentPanel, player) -> new MenuPanel(this.panelName, getMenu()), true); + } + return this.panelHandler; + } + + @Override + public @NotNull Result onMousePressed(double x, double y, int mouseButton) { + if (!this.open) { + forEachSiblingMenuButton(w -> { + w.closeMenu(false); + return true; + }); + } + toggleMenu(false); + return Result.SUCCESS; + } + + @Override + public void onMouseEnterArea() { + super.onMouseEnterArea(); + if (this.openOnHover && forEachSiblingMenuButton(mb -> !mb.open || mb.softOpen)) { + openMenu(true); + } + } + + protected boolean forEachSiblingMenuButton(Predicate> test) { + Menu menuParent = WidgetTree.findParent(this, Menu.class); + if (menuParent != null) { + return WidgetTree.foreachChild(menuParent, + w -> !(w instanceof AbstractMenuButton mb) || mb == this || test.test(mb), false); + } + return true; + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + protected void checkClose() { + if (this.openOnHover && !isSelfOrChildHovered()) { + closeMenu(true); + Menu menuParent = WidgetTree.findParent(this, Menu.class); + if (menuParent != null) { + menuParent.checkClose(); + } + } + } + + @Override + public boolean isSelfOrChildHovered() { + if (isBelowMouse()) return true; + if (!isOpen() || this.menu == null) return false; + return this.menu.isSelfOrChildHovered(); + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getButtonTheme(); + } + + public enum Direction { + + UP(resizer -> resizer.bottomRel(1f)), + DOWN(resizer -> resizer.topRel(1f)), + LEFT_UP(resizer -> resizer.rightRel(1f).bottom(0)), + LEFT_DOWN(resizer -> resizer.rightRel(1f).top(0)), + RIGHT_UP(resizer -> resizer.leftRel(1f).bottom(0)), + RIGHT_DOWN(resizer -> resizer.leftRel(1f).top(0)), + UNDEFINED(resizer -> {}); + + private final Consumer positioner; + + Direction(Consumer positioner) { + this.positioner = positioner; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/ContextMenuButton.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/ContextMenuButton.java new file mode 100644 index 00000000000..6c103020234 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/ContextMenuButton.java @@ -0,0 +1,161 @@ +package com.gregtechceu.gtceu.api.mui.widgets.menu; + +import com.gregtechceu.gtceu.api.mui.base.widget.IPositioned; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.widgets.ListWidget; + +import java.util.function.Consumer; + +/** + * A button that opens a context menu on click or hover. + * + * @param type of this widget + */ +public class ContextMenuButton> extends AbstractMenuButton { + + /** + * @param panelName the panel name that the menu may create + */ + public ContextMenuButton(String panelName) { + super(panelName); + this.openOnHover = true; + } + + @Override + protected Menu createMenu() { + // menu is created by the user with the setter + return null; + } + + /** + * Sets the menu widget. The menu will by default be relative to this button. + * It is common to use {@link IPositioned#widthRel(float)} and {@link IPositioned#coverChildrenHeight()}. + * The {@link #direction(Direction)} will handle the position of the menu, but can be customized if + * {@link #openCustom()} is used. + * + * @param menu displayed menu + * @return this + */ + public W menu(Menu menu) { + setMenu(menu); + return getThis(); + } + + /** + * This is a shortcut that is meant to be used when a simple list of options should be displayed. It is recommended + * to call + * {@link com.gregtechceu.gtceu.api.mui.widgets.ListWidget#maxSize(int)} to limit the size. This is method is not + * suited for any customization on the actual menu widget. + * + * @param builder list builder which is called exactly once + * @return this + */ + public W menuList(Consumer> builder) { + ListWidget l = new ListWidget<>().widthRel(1f); + builder.accept(l); + return menu(new Menu<>() + .widthRel(1f) + .coverChildrenHeight() + .child(l)); + } + + /** + * Sets the general direction in which the menu should open. This is just a shortcut to {@link IPositioned} position + * calls. + * Use {@link #openCustom()} if none of the predefined options suit your needs. You can then position the menu + * yourself. + * + * @param direction general direction to open the menu in + * @return this + */ + public W direction(Direction direction) { + this.direction = direction; + return getThis(); + } + + /** + * Sets this button to require a click to open the menu. Hovering this button will not open it. + * + * @return this + */ + public W requiresClick() { + return openOnHover(false); + } + + /** + * Sets whether the menu should be opened when this button is hovered by the mouse. + * + * @param openOnHover true if the menu can be opened by hover + * @return this + */ + public W openOnHover(boolean openOnHover) { + this.openOnHover = openOnHover; + return getThis(); + } + + /** + * Sets the menu to open in the "up" direction. This does not set a horizontal position. + * This is best used with {@link IPositioned#widthRel(float) IPositioned.widthRel(1f)} + * + * @return this + */ + public W openUp() { + return direction(Direction.UP); + } + + /** + * Sets the menu to open in the "down" direction. This does not set a horizontal position. + * This is best used with {@link IPositioned#widthRel(float) IPositioned.widthRel(1f)} + * + * @return this + */ + public W openDown() { + return direction(Direction.DOWN); + } + + /** + * Sets the menu to open in the "left and up" direction. + * + * @return this + */ + public W openLeftUp() { + return direction(Direction.LEFT_UP); + } + + /** + * Sets the menu to open in the "left and down" direction. + * + * @return this + */ + public W openLeftDown() { + return direction(Direction.LEFT_DOWN); + } + + /** + * Sets the menu to open in the "right and up" direction. + * + * @return this + */ + public W openRightUp() { + return direction(Direction.RIGHT_UP); + } + + /** + * Sets the menu to open in the "right and down" direction. + * + * @return this + */ + public W openRightDown() { + return direction(Direction.RIGHT_DOWN); + } + + /** + * Sets the menu to open in no specified direction. The position of the menu must be set manually, or it is left at + * 0,0. + * + * @return this + */ + public W openCustom() { + return direction(Direction.UNDEFINED); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/DropdownWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/DropdownWidget.java new file mode 100644 index 00000000000..818b0f3cd15 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/DropdownWidget.java @@ -0,0 +1,229 @@ +package com.gregtechceu.gtceu.api.mui.widgets.menu; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.base.value.ISyncOrValue; +import com.gregtechceu.gtceu.api.mui.base.value.IValue; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.MutableSingletonList; +import com.gregtechceu.gtceu.api.mui.widgets.ButtonWidget; +import com.gregtechceu.gtceu.api.mui.widgets.ListWidget; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A button which displays a list of options when clicked. When an option is clicked, the menu closes and the button + * displays that selected + * option. To use it, supply the appropriate value type you want to use to the constructor. This is used to validate + * synced values. + * Add options that can be selected with {@link #option(Object)}, {@link #options(Iterable)} or + * {@link #options(Object[])}. + * Finally set a function that converts an option to a widget using {@link #optionToWidget(ToWidget)}. When a value is + * selected, it will + * sync to any set value handler. + * + * @param type of the values used + * @param type of this widget + */ +public class DropdownWidget> extends AbstractMenuButton { + + private final Class valueType; + private final MutableSingletonList selected = new MutableSingletonList<>(); + private final List values = new ArrayList<>(); + private IValue value; + private int maxListSize = 100; + private ToWidget toWidget; + + public DropdownWidget(String panelName, Class valueType) { + super(panelName); + this.valueType = valueType; + this.openOnHover = false; + } + + @Override + public void onInit() { + super.onInit(); + setValue(this.value.getValue(), false); + } + + @Override + public @NotNull List getChildren() { + return selected; + } + + protected IWidget valueToWidget(T v, boolean forSelectedDisplay) { + if (this.toWidget != null) { + return this.toWidget.apply(v, forSelectedDisplay); + } + return IKey.str(String.valueOf(v)).asWidget(); + } + + protected void setValue(T value, boolean updateValue) { + if (this.selected.hasValue()) { + this.selected.get().dispose(); + } + if (updateValue && this.value != null) this.value.setValue(value); + this.selected.set(valueToWidget(value, true)); + if (isValid()) { + this.selected.get().initialise(this, true); + scheduleResize(); + } + } + + @Override + protected Menu createMenu() { + return new Menu<>() + .widthRel(1f) + .coverChildrenHeight() + .child(new ListWidget<>() + .widthRel(1f) + .maxSize(this.maxListSize) + .children(this.values, v -> new ButtonWidget<>() + .widthRel(1f) + .coverChildrenHeight() + .child(valueToWidget(v, false)) + .onMousePressed((x, y, b) -> { + setValue(v, true); + closeMenu(false); + return true; + }))); + } + + @Override + public boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + return syncOrValue.isValueOfType(this.valueType); + } + + @Override + protected void setSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + super.setSyncOrValue(syncOrValue); + this.value = syncOrValue.castValueNullable(this.valueType); + } + + /** + * Deletes the current cached menu. This can be used if the list of options changes while the menu is open. + * If the menu is currently open it won't be affected. + */ + public void deleteMenu() { + setMenu(null); + } + + /** + * Sets a value handler that handles the selected value. + * + * @param value value handler + * @return this + */ + public W value(IValue value) { + setSyncOrValue(value); + return getThis(); + } + + /** + * Adds an option that can be selected. + * + * @param option selectable option + * @return this + */ + public W option(T option) { + this.values.add(option); + return getThis(); + } + + /** + * Adds an iterable of selectable options. + * + * @param options selectable options + * @return this + */ + public W options(Iterable options) { + for (T t : options) this.values.add(t); + return getThis(); + } + + /** + * Adds an array of selectable options. + * + * @param options selectable options + * @return this + */ + public W options(T... options) { + this.values.addAll(Arrays.asList(options)); + return getThis(); + } + + /** + * Clears all currently set selectable options. + * + * @return this + */ + public W clearOptions() { + this.values.clear(); + return getThis(); + } + + /** + * Sets a function which converts options into displayable widgets. The function is called once for each option when + * the menu is created + * and each time when a value is selected. The function itself has a value and boolean parameter. The value is the + * option which is + * displayed and the boolean is true if the widget is created for the selected display. There is no limit to what + * combination of widgets + * an option can be. Usually it is a text or a row with an icon and text. + * If this function is not a set {@link String#valueOf(Object)} is used to display text. + * + * @param toWidget function to create a display widget from an option + * @return this + * @see ToWidget + */ + public W optionToWidget(ToWidget toWidget) { + this.toWidget = toWidget; + return getThis(); + } + + /** + * Sets the maximum size of the list widget in pixel. By default, it is set to 100. + * + * @param maxListSize maximum list size in pixel + * @return this + */ + public W maxVerticalMenuSize(int maxListSize) { + this.maxListSize = maxListSize; + return getThis(); + } + + /** + * Sets the menu to open in the "up" direction. + * + * @return this + */ + public W directionUp() { + this.direction = Direction.UP; + return getThis(); + } + + /** + * Sets the menu to open in the "down" direction. + * + * @return this + */ + public W directionDown() { + this.direction = Direction.DOWN; + return getThis(); + } + + public interface ToWidget { + + /** + * A function to convert a value into a display widget. + * + * @param value value to display + * @param forSelectedDisplay if the widget is used on the button when the menu is closed + * @return this + */ + IWidget apply(T value, boolean forSelectedDisplay); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/IMenuPart.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/IMenuPart.java new file mode 100644 index 00000000000..5a8a3a8283d --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/IMenuPart.java @@ -0,0 +1,13 @@ +package com.gregtechceu.gtceu.api.mui.widgets.menu; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; + +public interface IMenuPart extends IWidget { + + default boolean isSelfOrChildHovered() { + return isBelowMouse() || !WidgetTree.foreachChild(this, + w -> !(w instanceof IMenuPart menuPart ? menuPart.isSelfOrChildHovered() : w.isBelowMouse()), + false); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/Menu.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/Menu.java new file mode 100644 index 00000000000..21033ce776f --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/Menu.java @@ -0,0 +1,45 @@ +package com.gregtechceu.gtceu.api.mui.widgets.menu; + +import com.gregtechceu.gtceu.api.mui.base.ITheme; +import com.gregtechceu.gtceu.api.mui.base.IThemeApi; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; +import com.gregtechceu.gtceu.api.mui.widget.ParentWidget; + +public class Menu> extends ParentWidget implements IMenuPart { + + private AbstractMenuButton menuSource; + + void setMenuSource(AbstractMenuButton source) { + this.menuSource = source; + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + protected void checkClose() { + if (this.menuSource != null && !this.menuSource.isBelowMouse() && !isSelfOrChildHovered()) { + this.menuSource.closeMenu(true); + this.menuSource.checkClose(); + } + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + if (!child.resizer().hasHeight()) { + child.resizer().height(12); + } + if (!child.resizer().hasWidth()) { + child.resizer().widthRel(1f); + } + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getWidgetTheme(IThemeApi.PANEL); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/MenuPanel.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/MenuPanel.java new file mode 100644 index 00000000000..968205fad10 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/menu/MenuPanel.java @@ -0,0 +1,37 @@ +package com.gregtechceu.gtceu.api.mui.widgets.menu; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public class MenuPanel extends ModularPanel { + + public MenuPanel(String name, IWidget menu) { + super(name); + fullScreenInvisible(); + child(menu); + themeOverride("modularui.context_menu"); + } + + public void openSubMenu(IWidget menuList) { + child(menuList); + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + child.scheduleResize(); + } + + @Override + public boolean isDraggable() { + return false; + } + + @Override + public boolean closeOnOutOfBoundsClick() { + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/FluidSlot.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/FluidSlot.java index 9c05cc6d368..86438a1f477 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/FluidSlot.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/FluidSlot.java @@ -167,7 +167,7 @@ public WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { } public int getSlotHoverColor() { - WidgetThemeEntry theme = getWidgetTheme(getContext().getTheme(), SlotTheme.class); + WidgetThemeEntry theme = getWidgetTheme(getPanel().getTheme(), SlotTheme.class); return theme.getTheme().getSlotHoverColor(); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/ItemSlot.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/ItemSlot.java index 6a917cbc2a5..2ad591b613f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/ItemSlot.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/slot/ItemSlot.java @@ -134,7 +134,7 @@ public WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { } public int getSlotHoverColor() { - WidgetThemeEntry theme = getWidgetTheme(getContext().getTheme(), SlotTheme.class); + WidgetThemeEntry theme = getWidgetTheme(getPanel().getTheme(), SlotTheme.class); return theme.getTheme().getSlotHoverColor(); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/textfield/BaseTextFieldWidget.java index 8d32977310b..18cc30e75c3 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/textfield/BaseTextFieldWidget.java @@ -112,7 +112,7 @@ public void onUpdate() { @Override public void preDraw(ModularGuiContext context, boolean transformed) { if (transformed) { - WidgetThemeEntry entry = getWidgetTheme(context.getTheme(), TextFieldTheme.class); + WidgetThemeEntry entry = getWidgetTheme(getPanel().getTheme(), TextFieldTheme.class); TextFieldTheme widgetTheme = entry.getTheme(); this.renderer.setColor(this.textColor != null ? this.textColor : widgetTheme.getTextColor()); this.renderer.setCursorColor(this.textColor != null ? this.textColor : widgetTheme.getTextColor()); diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ClientScreenHandler.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ClientScreenHandler.java index c6e07f3ee10..ea7aeb1c649 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ClientScreenHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ClientScreenHandler.java @@ -4,12 +4,10 @@ import com.gregtechceu.gtceu.api.mui.GuiErrorHandler; import com.gregtechceu.gtceu.api.mui.base.IMuiScreen; import com.gregtechceu.gtceu.api.mui.base.MCHelper; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; import com.gregtechceu.gtceu.api.mui.base.widget.IVanillaSlot; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.base.widget.Interactable; import com.gregtechceu.gtceu.api.mui.drawable.GuiDraw; -import com.gregtechceu.gtceu.api.mui.overlay.OverlayManager; import com.gregtechceu.gtceu.api.mui.overlay.OverlayStack; import com.gregtechceu.gtceu.api.mui.utils.Color; import com.gregtechceu.gtceu.api.mui.utils.FpsCounter; @@ -288,7 +286,7 @@ private static void onGuiChanged(Screen oldScreen, Screen newScreen) { ModularNetwork.CLIENT.closeAll(); } - OverlayManager.onOpenScreen(newScreen); + OverlayStack.onOpenScreen(newScreen); } private static void invalidateCurrentScreen() { @@ -461,7 +459,7 @@ public static void drawContainer(GuiGraphics graphics, ModularScreen muiScreen, // acc.invokeRenderLabels(graphics, mouseX, mouseY); acc.setHoveredSlot(null); - IGuiElement hovered = muiScreen.getContext().getTopHovered(); + IWidget hovered = muiScreen.getContext().getTopHovered(); if (hovered instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot()) { acc.setHoveredSlot(vanillaSlot.getVanillaSlot()); } @@ -553,18 +551,24 @@ public static void drawDebugScreen(GuiGraphics graphics, @Nullable ModularScreen int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); int screenH = muiScreen.getScreenArea().height; - int color = Color.argb(180, 40, 115, 220); - float scale = 0.80f; + int outlineColor = Long.decode(ConfigHolder.INSTANCE.dev.mui.outlineColor).intValue();// Color.argb(180, 40, + // 115, 220); + int textColor = Long.decode(ConfigHolder.INSTANCE.dev.mui.textColor).intValue();// Color.argb(180, 40, 115, + // 220); + float scale = ConfigHolder.INSTANCE.dev.mui.scale; int shift = (int) (11 * scale + 0.5f); int lineY = screenH - shift - 2; - GuiDraw.drawText(graphics, "Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, scale, color, true); + if (GTCEu.Mods.isJEILoaded() || GTCEu.Mods.isEMILoaded() || GTCEu.Mods.isREILoaded()) lineY -= 12; + GuiDraw.drawText(graphics, "Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, scale, outlineColor, true); lineY -= shift + 2; - GuiDraw.drawText(graphics, "FPS: " + fpsCounter.getFps(), 5, screenH - 23, scale, color, true); + GuiDraw.drawText(graphics, "FPS: " + fpsCounter.getFps(), 5, screenH - 23, scale, outlineColor, true); lineY -= shift; - GuiDraw.drawText(graphics, "Theme ID: " + context.getTheme().getId(), 5, lineY, scale, color, true); + GuiDraw.drawText(graphics, "Theme ID: " + context.getTheme().getId(), 5, lineY, scale, outlineColor, true); LocatedWidget locatedHovered = muiScreen.getPanelManager().getTopWidgetLocated(true); - if (locatedHovered != null) { - drawSegmentLine(graphics, lineY -= 4, scale, color); + boolean showHovered = ConfigHolder.INSTANCE.dev.mui.showHovered; + boolean showParent = ConfigHolder.INSTANCE.dev.mui.showParent; + if (locatedHovered != null && (showHovered || showParent)) { + drawSegmentLine(graphics, lineY -= 4, scale, outlineColor); lineY -= 10; IWidget hovered = locatedHovered.getElement(); @@ -575,64 +579,84 @@ public static void drawDebugScreen(GuiGraphics graphics, @Nullable ModularScreen Area area = hovered.getArea(); IWidget parent = hovered.getParent(); - GuiDraw.drawBorderOutsideXYWH(graphics, 0, 0, area.width, area.height, scale, color); - if (hovered.hasParent()) { + if (showHovered && ConfigHolder.INSTANCE.dev.mui.showOutline) { + GuiDraw.drawBorderOutsideXYWH(graphics, 0, 0, area.width, area.height, scale, outlineColor); + } + if (hovered.hasParent() && showParent && ConfigHolder.INSTANCE.dev.mui.showParentOutline) { GuiDraw.drawBorderOutsideXYWH(graphics, -area.rx, -area.ry, parent.getArea().width, - parent.getArea().height, - scale, Color.withAlpha(color, 0.3f)); + parent.getArea().height, scale, Color.withAlpha(outlineColor, 0.3f)); } graphics.pose().popPose(); locatedHovered.unapplyMatrix(context); - GuiDraw.drawText(graphics, - "Widget Theme: " + hovered.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, - lineY, scale, color, true); - lineY -= shift; - GuiDraw.drawText(graphics, "Size: " + area.width + ", " + area.height, 5, lineY, scale, color, true); - lineY -= shift; - GuiDraw.drawText(graphics, "Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, - lineY, scale, color, false); - lineY -= shift; - GuiDraw.drawText(graphics, "Class: " + hovered, 5, lineY, 1, color, true); - if (hovered.hasParent()) { - drawSegmentLine(graphics, lineY -= 4, scale, color); - lineY -= 10; - GuiDraw.drawText(graphics, - "Widget Theme: " + parent.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, - lineY, scale, color, true); - lineY -= shift; + if (showHovered) { + if (ConfigHolder.INSTANCE.dev.mui.showWidgetTheme) { + GuiDraw.drawText(graphics, "Widget Theme: " + + hovered.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), + 5, lineY, scale, textColor, true); + lineY -= shift; + } + if (ConfigHolder.INSTANCE.dev.mui.showSize) { + GuiDraw.drawText(graphics, "Size: " + area.width + ", " + area.height, 5, lineY, scale, textColor, + true); + lineY -= shift; + } + if (ConfigHolder.INSTANCE.dev.mui.showPos) { + GuiDraw.drawText(graphics, "Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, + 5, lineY, scale, textColor, false); + lineY -= shift; + } + GuiDraw.drawText(graphics, "Widget: " + hovered, 5, lineY, scale, textColor, true); + } + if (hovered.hasParent() && showParent) { + if (showHovered) { + drawSegmentLine(graphics, lineY -= 4, scale, textColor); + lineY -= 10; + } + if (ConfigHolder.INSTANCE.dev.mui.showParentWidgetTheme) { + GuiDraw.drawText(graphics, "Widget Theme: " + + parent.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), + 5, lineY, scale, textColor, true); + lineY -= shift; + } area = parent.getArea(); - GuiDraw.drawText(graphics, "Parent size: " + area.width + ", " + area.height, 5, lineY, scale, color, - true); - lineY -= shift; - GuiDraw.drawText(graphics, "Parent: " + parent, 5, lineY, 1, color, true); + if (ConfigHolder.INSTANCE.dev.mui.showParentSize) { + GuiDraw.drawText(graphics, "Parent size: " + area.width + ", " + area.height, 5, lineY, scale, + textColor, true); + lineY -= shift; + } + GuiDraw.drawText(graphics, "Parent: " + parent, 5, lineY, 1, outlineColor, true); } - if (hovered instanceof ItemSlot slotWidget) { - drawSegmentLine(graphics, lineY -= 4, scale, color); - lineY -= 10; - ModularSlot slot = slotWidget.getSlot(); - GuiDraw.drawText(graphics, "Slot Index: " + slot.getSlotIndex(), 5, lineY, scale, color, false); - lineY -= shift; - GuiDraw.drawText(graphics, "Slot Number: " + ((Slot) slot).index, 5, lineY, scale, color, false); - lineY -= shift; - if (slotWidget.isSynced()) { - SlotGroup slotGroup = slot.getSlotGroup(); - boolean allowShiftTransfer = slotGroup != null && slotGroup.isAllowShiftTransfer(); - GuiDraw.drawText(graphics, - "Shift-Click Priority: " + - (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), - 5, lineY, scale, color, true); + if (ConfigHolder.INSTANCE.dev.mui.showExtra) { + if (hovered instanceof ItemSlot slotWidget) { + drawSegmentLine(graphics, lineY -= 4, scale, textColor); + lineY -= 10; + ModularSlot slot = slotWidget.getSlot(); + GuiDraw.drawText(graphics, "Slot Index: " + slot.getSlotIndex(), 5, lineY, scale, textColor, false); + lineY -= shift; + GuiDraw.drawText(graphics, "Slot Number: " + ((Slot) slot).index, 5, lineY, scale, textColor, + false); + lineY -= shift; + if (slotWidget.isSynced()) { + SlotGroup slotGroup = slot.getSlotGroup(); + boolean allowShiftTransfer = slotGroup != null && slotGroup.isAllowShiftTransfer(); + GuiDraw.drawText(graphics, + "Shift-Click Priority: " + + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), + 5, lineY, scale, textColor, true); + } + } else if (hovered instanceof RichTextWidget richTextWidget) { + drawSegmentLine(graphics, lineY -= 4, scale, outlineColor); + lineY -= 10; + locatedHovered.applyMatrix(context); + Object hoveredElement = richTextWidget.getHoveredElement(); + locatedHovered.unapplyMatrix(context); + GuiDraw.drawText(graphics, "Hovered: " + hoveredElement, 5, lineY, scale, textColor, true); } - } else if (hovered instanceof RichTextWidget richTextWidget) { - drawSegmentLine(graphics, lineY -= 4, scale, color); - lineY -= 10; - locatedHovered.applyMatrix(context); - Object hoveredElement = richTextWidget.getHoveredElement(); - locatedHovered.unapplyMatrix(context); - GuiDraw.drawText(graphics, "Hovered: " + hoveredElement, 5, lineY, scale, color, true); } } // dot at mouse pos - GuiDraw.drawRect(graphics, mouseX, mouseY, 1, 1, Color.withAlpha(Color.GREEN.main, 0.8f)); + GuiDraw.drawRect(graphics, mouseX, mouseY, 1, 1, + Long.decode(ConfigHolder.INSTANCE.dev.mui.cursorColor).intValue()); graphics.setColor(1f, 1f, 1f, 1f); } diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/CustomModularScreen.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/CustomModularScreen.java index b32ca7928d1..430a7646f3d 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/CustomModularScreen.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/CustomModularScreen.java @@ -18,8 +18,14 @@ public abstract class CustomModularScreen extends ModularScreen { /** * Creates a new screen with ModularUI as its owner. */ + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + @Deprecated public CustomModularScreen() { super(GTCEu.MOD_ID); + if (GTCEu.isDev()) { + GTCEu.LOGGER.error("The single arg ModularScreen constructor should not be used. " + + "Use the other one and pass in your mod id."); + } } /** diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/DraggablePanelWrapper.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/DraggablePanelWrapper.java index 7b61d94f03c..632193ae1ef 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/DraggablePanelWrapper.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/DraggablePanelWrapper.java @@ -52,10 +52,9 @@ public void onDragEnd(boolean successful) { float x = this.panel.getContext().getAbsMouseX() - this.relativeClickX; y = y / (this.panel.getScreen().getScreenArea().height - this.panel.getArea().height); x = x / (this.panel.getScreen().getScreenArea().width - this.panel.getArea().width); - this.panel.flex().resetPosition(); - this.panel.flex().relativeToScreen(); - this.panel.flex().topRelAnchor(y, y) - .leftRelAnchor(x, x); + this.panel.resizer().resetPosition(); + this.panel.resizer().relativeToScreen(); + this.panel.resizer().topRelAnchor(y, y).leftRelAnchor(x, x); this.panel.scheduleResize(); } } diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularPanel.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularPanel.java index 2375b70bf85..37a886391ad 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularPanel.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularPanel.java @@ -3,6 +3,7 @@ import com.gregtechceu.gtceu.api.mui.animation.Animator; import com.gregtechceu.gtceu.api.mui.base.IPanelHandler; import com.gregtechceu.gtceu.api.mui.base.ITheme; +import com.gregtechceu.gtceu.api.mui.base.IThemeApi; import com.gregtechceu.gtceu.api.mui.base.MCHelper; import com.gregtechceu.gtceu.api.mui.base.layout.IViewport; import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; @@ -12,6 +13,7 @@ import com.gregtechceu.gtceu.api.mui.utils.HoveredWidgetList; import com.gregtechceu.gtceu.api.mui.utils.Interpolation; import com.gregtechceu.gtceu.api.mui.utils.Interpolations; +import com.gregtechceu.gtceu.api.mui.utils.ObjectList; import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncHandler; import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.api.mui.widget.ParentWidget; @@ -25,8 +27,6 @@ import net.minecraft.Util; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.ApiStatus; @@ -70,7 +70,7 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int hei private State state = State.IDLE; private boolean cantDisposeNow = false; @Getter - private final @NotNull ObjectArrayList hovering = new ObjectArrayList<>(); + private final @NotNull ObjectList hovering = ObjectList.create(); private final Input keyboard = new Input(); private final Input mouse = new Input(); @@ -86,6 +86,8 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int hei private Animator animator; private boolean resizeable = false; + private String themeOverride; + private ITheme theme; private Runnable onCloseAction; @@ -241,6 +243,7 @@ public boolean canHover() { public void onOpen(ModularScreen screen) { this.screen = screen; getArea().z(1); + resizer().initialize(this.screen.getResizeNode(), this.screen.getResizeNode()); initialise(this, false); WidgetTree.onUpdate(this); // TODO: NEA handles main panel @@ -269,7 +272,7 @@ public void onClose() { @Override public boolean isExcludeAreaInXei() { - return super.isExcludeAreaInXei() || (!getScreen().isOverlay() && !this.invisible && !flex().isFullSize()); + return super.isExcludeAreaInXei() || (!getScreen().isOverlay() && !this.invisible && !resizer().isFullSize()); } @MustBeInvokedByOverriders @@ -813,6 +816,13 @@ void closeClientSubPanels() { } } + public ITheme getTheme() { + if (this.theme == null) { + this.theme = IThemeApi.get().getThemeForScreen(this, this.themeOverride); + } + return this.theme; + } + public ModularPanel bindPlayerInventory() { return child(SlotGroupWidget.playerInventory(true)); } @@ -841,6 +851,12 @@ public ModularPanel onCloseAction(Runnable onCloseAction) { return this; } + public ModularPanel themeOverride(String id) { + this.themeOverride = id; + this.theme = null; + return this; + } + @Deprecated @Override public ModularPanel name(String name) { @@ -877,7 +893,7 @@ public enum State { */ private static class Input { - private final ObjectList acceptedInteractions = new ObjectArrayList<>(); + private final ObjectList acceptedInteractions = ObjectList.create(); @Nullable private LocatedWidget lastPressed; private boolean held; diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularScreen.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularScreen.java index b31f97e5eba..878b0e23b17 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularScreen.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/ModularScreen.java @@ -13,6 +13,7 @@ import com.gregtechceu.gtceu.api.mui.value.sync.ModularSyncManager; import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.api.mui.widget.sizer.ScreenResizeNode; import com.gregtechceu.gtceu.api.mui.widget.wrapper.WidgetWrapper; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; @@ -36,10 +37,7 @@ import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.systems.RenderSystem; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.*; import lombok.Getter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.MustBeInvokedByOverriders; @@ -100,10 +98,15 @@ public static ModularScreen getCurrent() { private final Map, List> guiActionListeners = new Object2ObjectOpenHashMap<>(); private final Object2ObjectArrayMap frameUpdates = new Object2ObjectArrayMap<>(); @Getter + private final ScreenResizeNode resizeNode = new ScreenResizeNode(this); + @Getter private boolean pauseScreen = false; @Getter private boolean openParentOnClose = false; + @Getter + @Nullable + private String themeOverride; private ITheme currentTheme; @Getter private IMuiScreen screenWrapper; @@ -114,12 +117,16 @@ public static ModularScreen getCurrent() { private boolean overlay = false; /** - * Creates a new screen with a ModularUI as its owner and a given {@link ModularPanel}. - * - * @param mainPanel main panel of this screen + * @deprecated use the other constructor */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public ModularScreen(@NotNull ModularPanel mainPanel) { this(GTCEu.MOD_ID, mainPanel); + if (GTCEu.isDev()) { + GTCEu.LOGGER.warn("The single arg ModularScreen constructor should not be used. " + + "Use the any of the other ones and pass in your mod id."); + } } /** @@ -214,9 +221,8 @@ public void onResize(int width, int height) { } this.context.pushViewport(null, this.context.getScreenArea()); - for (ModularPanel panel : this.panelManager.getReverseOpenPanels()) { - WidgetTree.resizeInternal(panel, true); - } + WidgetTree.verifyTree(this.resizeNode, new ReferenceOpenHashSet<>()); + WidgetTree.resizeInternal(this.resizeNode, true); this.context.popViewport(null); if (!isOverlay()) { @@ -729,7 +735,7 @@ private static Class getGuiActionClass(IGuiAction action) { public ITheme getCurrentTheme() { if (this.currentTheme == null) { - useTheme(null); + useTheme(this.themeOverride); } return this.currentTheme; } @@ -743,7 +749,8 @@ public ITheme getCurrentTheme() { * @return this for builder like usage */ public ModularScreen useTheme(String theme) { - this.currentTheme = IThemeApi.get().getThemeForScreen(this, theme); + this.themeOverride = theme; + this.currentTheme = IThemeApi.get().getThemeForScreen(this, this.themeOverride); return this; } diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/OpenScreenEvent.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/OpenScreenEvent.java new file mode 100644 index 00000000000..e16584a243b --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/OpenScreenEvent.java @@ -0,0 +1,39 @@ +package com.gregtechceu.gtceu.client.mui.screen; + +import com.gregtechceu.gtceu.api.mui.base.IMuiScreen; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.eventbus.api.Event; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class OpenScreenEvent extends Event { + + @Getter + private final Screen screen; + private final List overlays = new ArrayList<>(); + + public OpenScreenEvent(Screen screen) { + this.screen = screen; + } + + public boolean isModularScreen() { + return screen instanceof IMuiScreen; + } + + public @Nullable ModularScreen getModularScreen() { + return screen instanceof IMuiScreen muiScreen ? muiScreen.getScreen() : null; + } + + public List getOverlays() { + return overlays; + } + + public void addOverlay(ModularScreen screen) { + this.overlays.add(screen); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/PanelManager.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/PanelManager.java index 1a88270a9a5..7285a8aa87a 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/PanelManager.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/PanelManager.java @@ -3,14 +3,13 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.mui.base.IPanelHandler; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.ObjectList; import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; import com.gregtechceu.gtceu.api.mui.widget.wrapper.WidgetWrapper; import com.gregtechceu.gtceu.client.mui.screen.viewport.LocatedWidget; import com.gregtechceu.gtceu.utils.ReverseIterable; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; import lombok.Getter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -34,7 +33,7 @@ public class PanelManager { /** * List of all open panels from top to bottom. */ - private final ObjectList panels = new ObjectArrayList<>(); + private final ObjectList panels = ObjectList.create(); // a clone of the list to avoid CMEs private final List panelsClone = new ArrayList<>(); private final List panelsView = Collections.unmodifiableList(this.panelsClone); @@ -42,7 +41,7 @@ public class PanelManager { private final List panelWrappers = new ArrayList<>(); private final List panelWrappersView = Collections.unmodifiableList(this.panelWrappers); private final ReverseIterable reversePanelWrappers = new ReverseIterable<>(this.panelWrappersView); - private final ObjectList disposal = new ObjectArrayList<>(DISPOSAL_CAPACITY); + private final ObjectList disposal = ObjectList.create(DISPOSAL_CAPACITY); private final Map panelHandlerMap = new Object2ObjectOpenHashMap<>(); private boolean cantDisposeNow = false; private boolean dirty = false; @@ -134,7 +133,7 @@ private void openPanel(ModularPanel panel, boolean resize) { this.dirty = true; panel.onOpen(this.screen); if (resize) { - WidgetTree.resizeInternal(panel, true); + WidgetTree.resizeInternal(panel.resizer(), true); } } diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/GuiContext.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/GuiContext.java index d76f64f618c..683f17f7af2 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/GuiContext.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/GuiContext.java @@ -3,7 +3,7 @@ import com.gregtechceu.gtceu.api.mui.base.GuiAxis; import com.gregtechceu.gtceu.api.mui.base.MCHelper; import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; -import com.gregtechceu.gtceu.api.mui.base.widget.IGuiElement; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; import com.gregtechceu.gtceu.api.mui.utils.Stencil; import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; import com.gregtechceu.gtceu.client.mui.screen.ClientScreenHandler; @@ -69,14 +69,14 @@ public static GuiContext getDefault() { @Getter private int currentDrawingZ = 0; - public boolean isAbove(IGuiElement widget) { + public boolean isAbove(IWidget widget) { return isMouseAbove(widget.getArea()); } /** * @return true the mouse is anywhere above the widget */ - public boolean isMouseAbove(IGuiElement widget) { + public boolean isMouseAbove(IWidget widget) { return isMouseAbove(widget.getArea()); } diff --git a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/ModularGuiContext.java b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/ModularGuiContext.java index 3af4237c2c0..73a92338b75 100644 --- a/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/ModularGuiContext.java +++ b/src/main/java/com/gregtechceu/gtceu/client/mui/screen/viewport/ModularGuiContext.java @@ -88,7 +88,7 @@ public boolean isHovered() { */ @ApiStatus.ScheduledForRemoval(inVersion = "2.7.0") @Deprecated - public boolean isHovered(IGuiElement guiElement) { + public boolean isHovered(IWidget guiElement) { return guiElement.isHovering(); } @@ -101,7 +101,7 @@ public boolean isHovered(IGuiElement guiElement) { */ @ApiStatus.ScheduledForRemoval(inVersion = "2.7.0") @Deprecated - public boolean isHoveredFor(IGuiElement guiElement, int ticks) { + public boolean isHoveredFor(IWidget guiElement, int ticks) { // convert from frames per second to ticks per second return guiElement.isHoveringFor(ticks); } @@ -126,9 +126,9 @@ public Iterable getAllHovered() { } /** - * @return all widgets which are below the mouse ({@link GuiContext#isAbove(IGuiElement)} is true) + * @return all widgets which are below the mouse ({@link GuiContext#isAbove(IWidget)} is true) */ - public Iterable getAllBelowMouse() { + public Iterable getAllBelowMouse() { return this.hoveredWidgets; } @@ -316,7 +316,7 @@ public boolean onHoveredClick(int button, LocatedWidget hovered) { draggable = new LocatedElement<>(iDraggable, hovered.getTransformationMatrix()); } else if (widget instanceof ModularPanel panel) { if (panel.isDraggable()) { - if (!panel.flex().hasFixedSize()) { + if (!panel.resizer().hasFixedSize()) { throw new IllegalStateException( "Panel must have a fixed size. It can't specify left AND right or top AND bottom!"); } @@ -476,7 +476,7 @@ public void setSettings(UISettings settings) { } } - private static class HoveredIterable implements Iterable { + private static class HoveredIterable implements Iterable { private final PanelManager panelManager; @@ -486,7 +486,7 @@ private HoveredIterable(PanelManager panelManager) { @NotNull @Override - public Iterator iterator() { + public Iterator iterator() { return new Iterator<>() { private final Iterator panelIt = HoveredIterable.this.panelManager.getOpenPanels() @@ -505,7 +505,7 @@ public boolean hasNext() { } @Override - public IGuiElement next() { + public IWidget next() { if (this.widgetIt == null || !this.widgetIt.hasNext()) { this.widgetIt = this.panelIt.next().getHovering().iterator(); } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/muimachine/TestMuiMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/muimachine/TestMuiMachine.java index 5734eb4870e..5b2cab5f976 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/muimachine/TestMuiMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/muimachine/TestMuiMachine.java @@ -58,6 +58,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.DoubleUnaryOperator; public class TestMuiMachine extends MetaMachine implements IMuiMachine { @@ -160,7 +161,8 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISet return new Column() .widthRel(1f) .coverChildrenHeight() - .children(vals.size(), i -> IKey.str(String.valueOf(vals.get(i))).asWidget().padding(2)); + .children(vals.size(), i -> IKey.str(String.valueOf(vals.get(i))).asWidget().padding(2)) + .name("synced number col"); }); // disable spotless on the menu layout code so it won't insert random line breaks @@ -176,7 +178,7 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISet .rightRel(1f), true); PagedWidget.Controller tabController = new PagedWidget.Controller(); - panel.flex() // returns object which is responsible for sizing + panel.resizer() // returns object which is responsible for sizing .size(176, 220) // set a static size for the main panel .align(Alignment.Center); // center the panel in the screen @@ -231,6 +233,7 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISet .margin(5, 5, 20, 5).name("crafting")))) .child(Flow.column() + .name("main col") .sizeRel(1f) .paddingBottom(7) .child(new ParentWidget<>() @@ -514,6 +517,7 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISet .name("page 4 storage") .sizeRel(1f) .child(new Column() + .name("page 4 col, dynamic widgets") .padding(7) .child(new ItemSlot() .slot(new ModularSlot(this.storageInventory0, 0) @@ -526,10 +530,10 @@ public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISet .child(new DynamicSyncedWidget<>() .widthRel(1f) .syncHandler(dynamicSyncHandler)) - .child(new DynamicSyncedWidget<>() + /*.child(new DynamicSyncedWidget<>() .widthRel(1f) .coverChildrenHeight() - .syncHandler(dynamicLinkedSyncHandler)))) + .syncHandler(dynamicLinkedSyncHandler))*/)) .addPage(createSchemaPage(data)))) .child(SlotGroupWidget.playerInventory(false))); /* @@ -636,6 +640,21 @@ public ModularPanel openThirdWindow(PanelSyncManager syncManager, IPanelHandler return panel; } + public void buildDialog(Dialog dialog) { + AtomicReference value = new AtomicReference<>(""); + dialog.setDraggable(true); + dialog.child(new TextFieldWidget() + .resizer(flex -> flex.size(100, 20).align(Alignment.Center)) + .value(new StringValue.Dynamic(value::get, value::set))) + .child(new ButtonWidget<>() + .resizer(flex -> flex.size(8, 8).top(5).right(5)) + .overlay(IKey.str("x")) + .onMousePressed((x, y, mouseButton) -> { + dialog.closeWith(value.get()); + return true; + })); + } + @Override public void clientTick() { if (this.time++ % 20 == 0) { diff --git a/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java b/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java index 81debf0b686..ed671b80cb8 100644 --- a/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java +++ b/src/main/java/com/gregtechceu/gtceu/config/ConfigHolder.java @@ -906,5 +906,59 @@ public static class DeveloperConfigs { @Configurable @Configurable.Comment({ "Dump all registered GT models/blockstates/etc?", "Default: false" }) public boolean dumpAssets = false; + + @Configurable + public DeveloperConfigs.MuiConfigs mui = new DeveloperConfigs.MuiConfigs(); + + public static class MuiConfigs { + + @Configurable + @Configurable.Comment({ "Color for outlining widgets in debug mode, in ARGB" }) + @Configurable.StringPattern(value = "#[0-9a-fA-F]{1,8}") + @Configurable.Gui.ColorValue + public String textColor = "#dcb42873"; + + @Configurable + @Configurable.Comment({ "Color for outlining widgets in debug mode, in ARGB" }) + @Configurable.StringPattern(value = "#[0-9a-fA-F]{1,8}") + @Configurable.Gui.ColorValue + public String outlineColor = "#dcb42873"; + + @Configurable + @Configurable.Comment({ "Color for cursor in debug mode, in ARGB" }) + @Configurable.StringPattern(value = "#[0-9a-fA-F]{1,8}") + @Configurable.Gui.ColorValue + public String cursorColor = "#ff4cAf50"; + + @Configurable + @Configurable.Comment({ "Scale of debug text", + "Default: 0.8f" }) + @Configurable.DecimalRange(min = 0.1f, max = 10.0f) + public float scale = 0.8f; + + @Configurable + public boolean showHovered = true; + @Configurable + public boolean showPos = true; + @Configurable + public boolean showSize = true; + @Configurable + public boolean showWidgetTheme = true; + @Configurable + public boolean showExtra = true; + @Configurable + public boolean showOutline = true; + + @Configurable + public boolean showParent = true; + @Configurable + public boolean showParentPos = true; + @Configurable + public boolean showParentSize = true; + @Configurable + public boolean showParentWidgetTheme = true; + @Configurable + public boolean showParentOutline = true; + } } }