Skip to content

Commit f42ea21

Browse files
authored
Allow collapsing disabled child widgets + sizing fixes (#130)
* Allow collapsing disabled child widgets * Fix disabled child's margin being considered * Correct comment and variable name * Remove seemingly unreachable code * Fix space size * Fix division by zero * Fix padding * oops
1 parent 70d589a commit f42ea21

File tree

7 files changed

+112
-38
lines changed

7 files changed

+112
-38
lines changed

src/main/java/com/cleanroommc/modularui/api/layout/ILayoutWidget.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.cleanroommc.modularui.api.layout;
22

3+
import com.cleanroommc.modularui.api.widget.IWidget;
4+
35
/**
46
* This is responsible for laying out widgets.
57
*/
@@ -17,4 +19,14 @@ public interface ILayoutWidget {
1719
* The last call guarantees, that this widget is fully calculated.
1820
*/
1921
default void postLayoutWidgets() {}
22+
23+
/**
24+
* Called when determining wrapping size of this widget.
25+
* If this method returns true, size and margin of the queried child will be ignored for calculation.
26+
* Typically return true when the child is disabled and you want to collapse it for layout.
27+
* This method should also be used for layouting children with {@link #layoutWidgets} if it might return true.
28+
*/
29+
default boolean shouldIgnoreChildSize(IWidget child) {
30+
return false;
31+
}
2032
}

src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) {
2121

2222
public static final Area SHARED = new Area();
2323

24+
public static final Area ZERO = new Area();
25+
2426
/**
2527
* relative position (in most cases the direct parent)
2628
*/

src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class Box {
1010

1111
public static final Box SHARED = new Box();
1212

13+
public static final Box ZERO = new Box();
14+
1315
public int left;
1416
public int top;
1517
public int right;
@@ -74,4 +76,14 @@ public int getStart(GuiAxis axis) {
7476
public int getEnd(GuiAxis axis) {
7577
return axis.isHorizontal() ? this.right : this.bottom;
7678
}
79+
80+
@Override
81+
public String toString() {
82+
return "Box{" +
83+
"left=" + left +
84+
", top=" + top +
85+
", right=" + right +
86+
", bottom=" + bottom +
87+
'}';
88+
}
7789
}

src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,9 +429,10 @@ private void coverChildrenForLayout(IWidget widget) {
429429
int x1 = Integer.MIN_VALUE, y1 = Integer.MIN_VALUE;
430430
int w = 0, h = 0;
431431
for (IWidget child : children) {
432-
Box margin = child.getArea().getMargin();
432+
final boolean shouldIgnoreChildSize = ((ILayoutWidget) this.parent).shouldIgnoreChildSize(child);
433+
Box margin = shouldIgnoreChildSize ? Box.ZERO : child.getArea().getMargin();
433434
IResizeable resizeable = child.resizer();
434-
Area area = child.getArea();
435+
Area area = shouldIgnoreChildSize ? Area.ZERO : child.getArea();
435436
if (this.x.dependsOnChildren() && resizeable.isWidthCalculated()) {
436437
w = Math.max(w, area.requestedWidth() + padding.horizontal());
437438
if (resizeable.isXCalculated()) {

src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
import com.cleanroommc.modularui.screen.viewport.ModularGuiContext;
99
import com.cleanroommc.modularui.theme.WidgetTheme;
1010
import com.cleanroommc.modularui.widget.AbstractScrollWidget;
11-
import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData;
1211
import com.cleanroommc.modularui.widget.scroll.ScrollData;
1312
import com.cleanroommc.modularui.widget.scroll.VerticalScrollData;
1413

1514
import it.unimi.dsi.fastutil.ints.IntArrayList;
1615
import it.unimi.dsi.fastutil.ints.IntList;
17-
import org.jetbrains.annotations.Nullable;
1816

1917
import java.util.function.IntFunction;
2018

@@ -29,6 +27,7 @@ public class ListWidget<I extends IWidget, W extends ListWidget<I, W>> extends A
2927
private ScrollData scrollData;
3028
private IIcon childSeparator;
3129
private final IntList separatorPositions = new IntArrayList();
30+
private boolean collapseDisabledChild = false;
3231

3332
public ListWidget() {
3433
super(null, null);
@@ -70,6 +69,7 @@ public void layoutWidgets() {
7069
int separatorSize = getSeparatorSize();
7170
int p = getArea().getPadding().getStart(axis);
7271
for (IWidget widget : getChildren()) {
72+
if (shouldIgnoreChildSize(widget)) continue;
7373
if (axis.isVertical() ?
7474
widget.getFlex().hasYPos() || !widget.resizer().isHeightCalculated() :
7575
widget.getFlex().hasXPos() || !widget.resizer().isWidthCalculated()) {
@@ -89,6 +89,11 @@ public void layoutWidgets() {
8989
getScrollData().setScrollSize(p + getArea().getPadding().getEnd(axis));
9090
}
9191

92+
@Override
93+
public boolean shouldIgnoreChildSize(IWidget child) {
94+
return this.collapseDisabledChild && !child.isEnabled();
95+
}
96+
9297
@Override
9398
public boolean addChild(I child, int index) {
9499
return super.addChild(child, index);
@@ -148,4 +153,12 @@ public W children(int amount, IntFunction<I> widgetCreator) {
148153
}
149154
return getThis();
150155
}
156+
157+
/**
158+
* Configures this widget to collapse disabled child widgets.
159+
*/
160+
public W collapseDisabledChild() {
161+
this.collapseDisabledChild = true;
162+
return getThis();
163+
}
151164
}

src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.cleanroommc.modularui.api.layout.ILayoutWidget;
55
import com.cleanroommc.modularui.api.widget.IWidget;
66
import com.cleanroommc.modularui.utils.Alignment;
7-
import com.cleanroommc.modularui.widget.AbstractParentWidget;
87
import com.cleanroommc.modularui.widget.ParentWidget;
98
import com.cleanroommc.modularui.widget.sizer.Box;
109

@@ -35,6 +34,10 @@ public static Flow column() {
3534
* Does not work with {@link Alignment.MainAxis#SPACE_BETWEEN} and {@link Alignment.MainAxis#SPACE_AROUND}.
3635
*/
3736
private int spaceBetween = 0;
37+
/**
38+
* Whether disabled child widgets should be collapsed for display.
39+
*/
40+
private boolean collapseDisabledChild = false;
3841

3942
public Flow(GuiAxis axis) {
4043
this.axis = axis;
@@ -44,52 +47,54 @@ public Flow(GuiAxis axis) {
4447
@Override
4548
public void layoutWidgets() {
4649
if (!hasChildren()) return;
47-
boolean hasSize = resizer().isSizeCalculated(this.axis);
48-
int size = getArea().getSize(axis);
49-
Box padding = getArea().getPadding();
50+
final boolean hasSize = resizer().isSizeCalculated(this.axis);
51+
final Box padding = getArea().getPadding();
52+
final int size = getArea().getSize(axis) - padding.getTotal(this.axis);
5053
Alignment.MainAxis maa = this.maa;
5154
if (!hasSize && maa != Alignment.MainAxis.START) {
52-
if (flex().dependsOnChildren(axis)) {
53-
maa = Alignment.MainAxis.START;
54-
} else {
55-
throw new IllegalStateException("Alignment.MainAxis other than start need the size to be calculated!");
56-
}
57-
}
58-
if (maa == Alignment.MainAxis.SPACE_BETWEEN && getChildren().size() == 1) {
59-
maa = Alignment.MainAxis.CENTER;
55+
maa = Alignment.MainAxis.START;
6056
}
6157
int space = this.spaceBetween;
6258

63-
int totalSize = 0;
59+
int childrenSize = 0;
6460
int expandedAmount = 0;
6561
int amount = 0;
6662

67-
// calculate total size and maximum width
63+
// calculate total size
6864
for (IWidget widget : getChildren()) {
69-
// exclude self positioned (Y) children
65+
// ignore disabled child if configured as such
66+
if (shouldIgnoreChildSize(widget)) continue;
67+
// exclude children whose position of main axis is fixed
7068
if (widget.flex().hasPos(this.axis)) continue;
7169
amount++;
7270
if (widget.flex().isExpanded()) {
7371
expandedAmount++;
74-
totalSize += widget.getArea().getMargin().getTotal(this.axis);
72+
childrenSize += widget.getArea().getMargin().getTotal(this.axis);
7573
continue;
7674
}
77-
totalSize += widget.getArea().requestedSize(this.axis);
75+
childrenSize += widget.getArea().requestedSize(this.axis);
7876
}
7977

78+
if (amount <= 1 && maa == Alignment.MainAxis.SPACE_BETWEEN) {
79+
maa = Alignment.MainAxis.CENTER;
80+
}
81+
final int spaceCount = Math.max(amount - 1, 0);
82+
8083
if (maa == Alignment.MainAxis.SPACE_BETWEEN || maa == Alignment.MainAxis.SPACE_AROUND) {
8184
if (expandedAmount > 0) {
8285
maa = Alignment.MainAxis.START;
8386
} else {
8487
space = 0;
8588
}
8689
}
87-
totalSize += space * (getChildren().size() - 1);
90+
childrenSize += space * spaceCount;
8891

8992
if (expandedAmount > 0 && hasSize) {
90-
int newSize = (size - totalSize - padding.getTotal(this.axis)) / expandedAmount;
93+
int newSize = (size - childrenSize) / expandedAmount;
9194
for (IWidget widget : getChildren()) {
92-
// exclude self positioned (Y) children
95+
// ignore disabled child if configured as such
96+
if (shouldIgnoreChildSize(widget)) continue;
97+
// exclude children whose position of main axis is fixed
9398
if (widget.flex().hasPos(this.axis)) continue;
9499
if (widget.flex().isExpanded()) {
95100
widget.getArea().setSize(this.axis, newSize);
@@ -102,25 +107,27 @@ public void layoutWidgets() {
102107
int lastP = padding.getStart(this.axis);
103108
if (hasSize) {
104109
if (maa == Alignment.MainAxis.CENTER) {
105-
lastP = (int) (size / 2f - totalSize / 2f);
110+
lastP += (int) (size / 2f - childrenSize / 2f);
106111
} else if (maa == Alignment.MainAxis.END) {
107-
lastP = size - totalSize;
112+
lastP += size - childrenSize;
108113
}
109114
}
110115

111116
for (IWidget widget : getChildren()) {
112-
// exclude self positioned (Y) children
117+
// ignore disabled child if configured as such
118+
if (shouldIgnoreChildSize(widget)) continue;
119+
// exclude children whose position of main axis is fixed
113120
if (widget.flex().hasPos(this.axis)) continue;
114121
Box margin = widget.getArea().getMargin();
115122

116-
// set calculated relative Y pos and set bottom margin for next widget
123+
// set calculated relative main axis pos and set end margin for next widget
117124
widget.getArea().setRelativePoint(this.axis, lastP + margin.getStart(this.axis));
118125
widget.resizer().setPosResized(this.axis, true);
119126
widget.resizer().setMarginPaddingApplied(this.axis, true);
120127

121128
lastP += widget.getArea().requestedSize(this.axis) + space;
122129
if (hasSize && maa == Alignment.MainAxis.SPACE_BETWEEN) {
123-
lastP += (size - totalSize) / (getChildren().size() - 1);
130+
lastP += (size - childrenSize) / spaceCount;
124131
}
125132
}
126133
}
@@ -132,26 +139,31 @@ public void postLayoutWidgets() {
132139
Box padding = getArea().getPadding();
133140
boolean hasWidth = resizer().isSizeCalculated(other);
134141
for (IWidget widget : getChildren()) {
135-
// exclude self positioned (Y) children
142+
// exclude children whose position of main axis is fixed
136143
if (widget.flex().hasPos(this.axis)) continue;
137144
Box margin = widget.getArea().getMargin();
138-
// don't align auto positioned (X) children in X
145+
// don't align auto positioned children in cross axis
139146
if (!widget.flex().hasPos(other) && widget.resizer().isSizeCalculated(other)) {
140-
int x = margin.getStart(other) + padding.getStart(other);
147+
int crossAxisPos = margin.getStart(other) + padding.getStart(other);
141148
if (hasWidth) {
142149
if (this.caa == Alignment.CrossAxis.CENTER) {
143-
x = (int) (width / 2f - widget.getArea().getSize(other) / 2f);
150+
crossAxisPos = (int) (width / 2f - widget.getArea().getSize(other) / 2f);
144151
} else if (this.caa == Alignment.CrossAxis.END) {
145-
x = width - widget.getArea().getSize(other) - margin.getEnd(other) - padding.getStart(other);
152+
crossAxisPos = width - widget.getArea().getSize(other) - margin.getEnd(other) - padding.getStart(other);
146153
}
147154
}
148-
widget.getArea().setRelativePoint(other, x);
155+
widget.getArea().setRelativePoint(other, crossAxisPos);
149156
widget.resizer().setPosResized(other, true);
150157
widget.resizer().setMarginPaddingApplied(other, true);
151158
}
152159
}
153160
}
154161

162+
@Override
163+
public boolean shouldIgnoreChildSize(IWidget child) {
164+
return this.collapseDisabledChild && !child.isEnabled();
165+
}
166+
155167
public Flow crossAxisAlignment(Alignment.CrossAxis caa) {
156168
this.caa = caa;
157169
return this;
@@ -167,6 +179,14 @@ public Flow childPadding(int spaceBetween) {
167179
return this;
168180
}
169181

182+
/**
183+
* Configures this widget to collapse disabled child widgets.
184+
*/
185+
public Flow collapseDisabledChild() {
186+
this.collapseDisabledChild = true;
187+
return this;
188+
}
189+
170190
public GuiAxis getAxis() {
171191
return axis;
172192
}

src/main/java/com/cleanroommc/modularui/widgets/layout/Grid.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class Grid extends AbstractScrollWidget<IWidget, Grid> implements ILayout
2727
private int minRowHeight = 5, minColWidth = 5;
2828
private Alignment alignment = Alignment.Center;
2929
private boolean dirty = false;
30+
private boolean collapseDisabledChild = false;
3031

3132
public Grid() {
3233
super(null, null);
@@ -67,7 +68,7 @@ public void layoutWidgets() {
6768
if (i == 0) {
6869
colSizes.add(this.minColWidth);
6970
}
70-
if (child != null && child.isEnabled()) {
71+
if (!shouldIgnoreChildSize(child)) {
7172
rowSizes.set(i, Math.max(rowSizes.getInt(i), getElementHeight(child.getArea())));
7273
colSizes.set(j, Math.max(colSizes.getInt(j), getElementWidth(child.getArea())));
7374
}
@@ -100,6 +101,11 @@ public void layoutWidgets() {
100101
}
101102
}
102103

104+
@Override
105+
public boolean shouldIgnoreChildSize(IWidget child) {
106+
return child == null || (this.collapseDisabledChild && !child.isEnabled());
107+
}
108+
103109
@Override
104110
public @NotNull List<IWidget> getChildren() {
105111
if (this.dirty) {
@@ -120,7 +126,7 @@ public int getDefaultHeight() {
120126
for (List<IWidget> row : this.matrix) {
121127
int rowHeight = 0;
122128
for (IWidget child : row) {
123-
if (child != null) {
129+
if (!shouldIgnoreChildSize(child)) {
124130
rowHeight = Math.max(rowHeight, getElementHeight(child.getArea()));
125131
}
126132
}
@@ -139,7 +145,7 @@ public int getDefaultWidth() {
139145
if (i == 0) {
140146
colSizes.add(this.minColWidth);
141147
}
142-
if (child != null) {
148+
if (!shouldIgnoreChildSize(child)) {
143149
colSizes.set(j, Math.max(colSizes.getInt(j), getElementWidth(child.getArea())));
144150
}
145151
j++;
@@ -282,6 +288,14 @@ public Grid minElementMarginBottom(int val) {
282288
return getThis();
283289
}
284290

291+
/**
292+
* Configures this widget to collapse row/column if all the child widgets in that axis are disabled.
293+
*/
294+
public Grid collapseDisabledChild() {
295+
this.collapseDisabledChild = true;
296+
return getThis();
297+
}
298+
285299
public static <T, I extends IWidget> List<List<I>> mapToMatrix(int rowLength, List<T> list, IndexedElementMapper<T, I> widgetCreator) {
286300
return mapToMatrix(rowLength, list.size(), i -> widgetCreator.apply(i, list.get(i)));
287301
}

0 commit comments

Comments
 (0)