More flexibility for placement

This commit is contained in:
Frank 2022-07-15 00:56:45 +02:00
parent f89035f9f3
commit 665a9d2c27
8 changed files with 185 additions and 27 deletions

View file

@ -2,14 +2,19 @@ package org.betterx.ui.layout.components;
import org.betterx.ui.layout.components.input.MouseEvent; import org.betterx.ui.layout.components.input.MouseEvent;
import org.betterx.ui.layout.components.render.ComponentRenderer; 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.DynamicSize;
import org.betterx.ui.layout.values.Rectangle; import org.betterx.ui.layout.values.Rectangle;
import com.mojang.blaze3d.vertex.PoseStack;
public abstract class Component<R extends ComponentRenderer> implements ComponentWithBounds { public abstract class Component<R extends ComponentRenderer> implements ComponentWithBounds {
protected final R renderer; protected final R renderer;
protected final DynamicSize width; protected final DynamicSize width;
protected final DynamicSize height; protected final DynamicSize height;
protected Rectangle relativeBounds; protected Rectangle relativeBounds;
protected Alignment vAlign = Alignment.MIN;
protected Alignment hAlign = Alignment.MIN;
public Component(DynamicSize width, DynamicSize height, R renderer) { public Component(DynamicSize width, DynamicSize height, R renderer) {
this.width = width.attachComponent(this::getContentWidth); this.width = width.attachComponent(this::getContentWidth);
@ -27,6 +32,10 @@ public abstract class Component<R extends ComponentRenderer> implements Componen
void setRelativeBounds(int left, int top) { void setRelativeBounds(int left, int top) {
relativeBounds = new Rectangle(left, top, width.calculatedSize(), height.calculatedSize()); relativeBounds = new Rectangle(left, top, width.calculatedSize(), height.calculatedSize());
onBoundsChanged();
}
protected void onBoundsChanged() {
} }
public Rectangle getRelativeBounds() { public Rectangle getRelativeBounds() {
@ -52,17 +61,20 @@ public abstract class Component<R extends ComponentRenderer> implements Componen
return height.calculatedSize(); 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 r = relativeBounds.movedBy(parentBounds.left, parentBounds.top);
Rectangle clip = r.intersect(clipRect); Rectangle clip = r.intersect(clipRect);
poseStack.pushPose();
poseStack.translate(relativeBounds.left, relativeBounds.top, 0);
if (r.overlaps(clip)) { 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) { if (renderer != null) {
renderer.renderInBounds(renderBounds, clipRect); renderer.renderInBounds(poseStack, renderBounds, clipRect);
} }
} }
@ -78,4 +90,34 @@ public abstract class Component<R extends ComponentRenderer> implements Componen
public String toString() { public String toString() {
return super.toString() + "(" + relativeBounds + ", " + width.calculatedSize() + "x" + height.calculatedSize() + ")"; return super.toString() + "(" + relativeBounds + ", " + width.calculatedSize() + "x" + height.calculatedSize() + ")";
} }
public Component<R> alignTop() {
vAlign = Alignment.MIN;
return this;
}
public Component<R> alignBottom() {
vAlign = Alignment.MAX;
return this;
}
public Component<R> centerVertical() {
vAlign = Alignment.CENTER;
return this;
}
public Component<R> alignLeft() {
hAlign = Alignment.MIN;
return this;
}
public Component<R> alignRight() {
hAlign = Alignment.MAX;
return this;
}
public Component<R> centerHorizontal() {
hAlign = Alignment.CENTER;
return this;
}
} }

View file

@ -1,8 +1,12 @@
package org.betterx.ui.layout.components; package org.betterx.ui.layout.components;
import org.betterx.ui.layout.components.render.*; import org.betterx.ui.layout.components.input.MouseEvent;
import org.betterx.ui.layout.components.input.*; import org.betterx.ui.layout.components.render.ComponentRenderer;
import org.betterx.ui.layout.values.*; 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.LinkedList;
import java.util.List; import java.util.List;
@ -36,8 +40,9 @@ public class HorizontalStack<R extends ComponentRenderer> extends Component<R> {
@Override @Override
protected int updateContainerHeight(int containerHeight) { protected int updateContainerHeight(int containerHeight) {
int myHeight = height.calculateOrFill(containerHeight); int myHeight = height.calculateOrFill(containerHeight);
components.stream().forEach(c -> c.height.calculateOrFill(myHeight));
for (Component<?> c : components) { for (Component<?> c : components) {
c.updateContainerHeight(myHeight); c.updateContainerHeight(c.height.calculatedSize());
} }
return myHeight; return myHeight;
} }
@ -49,7 +54,10 @@ public class HorizontalStack<R extends ComponentRenderer> extends Component<R> {
int offset = 0; int offset = 0;
for (Component<?> c : components) { 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; offset += c.relativeBounds.width;
} }
} }
@ -89,10 +97,10 @@ public class HorizontalStack<R extends ComponentRenderer> extends Component<R> {
// } // }
@Override @Override
protected void renderInBounds(Rectangle renderBounds, Rectangle clipRect) { protected void renderInBounds(PoseStack poseStack, Rectangle renderBounds, Rectangle clipRect) {
super.renderInBounds(renderBounds, clipRect); super.renderInBounds(poseStack, renderBounds, clipRect);
for (Component<?> c : components) { for (Component<?> c : components) {
c.render(renderBounds, clipRect); c.render(poseStack, renderBounds, clipRect);
} }
} }
@ -105,8 +113,33 @@ public class HorizontalStack<R extends ComponentRenderer> extends Component<R> {
} }
} }
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<R> add(Component<?> c) { public HorizontalStack<R> add(Component<?> c) {
this.components.add(c); this.components.add(c);
return this; return this;
} }
private HorizontalStack<R> addEmpty(DynamicSize size) {
this.components.add(new Empty(size, DynamicSize.fixed(0)));
return this;
}
public HorizontalStack<R> addSpacer(int size) {
return addEmpty(DynamicSize.fixed(size));
}
public HorizontalStack<R> addSpacer(float percentage) {
return addEmpty(DynamicSize.relative(percentage));
}
public HorizontalStack<R> addFiller() {
return addEmpty(DynamicSize.fill());
}
} }

View file

@ -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.DynamicSize;
import org.betterx.ui.layout.values.Rectangle; import org.betterx.ui.layout.values.Rectangle;
import com.mojang.blaze3d.vertex.PoseStack;
public class VerticalScroll<R extends ComponentRenderer, RS extends ScrollerRenderer> extends Component<R> { public class VerticalScroll<R extends ComponentRenderer, RS extends ScrollerRenderer> extends Component<R> {
protected Component<?> child; protected Component<?> child;
protected final RS scrollerRenderer; protected final RS scrollerRenderer;
@ -33,7 +35,8 @@ public class VerticalScroll<R extends ComponentRenderer, RS extends ScrollerRend
protected int updateContainerWidth(int containerWidth) { protected int updateContainerWidth(int containerWidth) {
int myWidth = width.calculateOrFill(containerWidth); int myWidth = width.calculateOrFill(containerWidth);
if (child != null) { if (child != null) {
child.updateContainerWidth(myWidth); child.width.calculateOrFill(myWidth);
child.updateContainerWidth(child.width.calculatedSize());
} }
return myWidth; return myWidth;
} }
@ -42,7 +45,8 @@ public class VerticalScroll<R extends ComponentRenderer, RS extends ScrollerRend
protected int updateContainerHeight(int containerHeight) { protected int updateContainerHeight(int containerHeight) {
int myHeight = height.calculateOrFill(containerHeight); int myHeight = height.calculateOrFill(containerHeight);
if (child != null) { if (child != null) {
child.updateContainerHeight(myHeight); child.height.calculateOrFill(myHeight);
child.updateContainerHeight(child.height.calculatedSize());
} }
return myHeight; return myHeight;
} }
@ -77,17 +81,21 @@ public class VerticalScroll<R extends ComponentRenderer, RS extends ScrollerRend
} }
@Override @Override
protected void renderInBounds(Rectangle renderBounds, Rectangle clipRect) { protected void renderInBounds(PoseStack poseStack, Rectangle renderBounds, Rectangle clipRect) {
super.renderInBounds(renderBounds, clipRect); super.renderInBounds(poseStack, renderBounds, clipRect);
if (showScrollBar()) { if (showScrollBar()) {
if (child != null) { if (child != null) {
child.render(renderBounds.movedBy(0, scrollerOffset(), scrollerRenderer.scrollerWidth(), 0), clipRect); child.render(
poseStack,
renderBounds.movedBy(0, scrollerOffset(), scrollerRenderer.scrollerWidth(), 0),
clipRect
);
} }
scrollerRenderer.renderScrollBar(renderBounds, saveScrollerY(), scrollerHeight); scrollerRenderer.renderScrollBar(renderBounds, saveScrollerY(), scrollerHeight);
} else { } else {
if (child != null) { if (child != null) {
child.render(renderBounds, clipRect); child.render(poseStack, renderBounds, clipRect);
} }
} }
} }

View file

@ -3,9 +3,12 @@ package org.betterx.ui.layout.components;
import org.betterx.ui.layout.components.input.MouseEvent; import org.betterx.ui.layout.components.input.MouseEvent;
import org.betterx.ui.layout.components.render.ComponentRenderer; 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.DynamicSize;
import org.betterx.ui.layout.values.Rectangle; import org.betterx.ui.layout.values.Rectangle;
import com.mojang.blaze3d.vertex.PoseStack;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -23,8 +26,9 @@ public class VerticalStack<R extends ComponentRenderer> extends Component<R> {
@Override @Override
protected int updateContainerWidth(int containerWidth) { protected int updateContainerWidth(int containerWidth) {
int myWidth = width.calculateOrFill(containerWidth); int myWidth = width.calculateOrFill(containerWidth);
components.stream().forEach(c -> c.width.calculateOrFill(myWidth));
for (Component<?> c : components) { for (Component<?> c : components) {
c.updateContainerWidth(myWidth); c.updateContainerWidth(c.width.calculatedSize());
} }
return myWidth; return myWidth;
} }
@ -50,7 +54,10 @@ public class VerticalStack<R extends ComponentRenderer> extends Component<R> {
int offset = 0; int offset = 0;
for (Component<?> c : components) { 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; offset += c.relativeBounds.height;
} }
} }
@ -89,10 +96,10 @@ public class VerticalStack<R extends ComponentRenderer> extends Component<R> {
} }
@Override @Override
protected void renderInBounds(Rectangle renderBounds, Rectangle clipRect) { protected void renderInBounds(PoseStack poseStack, Rectangle renderBounds, Rectangle clipRect) {
super.renderInBounds(renderBounds, clipRect); super.renderInBounds(poseStack, renderBounds, clipRect);
for (Component<?> c : components) { for (Component<?> c : components) {
c.render(renderBounds, clipRect); c.render(poseStack, renderBounds, clipRect);
} }
} }
@ -105,8 +112,33 @@ public class VerticalStack<R extends ComponentRenderer> extends Component<R> {
} }
} }
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<R> add(Component<?> c) { public VerticalStack<R> add(Component<?> c) {
this.components.add(c); this.components.add(c);
return this; return this;
} }
private VerticalStack<R> addEmpty(DynamicSize size) {
this.components.add(new Empty(DynamicSize.fixed(0), size));
return this;
}
public VerticalStack<R> addSpacer(int size) {
return addEmpty(DynamicSize.fixed(size));
}
public VerticalStack<R> addSpacer(float percentage) {
return addEmpty(DynamicSize.relative(percentage));
}
public VerticalStack<R> addFiller() {
return addEmpty(DynamicSize.fill());
}
} }

View file

@ -2,6 +2,8 @@ package org.betterx.ui.layout.components.render;
import org.betterx.ui.layout.values.Rectangle; import org.betterx.ui.layout.values.Rectangle;
import com.mojang.blaze3d.vertex.PoseStack;
public interface ComponentRenderer { public interface ComponentRenderer {
void renderInBounds(Rectangle bounds, Rectangle clipRect); void renderInBounds(PoseStack stack, Rectangle bounds, Rectangle clipRect);
} }

View file

@ -0,0 +1,5 @@
package org.betterx.ui.layout.values;
public enum Alignment {
MIN, MAX, CENTER
}

View file

@ -25,6 +25,10 @@ public class DynamicSize {
return new DynamicSize(SizeType.FIT_CONTENT); return new DynamicSize(SizeType.FIT_CONTENT);
} }
public static DynamicSize fitOrFill() {
return new DynamicSize(SizeType.FIT_CONTENT_OR_FILL);
}
public int calculatedSize() { public int calculatedSize() {
return calculatedSize; return calculatedSize;
} }
@ -67,7 +71,7 @@ public class DynamicSize {
public int calculateOrFill(int parentSize) { public int calculateOrFill(int parentSize) {
calculatedSize = calculate(parentSize); calculatedSize = calculate(parentSize);
if (sizeType instanceof SizeType.Fill) { if (sizeType instanceof SizeType.Fill || sizeType instanceof SizeType.FitContentOrFill) {
calculatedSize = parentSize; calculatedSize = parentSize;
} }
@ -75,7 +79,7 @@ public class DynamicSize {
} }
public double fillWeight() { public double fillWeight() {
if (sizeType instanceof SizeType.Fill fill) { if (sizeType instanceof SizeType.Fill fill || sizeType instanceof SizeType.FitContentOrFill) {
return 1; return 1;
} }
return 0; return 0;

View file

@ -2,12 +2,34 @@ package org.betterx.ui.layout.values;
public interface SizeType { public interface SizeType {
FitContent FIT_CONTENT = new FitContent(); FitContent FIT_CONTENT = new FitContent();
FitContentOrFill FIT_CONTENT_OR_FILL = new FitContentOrFill();
Fill FILL = new Fill(); Fill FILL = new Fill();
record Fill() implements SizeType { 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 @FunctionalInterface
public interface ContentSizeSupplier { public interface ContentSizeSupplier {
int get(); int get();
@ -20,6 +42,16 @@ public interface SizeType {
public FitContent copyForSupplier(ContentSizeSupplier component) { public FitContent copyForSupplier(ContentSizeSupplier component) {
return new FitContent(component); return new FitContent(component);
} }
public ContentSizeSupplier contentSize() {
return contentSize;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
} }
record Fixed(int size) implements SizeType { record Fixed(int size) implements SizeType {