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 d1a7b947c71..3c6a76c76a3 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 @@ -43,10 +43,25 @@ default void applyPos() {} void setLayoutDone(boolean done); + default void setAxisResized(GuiAxis axis, boolean pos, boolean size) { + if (axis.isHorizontal()) { + setXAxisResized(pos, size); + } else { + setYAxisResized(pos, size); + } + } + + void setXAxisResized(boolean pos, boolean size); + + void setYAxisResized(boolean pos, boolean size); + /** * Marks position and size as calculated. */ - void setResized(boolean x, boolean y, boolean w, boolean h); + default void setResized(boolean x, boolean y, boolean w, boolean h) { + setXAxisResized(x, w); + setYAxisResized(y, h); + } default void setPosResized(boolean x, boolean y) { setResized(x, y, isWidthCalculated(), isHeightCalculated()); @@ -57,11 +72,11 @@ default void setSizeResized(boolean w, boolean h) { } default void setXResized(boolean v) { - setResized(v, isYCalculated(), isWidthCalculated(), isHeightCalculated()); + setXAxisResized(v, isWidthCalculated()); } default void setYResized(boolean v) { - setResized(isXCalculated(), v, isWidthCalculated(), isHeightCalculated()); + setYAxisResized(v, isHeightCalculated()); } default void setPosResized(GuiAxis axis, boolean v) { @@ -73,11 +88,11 @@ default void setPosResized(GuiAxis axis, boolean v) { } default void setWidthResized(boolean v) { - setResized(isXCalculated(), isYCalculated(), v, isHeightCalculated()); + setXAxisResized(isXCalculated(), v); } default void setHeightResized(boolean v) { - setResized(isXCalculated(), isYCalculated(), isWidthCalculated(), v); + setYAxisResized(isYCalculated(), v); } default void setSizeResized(GuiAxis axis, boolean v) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/Alignment.java b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/Alignment.java index e9932a036c9..8efc81e474a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/Alignment.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/Alignment.java @@ -62,10 +62,33 @@ private Alignment(float x, float y, String name) { */ public enum MainAxis { + /** + * All children will be put at the start of the Flow next to each other. + */ START, + /** + * All children will be put in the center of the Flow next to each other. + */ CENTER, + /** + * All children will be put at the end of the Flow next to each other (this does not reverse children order). + */ END, + /** + * This maximizes the space between children with the given available space of the Flow. The first widget will + * be put at the very + * start and the last widget will be put at the very end. If the flow has exactly one child, then this behaves + * the same as + * {@link #CENTER}. + */ SPACE_BETWEEN, + /** + * This maximizes the space around the children with the given available space of the Flow. Contrary to + * {@link #SPACE_BETWEEN} this + * does not put one "space" between every widget, but rather one "space" on both sides of every widget. If the + * flow has exactly one + * child, then this behaves the same as {@link #CENTER}. + */ SPACE_AROUND } @@ -75,8 +98,17 @@ public enum MainAxis { */ public enum CrossAxis { + /** + * All children will be put at the start of the Flow next to each other. + */ START, + /** + * All children will be put in the center of the Flow next to each other. + */ CENTER, + /** + * All children will be put at the end of the Flow next to each other (this does not reverse children order). + */ END } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ColorShade.java b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ColorShade.java index c85643cbb3a..13df39eb786 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ColorShade.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/utils/ColorShade.java @@ -64,6 +64,10 @@ public int darkerSafe(int index) { return this.darker[Mth.clamp(index, 0, this.darker.length - 1)]; } + public int darkerShadeCount() { + return this.darker.length; + } + public int brighter(int index) { return this.brighter[index]; } @@ -72,6 +76,10 @@ public int brighterSafe(int index) { return this.brighter[Mth.clamp(index, 0, this.brighter.length - 1)]; } + public int brighterShadeCount() { + return this.brighter.length; + } + @NotNull @Override public IntIterator iterator() { diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigDecimalSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigDecimalSyncValue.java index 50f117c2680..3022b9dab7f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigDecimalSyncValue.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigDecimalSyncValue.java @@ -14,7 +14,12 @@ public class BigDecimalSyncValue extends GenericSyncValue implements IStringValue { public BigDecimalSyncValue(@NotNull Supplier getter, @Nullable Consumer setter) { - super(BigDecimal.class, getter, setter, ByteBufAdapters.BIG_DECIMAL, ICopy.immutable()); + this(getter, setter, false); + } + + public BigDecimalSyncValue(@NotNull Supplier getter, @Nullable Consumer setter, + boolean nullable) { + super(BigDecimal.class, getter, setter, ByteBufAdapters.BIG_DECIMAL, ICopy.immutable(), nullable); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigIntegerSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigIntegerSyncValue.java index cf517aa6833..eac4f46a92f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigIntegerSyncValue.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/BigIntegerSyncValue.java @@ -14,7 +14,12 @@ public class BigIntegerSyncValue extends GenericSyncValue implements IStringValue { public BigIntegerSyncValue(@NotNull Supplier getter, @Nullable Consumer setter) { - super(BigInteger.class, getter, setter, ByteBufAdapters.BIG_INT, ICopy.immutable()); + this(getter, setter, false); + } + + public BigIntegerSyncValue(@NotNull Supplier getter, @Nullable Consumer setter, + boolean nullable) { + super(BigInteger.class, getter, setter, ByteBufAdapters.BIG_INT, ICopy.immutable(), nullable); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/ByteArraySyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/ByteArraySyncValue.java index aec4ba6a297..b16ea50fdec 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/ByteArraySyncValue.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/ByteArraySyncValue.java @@ -11,6 +11,10 @@ public class ByteArraySyncValue extends GenericSyncValue { public ByteArraySyncValue(@NotNull Supplier getter, @Nullable Consumer setter) { - super(byte[].class, getter, setter, ByteBufAdapters.BYTE_ARR, byte[]::clone); + this(getter, setter, false); + } + + public ByteArraySyncValue(@NotNull Supplier getter, @Nullable Consumer setter, boolean nullable) { + super(byte[].class, getter, setter, ByteBufAdapters.BYTE_ARR, byte[]::clone, nullable); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericSyncValue.java index e473d9beffe..9a0231dce17 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericSyncValue.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/GenericSyncValue.java @@ -115,6 +115,18 @@ public GenericSyncValue(@NotNull Supplier getter, this(null, getter, setter, deserializer, serializer, equals, copy); } + @ApiStatus.Obsolete + public GenericSyncValue(@NotNull Class type, + @NotNull Supplier getter, + @Nullable Consumer setter, + @NotNull IByteBufAdapter adapter, + @Nullable ICopy copy, + boolean nullable) { + this(type, getter, setter, adapter, adapter, adapter, copy, nullable); + } + + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + @Deprecated public GenericSyncValue(@NotNull Class type, @NotNull Supplier getter, @Nullable Consumer setter, @@ -123,6 +135,7 @@ public GenericSyncValue(@NotNull Class type, this(type, getter, setter, adapter, adapter, adapter, copy); } + @ApiStatus.Obsolete public GenericSyncValue(@NotNull Class type, @NotNull Supplier getter, @Nullable Consumer setter, @@ -130,6 +143,8 @@ public GenericSyncValue(@NotNull Class type, this(type, getter, setter, adapter, adapter, adapter, null); } + @ApiStatus.ScheduledForRemoval(inVersion = "3.3.0") + @Deprecated public GenericSyncValue(@NotNull Class type, @NotNull Supplier getter, @Nullable Consumer setter, @@ -137,16 +152,32 @@ public GenericSyncValue(@NotNull Class type, @NotNull IByteBufSerializer serializer, @Nullable EqualityTest equals, @Nullable ICopy copy) { + this(type, getter, setter, deserializer, serializer, equals, copy, false); + } + + @ApiStatus.Obsolete + public GenericSyncValue(@NotNull Class type, + @NotNull Supplier getter, + @Nullable Consumer setter, + @NotNull IByteBufDeserializer deserializer, + @NotNull IByteBufSerializer serializer, + @Nullable EqualityTest equals, + @Nullable ICopy copy, + boolean nullable) { super(type, getter, setter); - this.deserializer = Objects.requireNonNull(deserializer); - this.serializer = Objects.requireNonNull(serializer); - this.equals = equals == null ? Objects::equals : EqualityTest.wrapNullSafe(equals); - this.copy = copy == null ? ICopy.ofSerializer(serializer, deserializer) : copy; + Objects.requireNonNull(deserializer); + Objects.requireNonNull(serializer); + this.deserializer = nullable ? IByteBufDeserializer.wrapNullSafe(deserializer) : deserializer; + this.serializer = nullable ? IByteBufSerializer.wrapNullSafe(serializer) : serializer; + if (equals == null) equals = EqualityTest.defaultTester(); + this.equals = nullable ? EqualityTest.wrapNullSafe(equals) : equals; + if (copy == null) copy = ICopy.ofSerializer(serializer, deserializer); + this.copy = copy; // null check in createDeepCopyOf() } @Override protected T createDeepCopyOf(T value) { - return this.copy.createDeepCopy(value); + return value == null ? null : this.copy.createDeepCopy(value); } @Override @@ -178,6 +209,7 @@ public static class Builder { private IByteBufSerializer serializer; private EqualityTest equals; private ICopy copy; + private boolean nullable; public Builder(Class type) { this.type = type; @@ -231,8 +263,13 @@ public Builder adapter(IByteBufAdapter adapter) { .equals(adapter); } + public Builder nullable() { + this.nullable = true; + return this; + } + public GenericSyncValue build() { - return new GenericSyncValue<>(type, getter, setter, deserializer, serializer, equals, copy); + return new GenericSyncValue<>(type, getter, setter, deserializer, serializer, equals, copy, nullable); } } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/LongArraySyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/LongArraySyncValue.java index e69ab0bd15a..2320ff41055 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/LongArraySyncValue.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/value/sync/LongArraySyncValue.java @@ -11,6 +11,10 @@ public class LongArraySyncValue extends GenericSyncValue { public LongArraySyncValue(@NotNull Supplier getter, @Nullable Consumer setter) { - super(long[].class, getter, setter, ByteBufAdapters.LONG_ARR, long[]::clone); + this(getter, setter, false); + } + + public LongArraySyncValue(@NotNull Supplier getter, @Nullable Consumer setter, boolean nullable) { + super(long[].class, getter, setter, ByteBufAdapters.LONG_ARR, long[]::clone, nullable); } } 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 index 384adf8d16c..69b3b2fee3b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/AbstractWidget.java @@ -325,6 +325,14 @@ protected void setName(String name) { this.name = name; } + public boolean isName(String name) { + return Objects.equals(name, this.name); + } + + public boolean nameContains(String part) { + return this.name != null && this.name.contains(part); + } + /** * This is only used in {@link #toString()}. * 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 ffaca03dcee..0e75b036d61 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 @@ -227,6 +227,7 @@ static boolean resize(ResizeNode resizer, boolean init, boolean onOpen, boolean if (isLayout && shouldLayout) { layoutSuccessful &= resizer.postLayoutChildren(); + if (!selfFullyCalculated) resizer.postResize(); } if (shouldLayout) resizer.setLayoutDone(layoutSuccessful); checkFullyCalculated(anotherResize, state, isLayout); 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 deleted file mode 100644 index e47a79d90c5..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/RenderNode.java +++ /dev/null @@ -1,27 +0,0 @@ -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 fdbfc1776fe..3563c4a86f2 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 @@ -16,7 +16,6 @@ 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.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; @@ -579,8 +578,6 @@ public W setEnabledIf(Predicate condition) { // === Resizing === // ---------------- - public void estimateSize(Bounds bounds) {} - @Override public int getDefaultWidth() { return isValid() ? getWidgetTheme(getPanel().getTheme()).getTheme().getDefaultWidth() : 18; 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 deleted file mode 100644 index d56584b7a36..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/WidgetNode.java +++ /dev/null @@ -1,14 +0,0 @@ -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/sizer/Area.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Area.java index f7f4f6b9ad0..b83509f0373 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 @@ -231,6 +231,10 @@ public int requestedSize(GuiAxis axis) { return axis.isHorizontal() ? requestedWidth() : requestedHeight(); } + public int paddedSize(GuiAxis axis) { + return axis.isHorizontal() ? paddedWidth() : paddedHeight(); + } + public int relativeEndX() { return this.rx + this.width; } 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 deleted file mode 100644 index 67629178666..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Bounds.java +++ /dev/null @@ -1,37 +0,0 @@ -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/DimensionSizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/DimensionSizer.java index 5492e412be9..4d83bbc6784 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 @@ -155,10 +155,6 @@ private boolean needsSize(Unit unit) { return unit.isRelative() && unit.getAnchor() != 0; } - 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(); diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java index 67bb6a26312..bee1a417d9e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/StandardResizer.java @@ -138,7 +138,7 @@ public boolean resize(boolean isParentLayout) { // calculate x, y, width and height if possible this.x.apply(area, relativeTo, () -> getWidget().getDefaultWidth()); this.y.apply(area, relativeTo, () -> getWidget().getDefaultHeight()); - return isFullyCalculated(isParentLayout); + return isSelfFullyCalculated(isParentLayout); } @Override @@ -360,9 +360,13 @@ public void setLayoutDone(boolean done) { } @Override - public void setResized(boolean x, boolean y, boolean w, boolean h) { - this.x.setResized(x, w); - this.y.setResized(y, h); + public void setXAxisResized(boolean pos, boolean size) { + this.x.setResized(pos, size); + } + + @Override + public void setYAxisResized(boolean pos, boolean size) { + this.y.setResized(pos, size); } @Override 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 index 01bb91148e2..66b0aeea9a4 100644 --- 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 @@ -78,7 +78,10 @@ public void setChildrenResized(boolean resized) { public void setLayoutDone(boolean done) {} @Override - public void setResized(boolean x, boolean y, boolean w, boolean h) {} + public void setXAxisResized(boolean pos, boolean size) {} + + @Override + public void setYAxisResized(boolean pos, boolean size) {} @Override public void setXMarginPaddingApplied(boolean b) {} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Unit.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Unit.java index 29734e6693e..bcf07ade4f7 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Unit.java +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widget/sizer/Unit.java @@ -30,12 +30,6 @@ public String getText(GuiAxis axis) { } } - public static final byte UNUSED = -2; - public static final byte DEFAULT = -1; - public static final byte START = 0; - public static final byte END = 1; - public static final byte SIZE = 2; - @Getter @Setter private boolean autoAnchor = true; 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 e39c1496c94..e172abdeed5 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 @@ -12,6 +12,7 @@ import com.gregtechceu.gtceu.api.mui.widget.scroll.VerticalScrollData; import com.gregtechceu.gtceu.api.mui.widget.sizer.Unit; import com.gregtechceu.gtceu.api.mui.widgets.layout.Flow; +import com.gregtechceu.gtceu.api.mui.widgets.layout.SimpleFlow; import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; import com.gregtechceu.gtceu.utils.ReversedList; @@ -19,6 +20,7 @@ import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; +import java.util.Collections; import java.util.List; import java.util.function.DoubleSupplier; import java.util.function.Function; @@ -116,11 +118,6 @@ public boolean layoutWidgets() { widget.resizer().setMarginPaddingApplied(true); this.separatorPositions.add(p); p += separatorSize; - /* - * if (isValid()) { - * widget.flex().applyPos(widget); - * } - */ } int size = p + getArea().getPadding().getEnd(axis); getScrollData().setScrollSize(size); @@ -139,7 +136,10 @@ public boolean layoutWidgets() { @Override public boolean postLayoutWidgets() { - return Flow.layoutCrossAxisListLike(this, getAxis(), this.crossAxisAlignment, this.reverseLayout); + SimpleFlow flow = new SimpleFlow(); + flow.widgets.addAll(getChildren()); + return Flow.layoutCrossAxisListLike(this, Collections.singletonList(flow), getAxis(), this.crossAxisAlignment, + 0); } @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 84f63095c79..bb9ee8a812c 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 @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.mui.widgets.layout; +import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.mui.base.GuiAxis; import com.gregtechceu.gtceu.api.mui.base.layout.ILayoutWidget; import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; @@ -12,8 +13,11 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import org.jetbrains.annotations.ApiStatus; +import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import java.util.function.IntFunction; @Accessors(fluent = true, chain = true) @@ -27,6 +31,10 @@ public static Flow column() { return new Flow(GuiAxis.Y); } + public static Flow col() { + return new Flow(GuiAxis.Y); + } + /** * The main axis on which to align children. */ @@ -35,12 +43,10 @@ public static Flow column() { /** * How the children should be laid out on the main axis. */ - @Setter private Alignment.MainAxis mainAxisAlignment = Alignment.MainAxis.START; /** * How the children should be laid out on the cross axis. */ - @Setter private Alignment.CrossAxis crossAxisAlignment = Alignment.CrossAxis.CENTER; /** * Additional space between each child on main axis. @@ -48,6 +54,7 @@ public static Flow column() { */ @Setter private int childPadding = 0; + private int crossAxisChildPadding = 0; /** * Whether disabled child widgets should be collapsed for display. */ @@ -57,6 +64,10 @@ public static Flow column() { */ private boolean reverseLayout = false; + private boolean wrap = false; + private final List ignoredWidgets = new ArrayList<>(); + private final List layoutWidgets = new ArrayList<>(); + public Flow(GuiAxis axis) { this.axis = axis; resizer(new ExpanderResizer(this, axis)); @@ -106,88 +117,86 @@ public int getDefaultCrossAxisSize() { return max + getArea().getPadding().getTotal(axis); } - @Override - public boolean layoutWidgets() { - if (!hasChildren()) return true; - final boolean hasSize = resizer().isSizeCalculated(this.axis); - final Box padding = getArea().getPadding(); - final int size = getArea().getSize(axis) - padding.getTotal(this.axis); - Alignment.MainAxis maa = this.mainAxisAlignment; - if (!hasSize && maa != Alignment.MainAxis.START) { - if (resizer().dependsOnChildren(this.axis)) { - // if this flow covers the children, we can assume start - maa = Alignment.MainAxis.START; - } else { - // for anything else than start we need the size to be known - return false; - } - } - List childrenList = this.reverseLayout ? new ReversedList<>(getChildren()) : getChildren(); - int space = this.childPadding; - - int childrenSize = 0; - int expandedAmount = 0; - int amount = 0; - - // calculate total size - for (IWidget widget : childrenList) { - // ignore disabled child if configured as such - if (shouldIgnoreChildSize(widget)) { - widget.resizer().setMarginPaddingApplied(true); + private boolean buildWrappedFlows(List children, int size, boolean wrap) { + this.layoutWidgets.clear(); + this.ignoredWidgets.clear(); + SimpleFlow currentFlow = new SimpleFlow(); + for (IWidget widget : children) { + // ignore disabled child if configured as such and + // exclude children whose position of main axis is fixed + if (shouldIgnoreChildSize(widget) || widget.resizer().hasPos(this.axis)) { + this.ignoredWidgets.add(widget); continue; } - // exclude children whose position of main axis is fixed - if (widget.resizer().hasPos(this.axis)) continue; - amount++; + boolean isEmpty = currentFlow.widgets.isEmpty(); if (widget.resizer().isExpanded()) { - expandedAmount++; - childrenSize += widget.getArea().getMargin().getTotal(this.axis); + currentFlow.expanderCount++; + // expanded widget size will be calculated later, but we still need to consider its margin + currentFlow.size += widget.getArea().getMargin().getTotal(this.axis); + if (!isEmpty) currentFlow.size += this.childPadding; + currentFlow.widgets.add(widget); + if (wrap) { + // if wrapping is enabled, then create a new row/col after every expanded + // TODO: is this desirable? + this.layoutWidgets.add(currentFlow); + currentFlow = new SimpleFlow(); + } continue; } // if the size of a widget is not calculated we can't continue if (!widget.resizer().isSizeCalculated(this.axis)) return false; - childrenSize += widget.getArea().requestedSize(this.axis); + int s = widget.getArea().requestedSize(this.axis); + if (!isEmpty) s += this.childPadding; + if (wrap && !isEmpty && currentFlow.size + s > size) { + // test if the widget with padding fits and create a new row/col if not + this.layoutWidgets.add(currentFlow); + currentFlow = new SimpleFlow(); + s -= this.childPadding; + } + currentFlow.size += s; + currentFlow.widgets.add(widget); } + if (!currentFlow.widgets.isEmpty()) this.layoutWidgets.add(currentFlow); + return true; + } - if (amount <= 1 && (maa == Alignment.MainAxis.SPACE_BETWEEN || maa == Alignment.MainAxis.SPACE_AROUND)) { - maa = Alignment.MainAxis.CENTER; + @Override + public boolean layoutWidgets() { + if (!hasChildren()) return true; + final boolean coverChildren = resizer().dependsOnChildren(this.axis); + boolean wrap = this.wrap; + if (coverChildren && wrap) { + GTCEu.LOGGER.warn( + "Flow can't coverChildren along its main axis and wrap at the same time. Offending widget: {}", + this); + wrap = false; } - final int spaceCount = Math.max(amount - 1, 0); - - if (maa == Alignment.MainAxis.SPACE_BETWEEN || maa == Alignment.MainAxis.SPACE_AROUND) { - if (expandedAmount > 0) { - maa = Alignment.MainAxis.START; - } else { - space = 0; + final boolean hasSize = resizer().isSizeCalculated(this.axis); + Alignment.MainAxis maa = this.mainAxisAlignment; + if (!hasSize) { + if (wrap) { + return false; } - } - childrenSize += space * spaceCount; - - if (expandedAmount > 0 && hasSize) { - int newSize = (size - childrenSize) / expandedAmount; - for (IWidget widget : childrenList) { - // ignore disabled child if configured as such - if (shouldIgnoreChildSize(widget)) continue; - // exclude children whose position of main axis is fixed - if (widget.resizer().hasPos(this.axis)) continue; - if (widget.resizer().isExpanded()) { - widget.getArea().setSize(this.axis, newSize); - widget.resizer().setSizeResized(this.axis, true); + if (maa != Alignment.MainAxis.START) { + if (resizer().dependsOnChildren(this.axis)) { + // if this flow covers the children, we can assume start + maa = Alignment.MainAxis.START; + } else { + // for anything else than start we need the size to be known + return false; } } } - // calculate start pos - int lastP = padding.getStart(this.axis); - if (hasSize) { - if (maa == Alignment.MainAxis.CENTER) { - lastP += (int) (size / 2f - childrenSize / 2f); - } else if (maa == Alignment.MainAxis.END) { - lastP += size - childrenSize; - } - } + final Box padding = getArea().getPadding(); + final int size = hasSize ? getArea().paddedSize(this.axis) : 0; + List childrenList = this.reverseLayout ? new ReversedList<>(getChildren()) : getChildren(); - for (IWidget widget : childrenList) { + if (!buildWrappedFlows(childrenList, size, wrap)) return false; + for (SimpleFlow flow : this.layoutWidgets) { + flow.layout(this.axis, size, padding, maa, this.childPadding); + } + for (IWidget widget : this.ignoredWidgets) { // ignore disabled child if configured as such if (shouldIgnoreChildSize(widget)) { widget.resizer().updateResized(); @@ -196,20 +205,8 @@ public boolean layoutWidgets() { } // exclude children whose position of main axis is fixed 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 - widget.getArea().setRelativePoint(this.axis, lastP + margin.getStart(this.axis)); - widget.resizer().setPosResized(this.axis, true); - widget.resizer().setMarginPaddingApplied(this.axis, true); - - lastP += widget.getArea().requestedSize(this.axis) + space; - if (hasSize && maa == Alignment.MainAxis.SPACE_BETWEEN) { - lastP += (size - childrenSize) / spaceCount; + // this is required when the widget has a pos on the main axis, but not on the cross axis + widget.resizer().updateResized(); } } return true; @@ -217,46 +214,48 @@ public boolean layoutWidgets() { @Override public boolean postLayoutWidgets() { - return Flow.layoutCrossAxisListLike(this, this.axis, this.crossAxisAlignment, this.reverseLayout); + return layoutCrossAxisListLike(this, this.layoutWidgets, this.axis, this.crossAxisAlignment, + this.crossAxisChildPadding); } - public static boolean layoutCrossAxisListLike(IWidget parent, GuiAxis axis, - Alignment.CrossAxis crossAxisAlignment, boolean reverseLayout) { - if (!parent.hasChildren()) return true; + public static boolean layoutCrossAxisListLike(IWidget parent, List flows, GuiAxis axis, + Alignment.CrossAxis crossAxisAlignment, int crossAxisSpaceBetween) { + if (flows.isEmpty()) return true; GuiAxis other = axis.getOther(); - int width = parent.getArea().getSize(other); + boolean isWrapped = flows.size() > 1; + // padding is applied in layoutCrossAxis() + int availableSize = parent.resizer().hasSize(other) ? parent.getArea().getSize(other) : -1; Box padding = parent.getArea().getPadding(); - boolean hasWidth = parent.resizer().isSizeCalculated(other); - if (!hasWidth && crossAxisAlignment != Alignment.CrossAxis.START) return false; - - List childrenList = reverseLayout ? new ReversedList<>(parent.getChildren()) : parent.getChildren(); + if (!isWrapped) { + // simplified logic for non-wrapped + flows.get(0).calculateCrossAxisSize(axis); + // starting pos is 0 and use parents padding + return flows.get(0).layoutCrossAxis(parent, axis, crossAxisAlignment, availableSize, 0, padding); + } + if (parent.resizer().dependsOnChildren(other)) { + // when covering children we can assume START + crossAxisAlignment = Alignment.CrossAxis.START; + } + if (crossAxisAlignment != Alignment.CrossAxis.START && !parent.resizer().hasSize(other)) return false; + int total = (flows.size() - 1) * crossAxisSpaceBetween; // start with cross axis child padding for total size + for (SimpleFlow flow : flows) { + flow.calculateCrossAxisSize(axis); + total += flow.crossSize; + } + // calculate starting pos + // TODO center padding + int p = parent.getArea().getPadding().getStart(other); + if (crossAxisAlignment == Alignment.CrossAxis.END) { + p = availableSize - total - parent.getArea().getMargin().getEnd(other); + } else if (crossAxisAlignment == Alignment.CrossAxis.CENTER) { + p = (availableSize - total) / 2; + } + crossAxisAlignment = Alignment.CrossAxis.CENTER; - for (IWidget widget : childrenList) { - // exclude children whose position of main axis is fixed - if (widget.resizer().hasPos(axis)) continue; - Box margin = widget.getArea().getMargin(); - // don't align auto positioned children in cross axis - if (!widget.resizer().hasPos(other) && widget.resizer().isSizeCalculated(other)) { - int crossAxisPos = margin.getStart(other) + padding.getStart(other); - if (hasWidth) { - if (crossAxisAlignment == Alignment.CrossAxis.CENTER) { - crossAxisPos = (int) (width / 2f - widget.getArea().getSize(other) / 2f); - } else if (crossAxisAlignment == Alignment.CrossAxis.END) { - crossAxisPos = width - widget.getArea().getSize(other) - margin.getEnd(other) - - padding.getStart(other); - } - } - widget.getArea().setRelativePoint(other, crossAxisPos); - widget.getArea().setPoint(other, parent.getArea().getPoint(other) + crossAxisPos); - 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.resizer().applyPos(widget); - * } - */ + for (SimpleFlow flow : flows) { + // use calculated pos and ignore parent padding + if (!flow.layoutCrossAxis(parent, axis, crossAxisAlignment, flow.crossSize, p, Box.ZERO)) return false; + p += flow.crossSize + crossAxisSpaceBetween; } return true; } @@ -292,6 +291,46 @@ public Flow children(int amount, IntFunction widgetCreator) { return getThis(); } + public Flow children(Iterable it, Function widgetCreator) { + for (T t : it) { + child(widgetCreator.apply(t)); + } + return getThis(); + } + + /** + * Sets the main axis alignment of this flow. This determines how multiple widgets are laid out along the main axis + * in this flow. + * + * @param maa main axis alignment + * @return this + * @see com.gregtechceu.gtceu.api.mui.utils.Alignment.MainAxis + */ + public Flow mainAxisAlignment(Alignment.MainAxis maa) { + this.mainAxisAlignment = maa; + return this; + } + + /** + * Sets the cross axis alignment of this flow. This determines how multiple widgets are laid out along the cross + * axis in this flow. + * + * @param caa cross axis alignment + * @return this + * @see com.gregtechceu.gtceu.api.mui.utils.Alignment.CrossAxis + */ + public Flow crossAxisAlignment(Alignment.CrossAxis caa) { + this.crossAxisAlignment = caa; + return this; + } + + /** + * Sets a fixed pixel size padding between all children widgets. + * + * @param spaceBetween pixel size padding between children + * @return this + */ + /** * Sets if disabled children should be collapsed. */ @@ -324,6 +363,41 @@ public Flow reverseLayout(boolean reverseLayout) { return this; } + public Flow reverseLayout() { + return reverseLayout(true); + } + + /** + * This causes the Flow to create multiple rows/columns when widgets overflow the main axis size. + * This my causes some unexpected behavior in layout. This feature is experimental. + * + * @param wrap if overflowing widgets should be wrapped to a next row/column + * @return this + */ + @ApiStatus.Experimental + public Flow wrap(boolean wrap) { + this.wrap = wrap; + return this; + } + + @ApiStatus.Experimental + public Flow wrap() { + return wrap(true); + } + + /** + * Sets the cross axis child padding. It is a fixed pixel size between rows/columnd. + * This only used if {@link #wrap()} is used. + * + * @param crossAxisChildPadding pixel space between rows/columns when wrapping is active + * @return this + */ + @ApiStatus.Experimental + public Flow crossAxisChildPadding(int crossAxisChildPadding) { + this.crossAxisChildPadding = crossAxisChildPadding; + return this; + } + @Override protected String getTypeName() { return this.axis.isHorizontal() ? "Row" : "Column"; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/SimpleFlow.java b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/SimpleFlow.java new file mode 100644 index 00000000000..675dd6c4a4a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/widgets/layout/SimpleFlow.java @@ -0,0 +1,128 @@ +package com.gregtechceu.gtceu.api.mui.widgets.layout; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Box; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleFlow { + + public final List widgets = new ArrayList<>(); + public int size, expanderCount, crossSize; + public boolean crossSizeCalculated; + + public void layout(GuiAxis axis, int availableSize, Box padding, Alignment.MainAxis maa, int childPadding) { + int amount = widgets.size(); + // special cases + if (expanderCount > 0) { + maa = Alignment.MainAxis.START; + } else if (amount <= 1 && (maa == Alignment.MainAxis.SPACE_BETWEEN || maa == Alignment.MainAxis.SPACE_AROUND)) { + maa = Alignment.MainAxis.CENTER; + } + + // calculate space between children + int space = childPadding; + if (maa == Alignment.MainAxis.SPACE_BETWEEN || maa == Alignment.MainAxis.SPACE_AROUND) { + if (maa == Alignment.MainAxis.SPACE_BETWEEN) { + space = (availableSize - size) / (amount - 1); + } else { + space = (availableSize - size) / amount; + } + } + + // calculate size of expanded + if (expanderCount > 0 && availableSize > 0) { + int newSize = (availableSize - size) / expanderCount; + for (IWidget widget : widgets) { + if (widget.resizer().isExpanded()) { + widget.getArea().setSize(axis, newSize); + widget.resizer().setSizeResized(axis, true); + } + } + } + + // calculate start pos + int lastP = padding.getStart(axis); + if (availableSize > 0) { + if (maa == Alignment.MainAxis.CENTER) { + lastP += (int) (availableSize / 2f - size / 2f); + } else if (maa == Alignment.MainAxis.END) { + lastP += availableSize - size; + } else if (maa == Alignment.MainAxis.SPACE_AROUND) { + lastP += space / 2; + } + } + + // apply pos + for (IWidget widget : widgets) { + Box margin = widget.getArea().getMargin(); + // set calculated relative main axis pos and set end margin for next widget + widget.getArea().setRelativePoint(axis, lastP + margin.getStart(axis)); + widget.resizer().setPosResized(axis, true); + widget.resizer().setMarginPaddingApplied(axis, true); + lastP += widget.getArea().requestedSize(axis) + space; + } + } + + public void calculateCrossAxisSize(GuiAxis axis) { + // calculates maximum size along the cross axis + GuiAxis other = axis.getOther(); + if (!this.crossSizeCalculated) { + this.crossSizeCalculated = true; + this.crossSize = 0; + for (IWidget widget : widgets) { + // exclude children whose position of main axis or cross axis is fixed + if (widget.resizer().hasXPos() || widget.resizer().hasYPos()) continue; + if (!widget.resizer().isSizeCalculated(other)) { + this.crossSizeCalculated = false; + continue; + } + this.crossSize = Math.max(widget.getArea().requestedSize(other), this.crossSize); + } + } + } + + public boolean layoutCrossAxis(IWidget parent, GuiAxis axis, Alignment.CrossAxis caa, int availableSize, int p, + Box padding) { + GuiAxis other = axis.getOther(); + if (availableSize < 0 && caa != Alignment.CrossAxis.START) return false; + for (IWidget widget : this.widgets) { + // exclude children whose position of main axis is fixed + if (widget.resizer().hasPos(axis)) continue; + Box margin = widget.getArea().getMargin(); + // don't align auto positioned children in cross axis + if (!widget.resizer().hasPos(other) && widget.resizer().isSizeCalculated(other)) { + int start = margin.getStart(other) + padding.getStart(other); + int crossAxisPos = 0; + if (caa == Alignment.CrossAxis.START) { + crossAxisPos = start; + } else { + int end = margin.getEnd(other) + padding.getEnd(other); + int s = widget.getArea().getSize(other); + if (caa == Alignment.CrossAxis.END) { + crossAxisPos = availableSize - s - end; + } else if (caa == Alignment.CrossAxis.CENTER) { + crossAxisPos = (int) (availableSize / 2f - widget.getArea().getSize(other) / 2f); + if (availableSize < s + start + end) { + GTCEu.LOGGER.warn( + "Widget {} is larger with padding on axis {} than parent {}. Padding can't be applied correctly!", + widget, other, parent); + } else { + if (crossAxisPos < start) crossAxisPos = start; + else if (crossAxisPos > availableSize - end - s) crossAxisPos = availableSize - end - s; + } + } + } + widget.getArea().setRelativePoint(other, crossAxisPos + p); + widget.getArea().setPoint(other, parent.getArea().getPoint(other) + crossAxisPos + p); + widget.resizer().setPosResized(other, true); + widget.resizer().setMarginPaddingApplied(other, true); + } + } + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/utils/ICopy.java b/src/main/java/com/gregtechceu/gtceu/utils/ICopy.java index 4502a9143e6..c94c3303e6b 100644 --- a/src/main/java/com/gregtechceu/gtceu/utils/ICopy.java +++ b/src/main/java/com/gregtechceu/gtceu/utils/ICopy.java @@ -27,4 +27,8 @@ static ICopy ofSerializer(IByteBufAdapter adapter) { } T createDeepCopy(T t); + + static ICopy wrapNullSafe(ICopy copy) { + return t -> t == null ? null : copy.createDeepCopy(t); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufDeserializer.java b/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufDeserializer.java index 4a1cc763d26..06623a3f6ff 100644 --- a/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufDeserializer.java +++ b/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufDeserializer.java @@ -16,4 +16,8 @@ public interface IByteBufDeserializer { * @return the read object */ T deserialize(FriendlyByteBuf buffer); + + static IByteBufDeserializer wrapNullSafe(IByteBufDeserializer deserializer) { + return buffer -> buffer.readBoolean() ? null : deserializer.deserialize(buffer); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufSerializer.java b/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufSerializer.java index 77a3025342b..a420fc7dffc 100644 --- a/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufSerializer.java +++ b/src/main/java/com/gregtechceu/gtceu/utils/serialization/network/IByteBufSerializer.java @@ -16,4 +16,13 @@ public interface IByteBufSerializer { * @param value object to write */ void serialize(FriendlyByteBuf buffer, T value); + + static IByteBufSerializer wrapNullSafe(IByteBufSerializer serializer) { + return (buffer, value) -> { + buffer.writeBoolean(value == null); + if (value != null) { + serializer.serialize(buffer, value); + } + }; + } }