diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/ILayoutWidget.java b/src/main/java/com/cleanroommc/modularui/api/layout/ILayoutWidget.java index 0c8e3456a..e7388c9a3 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/ILayoutWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/ILayoutWidget.java @@ -1,5 +1,7 @@ package com.cleanroommc.modularui.api.layout; +import com.cleanroommc.modularui.api.widget.IWidget; + /** * This is responsible for laying out widgets. */ @@ -17,4 +19,14 @@ public interface ILayoutWidget { * The last call guarantees, that this widget is fully calculated. */ default void postLayoutWidgets() {} + + /** + * Called when determining wrapping size of this widget. + * If this method returns true, size and margin of the queried child will be ignored for calculation. + * Typically return true when the child is disabled and you want to collapse it for layout. + * This method should also be used for layouting children with {@link #layoutWidgets} if it might return true. + */ + default boolean shouldIgnoreChildSize(IWidget child) { + return false; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java index 12e502a32..6ecfc6bc6 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -21,6 +21,8 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) { public static final Area SHARED = new Area(); + public static final Area ZERO = new Area(); + /** * relative position (in most cases the direct parent) */ diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java index 6f615b6a0..125132777 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java @@ -10,6 +10,8 @@ public class Box { public static final Box SHARED = new Box(); + public static final Box ZERO = new Box(); + public int left; public int top; public int right; @@ -74,4 +76,14 @@ public int getStart(GuiAxis axis) { public int getEnd(GuiAxis axis) { return axis.isHorizontal() ? this.right : this.bottom; } + + @Override + public String toString() { + return "Box{" + + "left=" + left + + ", top=" + top + + ", right=" + right + + ", bottom=" + bottom + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java index 934a32f3f..20e393fe4 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java @@ -429,9 +429,10 @@ private void coverChildrenForLayout(IWidget widget) { int x1 = Integer.MIN_VALUE, y1 = Integer.MIN_VALUE; int w = 0, h = 0; for (IWidget child : children) { - Box margin = child.getArea().getMargin(); + final boolean shouldIgnoreChildSize = ((ILayoutWidget) this.parent).shouldIgnoreChildSize(child); + Box margin = shouldIgnoreChildSize ? Box.ZERO : child.getArea().getMargin(); IResizeable resizeable = child.resizer(); - Area area = child.getArea(); + Area area = shouldIgnoreChildSize ? Area.ZERO : child.getArea(); if (this.x.dependsOnChildren() && resizeable.isWidthCalculated()) { w = Math.max(w, area.requestedWidth() + padding.horizontal()); if (resizeable.isXCalculated()) { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java index bdb168e40..ae4088daf 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java @@ -8,13 +8,11 @@ import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.widget.AbstractScrollWidget; -import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; import com.cleanroommc.modularui.widget.scroll.ScrollData; import com.cleanroommc.modularui.widget.scroll.VerticalScrollData; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import org.jetbrains.annotations.Nullable; import java.util.function.IntFunction; @@ -29,6 +27,7 @@ public class ListWidget> extends A private ScrollData scrollData; private IIcon childSeparator; private final IntList separatorPositions = new IntArrayList(); + private boolean collapseDisabledChild = false; public ListWidget() { super(null, null); @@ -70,6 +69,7 @@ public void layoutWidgets() { int separatorSize = getSeparatorSize(); int p = getArea().getPadding().getStart(axis); for (IWidget widget : getChildren()) { + if (shouldIgnoreChildSize(widget)) continue; if (axis.isVertical() ? widget.getFlex().hasYPos() || !widget.resizer().isHeightCalculated() : widget.getFlex().hasXPos() || !widget.resizer().isWidthCalculated()) { @@ -89,6 +89,11 @@ public void layoutWidgets() { getScrollData().setScrollSize(p + getArea().getPadding().getEnd(axis)); } + @Override + public boolean shouldIgnoreChildSize(IWidget child) { + return this.collapseDisabledChild && !child.isEnabled(); + } + @Override public boolean addChild(I child, int index) { return super.addChild(child, index); @@ -148,4 +153,12 @@ public W children(int amount, IntFunction widgetCreator) { } return getThis(); } + + /** + * Configures this widget to collapse disabled child widgets. + */ + public W collapseDisabledChild() { + this.collapseDisabledChild = true; + return getThis(); + } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index 49b215e97..06f8e00ac 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -4,7 +4,6 @@ import com.cleanroommc.modularui.api.layout.ILayoutWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.utils.Alignment; -import com.cleanroommc.modularui.widget.AbstractParentWidget; import com.cleanroommc.modularui.widget.ParentWidget; import com.cleanroommc.modularui.widget.sizer.Box; @@ -35,6 +34,10 @@ public static Flow column() { * Does not work with {@link Alignment.MainAxis#SPACE_BETWEEN} and {@link Alignment.MainAxis#SPACE_AROUND}. */ private int spaceBetween = 0; + /** + * Whether disabled child widgets should be collapsed for display. + */ + private boolean collapseDisabledChild = false; public Flow(GuiAxis axis) { this.axis = axis; @@ -44,39 +47,39 @@ public Flow(GuiAxis axis) { @Override public void layoutWidgets() { if (!hasChildren()) return; - boolean hasSize = resizer().isSizeCalculated(this.axis); - int size = getArea().getSize(axis); - Box padding = getArea().getPadding(); + 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.maa; if (!hasSize && maa != Alignment.MainAxis.START) { - if (flex().dependsOnChildren(axis)) { - maa = Alignment.MainAxis.START; - } else { - throw new IllegalStateException("Alignment.MainAxis other than start need the size to be calculated!"); - } - } - if (maa == Alignment.MainAxis.SPACE_BETWEEN && getChildren().size() == 1) { - maa = Alignment.MainAxis.CENTER; + maa = Alignment.MainAxis.START; } int space = this.spaceBetween; - int totalSize = 0; + int childrenSize = 0; int expandedAmount = 0; int amount = 0; - // calculate total size and maximum width + // calculate total size for (IWidget widget : getChildren()) { - // exclude self positioned (Y) children + // 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; amount++; if (widget.flex().isExpanded()) { expandedAmount++; - totalSize += widget.getArea().getMargin().getTotal(this.axis); + childrenSize += widget.getArea().getMargin().getTotal(this.axis); continue; } - totalSize += widget.getArea().requestedSize(this.axis); + childrenSize += widget.getArea().requestedSize(this.axis); } + if (amount <= 1 && maa == Alignment.MainAxis.SPACE_BETWEEN) { + maa = Alignment.MainAxis.CENTER; + } + 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; @@ -84,12 +87,14 @@ public void layoutWidgets() { space = 0; } } - totalSize += space * (getChildren().size() - 1); + childrenSize += space * spaceCount; if (expandedAmount > 0 && hasSize) { - int newSize = (size - totalSize - padding.getTotal(this.axis)) / expandedAmount; + int newSize = (size - childrenSize) / expandedAmount; for (IWidget widget : getChildren()) { - // exclude self positioned (Y) children + // 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()) { widget.getArea().setSize(this.axis, newSize); @@ -102,25 +107,27 @@ public void layoutWidgets() { int lastP = padding.getStart(this.axis); if (hasSize) { if (maa == Alignment.MainAxis.CENTER) { - lastP = (int) (size / 2f - totalSize / 2f); + lastP += (int) (size / 2f - childrenSize / 2f); } else if (maa == Alignment.MainAxis.END) { - lastP = size - totalSize; + lastP += size - childrenSize; } } for (IWidget widget : getChildren()) { - // exclude self positioned (Y) children + // 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; Box margin = widget.getArea().getMargin(); - // set calculated relative Y pos and set bottom margin for next widget + // 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 - totalSize) / (getChildren().size() - 1); + lastP += (size - childrenSize) / spaceCount; } } } @@ -132,26 +139,31 @@ public void postLayoutWidgets() { Box padding = getArea().getPadding(); boolean hasWidth = resizer().isSizeCalculated(other); for (IWidget widget : getChildren()) { - // exclude self positioned (Y) children + // exclude children whose position of main axis is fixed if (widget.flex().hasPos(this.axis)) continue; Box margin = widget.getArea().getMargin(); - // don't align auto positioned (X) children in X + // don't align auto positioned children in cross axis if (!widget.flex().hasPos(other) && widget.resizer().isSizeCalculated(other)) { - int x = margin.getStart(other) + padding.getStart(other); + int crossAxisPos = margin.getStart(other) + padding.getStart(other); if (hasWidth) { if (this.caa == Alignment.CrossAxis.CENTER) { - x = (int) (width / 2f - widget.getArea().getSize(other) / 2f); + crossAxisPos = (int) (width / 2f - widget.getArea().getSize(other) / 2f); } else if (this.caa == Alignment.CrossAxis.END) { - x = width - widget.getArea().getSize(other) - margin.getEnd(other) - padding.getStart(other); + crossAxisPos = width - widget.getArea().getSize(other) - margin.getEnd(other) - padding.getStart(other); } } - widget.getArea().setRelativePoint(other, x); + widget.getArea().setRelativePoint(other, crossAxisPos); widget.resizer().setPosResized(other, true); widget.resizer().setMarginPaddingApplied(other, true); } } } + @Override + public boolean shouldIgnoreChildSize(IWidget child) { + return this.collapseDisabledChild && !child.isEnabled(); + } + public Flow crossAxisAlignment(Alignment.CrossAxis caa) { this.caa = caa; return this; @@ -167,6 +179,14 @@ public Flow childPadding(int spaceBetween) { return this; } + /** + * Configures this widget to collapse disabled child widgets. + */ + public Flow collapseDisabledChild() { + this.collapseDisabledChild = true; + return this; + } + public GuiAxis getAxis() { return axis; } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java index 5119582f5..2f4261100 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java @@ -27,6 +27,7 @@ public class Grid extends AbstractScrollWidget implements ILayout private int minRowHeight = 5, minColWidth = 5; private Alignment alignment = Alignment.Center; private boolean dirty = false; + private boolean collapseDisabledChild = false; public Grid() { super(null, null); @@ -67,7 +68,7 @@ public void layoutWidgets() { if (i == 0) { colSizes.add(this.minColWidth); } - if (child != null && child.isEnabled()) { + if (!shouldIgnoreChildSize(child)) { rowSizes.set(i, Math.max(rowSizes.getInt(i), getElementHeight(child.getArea()))); colSizes.set(j, Math.max(colSizes.getInt(j), getElementWidth(child.getArea()))); } @@ -100,6 +101,11 @@ public void layoutWidgets() { } } + @Override + public boolean shouldIgnoreChildSize(IWidget child) { + return child == null || (this.collapseDisabledChild && !child.isEnabled()); + } + @Override public @NotNull List getChildren() { if (this.dirty) { @@ -120,7 +126,7 @@ public int getDefaultHeight() { for (List row : this.matrix) { int rowHeight = 0; for (IWidget child : row) { - if (child != null) { + if (!shouldIgnoreChildSize(child)) { rowHeight = Math.max(rowHeight, getElementHeight(child.getArea())); } } @@ -139,7 +145,7 @@ public int getDefaultWidth() { if (i == 0) { colSizes.add(this.minColWidth); } - if (child != null) { + if (!shouldIgnoreChildSize(child)) { colSizes.set(j, Math.max(colSizes.getInt(j), getElementWidth(child.getArea()))); } j++; @@ -282,6 +288,14 @@ public Grid minElementMarginBottom(int val) { return getThis(); } + /** + * Configures this widget to collapse row/column if all the child widgets in that axis are disabled. + */ + public Grid collapseDisabledChild() { + this.collapseDisabledChild = true; + return getThis(); + } + public static List> mapToMatrix(int rowLength, List list, IndexedElementMapper widgetCreator) { return mapToMatrix(rowLength, list.size(), i -> widgetCreator.apply(i, list.get(i))); }