From 665a9d2c27d26050c44a13d57582cd5cab95ff48 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 15 Jul 2022 00:56:45 +0200 Subject: [PATCH] More flexibility for placement --- .../ui/layout/components/Component.java | 50 +++++++++++++++++-- .../ui/layout/components/HorizontalStack.java | 49 +++++++++++++++--- .../ui/layout/components/VerticalScroll.java | 20 +++++--- .../ui/layout/components/VerticalStack.java | 42 ++++++++++++++-- .../components/render/ComponentRenderer.java | 4 +- .../betterx/ui/layout/values/Alignment.java | 5 ++ .../betterx/ui/layout/values/DynamicSize.java | 8 ++- .../betterx/ui/layout/values/SizeType.java | 34 ++++++++++++- 8 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/betterx/ui/layout/values/Alignment.java diff --git a/src/main/java/org/betterx/ui/layout/components/Component.java b/src/main/java/org/betterx/ui/layout/components/Component.java index 5dc95137..e830cde3 100644 --- a/src/main/java/org/betterx/ui/layout/components/Component.java +++ b/src/main/java/org/betterx/ui/layout/components/Component.java @@ -2,14 +2,19 @@ package org.betterx.ui.layout.components; import org.betterx.ui.layout.components.input.MouseEvent; import org.betterx.ui.layout.components.render.ComponentRenderer; +import org.betterx.ui.layout.values.Alignment; import org.betterx.ui.layout.values.DynamicSize; import org.betterx.ui.layout.values.Rectangle; +import com.mojang.blaze3d.vertex.PoseStack; + public abstract class Component implements ComponentWithBounds { protected final R renderer; protected final DynamicSize width; protected final DynamicSize height; protected Rectangle relativeBounds; + protected Alignment vAlign = Alignment.MIN; + protected Alignment hAlign = Alignment.MIN; public Component(DynamicSize width, DynamicSize height, R renderer) { this.width = width.attachComponent(this::getContentWidth); @@ -27,6 +32,10 @@ public abstract class Component implements Componen void setRelativeBounds(int left, int top) { relativeBounds = new Rectangle(left, top, width.calculatedSize(), height.calculatedSize()); + onBoundsChanged(); + } + + protected void onBoundsChanged() { } public Rectangle getRelativeBounds() { @@ -52,17 +61,20 @@ public abstract class Component implements Componen return height.calculatedSize(); } - public void render(Rectangle parentBounds, Rectangle clipRect) { + public void render(PoseStack poseStack, Rectangle parentBounds, Rectangle clipRect) { Rectangle r = relativeBounds.movedBy(parentBounds.left, parentBounds.top); Rectangle clip = r.intersect(clipRect); + poseStack.pushPose(); + poseStack.translate(relativeBounds.left, relativeBounds.top, 0); if (r.overlaps(clip)) { - renderInBounds(r, clip); + renderInBounds(poseStack, r, clip); } + poseStack.popPose(); } - protected void renderInBounds(Rectangle renderBounds, Rectangle clipRect) { + protected void renderInBounds(PoseStack poseStack, Rectangle renderBounds, Rectangle clipRect) { if (renderer != null) { - renderer.renderInBounds(renderBounds, clipRect); + renderer.renderInBounds(poseStack, renderBounds, clipRect); } } @@ -78,4 +90,34 @@ public abstract class Component implements Componen public String toString() { return super.toString() + "(" + relativeBounds + ", " + width.calculatedSize() + "x" + height.calculatedSize() + ")"; } + + public Component alignTop() { + vAlign = Alignment.MIN; + return this; + } + + public Component alignBottom() { + vAlign = Alignment.MAX; + return this; + } + + public Component centerVertical() { + vAlign = Alignment.CENTER; + return this; + } + + public Component alignLeft() { + hAlign = Alignment.MIN; + return this; + } + + public Component alignRight() { + hAlign = Alignment.MAX; + return this; + } + + public Component centerHorizontal() { + hAlign = Alignment.CENTER; + return this; + } } diff --git a/src/main/java/org/betterx/ui/layout/components/HorizontalStack.java b/src/main/java/org/betterx/ui/layout/components/HorizontalStack.java index 14e10b44..31d7ae05 100644 --- a/src/main/java/org/betterx/ui/layout/components/HorizontalStack.java +++ b/src/main/java/org/betterx/ui/layout/components/HorizontalStack.java @@ -1,8 +1,12 @@ package org.betterx.ui.layout.components; -import org.betterx.ui.layout.components.render.*; -import org.betterx.ui.layout.components.input.*; -import org.betterx.ui.layout.values.*; +import org.betterx.ui.layout.components.input.MouseEvent; +import org.betterx.ui.layout.components.render.ComponentRenderer; +import org.betterx.ui.layout.values.Alignment; +import org.betterx.ui.layout.values.DynamicSize; +import org.betterx.ui.layout.values.Rectangle; + +import com.mojang.blaze3d.vertex.PoseStack; import java.util.LinkedList; import java.util.List; @@ -36,8 +40,9 @@ public class HorizontalStack extends Component { @Override protected int updateContainerHeight(int containerHeight) { int myHeight = height.calculateOrFill(containerHeight); + components.stream().forEach(c -> c.height.calculateOrFill(myHeight)); for (Component c : components) { - c.updateContainerHeight(myHeight); + c.updateContainerHeight(c.height.calculatedSize()); } return myHeight; } @@ -49,7 +54,10 @@ public class HorizontalStack extends Component { int offset = 0; for (Component c : components) { - c.setRelativeBounds(offset, 0); + int delta = relativeBounds.height - c.height.calculatedSize(); + if (c.hAlign == Alignment.MIN) delta = 0; + else if (c.hAlign == Alignment.CENTER) delta /= 2; + c.setRelativeBounds(offset, delta); offset += c.relativeBounds.width; } } @@ -89,10 +97,10 @@ public class HorizontalStack extends Component { // } @Override - protected void renderInBounds(Rectangle renderBounds, Rectangle clipRect) { - super.renderInBounds(renderBounds, clipRect); + protected void renderInBounds(PoseStack poseStack, Rectangle renderBounds, Rectangle clipRect) { + super.renderInBounds(poseStack, renderBounds, clipRect); for (Component c : components) { - c.render(renderBounds, clipRect); + c.render(poseStack, renderBounds, clipRect); } } @@ -105,8 +113,33 @@ public class HorizontalStack extends Component { } } + public static HorizontalStack centered(Component c) { + return new HorizontalStack<>(DynamicSize.relative(1), DynamicSize.relative(1)).addFiller().add(c).addFiller(); + } + + public static HorizontalStack bottom(Component c) { + return new HorizontalStack<>(DynamicSize.relative(1), DynamicSize.relative(1)).add(c).addFiller(); + } + public HorizontalStack add(Component c) { this.components.add(c); return this; } + + private HorizontalStack addEmpty(DynamicSize size) { + this.components.add(new Empty(size, DynamicSize.fixed(0))); + return this; + } + + public HorizontalStack addSpacer(int size) { + return addEmpty(DynamicSize.fixed(size)); + } + + public HorizontalStack addSpacer(float percentage) { + return addEmpty(DynamicSize.relative(percentage)); + } + + public HorizontalStack addFiller() { + return addEmpty(DynamicSize.fill()); + } } diff --git a/src/main/java/org/betterx/ui/layout/components/VerticalScroll.java b/src/main/java/org/betterx/ui/layout/components/VerticalScroll.java index c530e088..8ee8d151 100644 --- a/src/main/java/org/betterx/ui/layout/components/VerticalScroll.java +++ b/src/main/java/org/betterx/ui/layout/components/VerticalScroll.java @@ -6,6 +6,8 @@ import org.betterx.ui.layout.components.render.ScrollerRenderer; import org.betterx.ui.layout.values.DynamicSize; import org.betterx.ui.layout.values.Rectangle; +import com.mojang.blaze3d.vertex.PoseStack; + public class VerticalScroll extends Component { protected Component child; protected final RS scrollerRenderer; @@ -33,7 +35,8 @@ public class VerticalScroll extends Component { @Override protected int updateContainerWidth(int containerWidth) { int myWidth = width.calculateOrFill(containerWidth); + components.stream().forEach(c -> c.width.calculateOrFill(myWidth)); for (Component c : components) { - c.updateContainerWidth(myWidth); + c.updateContainerWidth(c.width.calculatedSize()); } return myWidth; } @@ -50,7 +54,10 @@ public class VerticalStack extends Component { int offset = 0; for (Component c : components) { - c.setRelativeBounds(0, offset); + int delta = relativeBounds.width - c.width.calculatedSize(); + if (c.hAlign == Alignment.MIN) delta = 0; + else if (c.hAlign == Alignment.CENTER) delta /= 2; + c.setRelativeBounds(delta, offset); offset += c.relativeBounds.height; } } @@ -89,10 +96,10 @@ public class VerticalStack extends Component { } @Override - protected void renderInBounds(Rectangle renderBounds, Rectangle clipRect) { - super.renderInBounds(renderBounds, clipRect); + protected void renderInBounds(PoseStack poseStack, Rectangle renderBounds, Rectangle clipRect) { + super.renderInBounds(poseStack, renderBounds, clipRect); for (Component c : components) { - c.render(renderBounds, clipRect); + c.render(poseStack, renderBounds, clipRect); } } @@ -105,8 +112,33 @@ public class VerticalStack extends Component { } } + public static VerticalStack centered(Component c) { + return new VerticalStack<>(DynamicSize.relative(1), DynamicSize.relative(1)).addFiller().add(c).addFiller(); + } + + public static VerticalStack bottom(Component c) { + return new VerticalStack<>(DynamicSize.relative(1), DynamicSize.relative(1)).add(c).addFiller(); + } + public VerticalStack add(Component c) { this.components.add(c); return this; } + + private VerticalStack addEmpty(DynamicSize size) { + this.components.add(new Empty(DynamicSize.fixed(0), size)); + return this; + } + + public VerticalStack addSpacer(int size) { + return addEmpty(DynamicSize.fixed(size)); + } + + public VerticalStack addSpacer(float percentage) { + return addEmpty(DynamicSize.relative(percentage)); + } + + public VerticalStack addFiller() { + return addEmpty(DynamicSize.fill()); + } } diff --git a/src/main/java/org/betterx/ui/layout/components/render/ComponentRenderer.java b/src/main/java/org/betterx/ui/layout/components/render/ComponentRenderer.java index 283084d3..55c9eef3 100644 --- a/src/main/java/org/betterx/ui/layout/components/render/ComponentRenderer.java +++ b/src/main/java/org/betterx/ui/layout/components/render/ComponentRenderer.java @@ -2,6 +2,8 @@ package org.betterx.ui.layout.components.render; import org.betterx.ui.layout.values.Rectangle; +import com.mojang.blaze3d.vertex.PoseStack; + public interface ComponentRenderer { - void renderInBounds(Rectangle bounds, Rectangle clipRect); + void renderInBounds(PoseStack stack, Rectangle bounds, Rectangle clipRect); } diff --git a/src/main/java/org/betterx/ui/layout/values/Alignment.java b/src/main/java/org/betterx/ui/layout/values/Alignment.java new file mode 100644 index 00000000..580389f2 --- /dev/null +++ b/src/main/java/org/betterx/ui/layout/values/Alignment.java @@ -0,0 +1,5 @@ +package org.betterx.ui.layout.values; + +public enum Alignment { + MIN, MAX, CENTER +} diff --git a/src/main/java/org/betterx/ui/layout/values/DynamicSize.java b/src/main/java/org/betterx/ui/layout/values/DynamicSize.java index e383a6f6..3d8d9636 100644 --- a/src/main/java/org/betterx/ui/layout/values/DynamicSize.java +++ b/src/main/java/org/betterx/ui/layout/values/DynamicSize.java @@ -25,6 +25,10 @@ public class DynamicSize { return new DynamicSize(SizeType.FIT_CONTENT); } + public static DynamicSize fitOrFill() { + return new DynamicSize(SizeType.FIT_CONTENT_OR_FILL); + } + public int calculatedSize() { return calculatedSize; } @@ -67,7 +71,7 @@ public class DynamicSize { public int calculateOrFill(int parentSize) { calculatedSize = calculate(parentSize); - if (sizeType instanceof SizeType.Fill) { + if (sizeType instanceof SizeType.Fill || sizeType instanceof SizeType.FitContentOrFill) { calculatedSize = parentSize; } @@ -75,7 +79,7 @@ public class DynamicSize { } public double fillWeight() { - if (sizeType instanceof SizeType.Fill fill) { + if (sizeType instanceof SizeType.Fill fill || sizeType instanceof SizeType.FitContentOrFill) { return 1; } return 0; diff --git a/src/main/java/org/betterx/ui/layout/values/SizeType.java b/src/main/java/org/betterx/ui/layout/values/SizeType.java index 1f0f9fe0..b594418c 100644 --- a/src/main/java/org/betterx/ui/layout/values/SizeType.java +++ b/src/main/java/org/betterx/ui/layout/values/SizeType.java @@ -2,12 +2,34 @@ package org.betterx.ui.layout.values; public interface SizeType { FitContent FIT_CONTENT = new FitContent(); + FitContentOrFill FIT_CONTENT_OR_FILL = new FitContentOrFill(); Fill FILL = new Fill(); record Fill() implements SizeType { } - record FitContent(ContentSizeSupplier contentSize) implements SizeType { + class FitContentOrFill extends FitContent { + public FitContentOrFill(ContentSizeSupplier contentSize) { + super(contentSize); + } + + public FitContentOrFill() { + super(); + } + + @Override + public FitContentOrFill copyForSupplier(ContentSizeSupplier component) { + return new FitContentOrFill(component); + } + } + + class FitContent implements SizeType { + private final ContentSizeSupplier contentSize; + + public FitContent(ContentSizeSupplier contentSize) { + this.contentSize = contentSize; + } + @FunctionalInterface public interface ContentSizeSupplier { int get(); @@ -20,6 +42,16 @@ public interface SizeType { public FitContent copyForSupplier(ContentSizeSupplier component) { return new FitContent(component); } + + public ContentSizeSupplier contentSize() { + return contentSize; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } record Fixed(int size) implements SizeType {