From d19123c9493322b52a62cc83ea6894abff299c7b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 20 Jul 2022 00:40:16 +0200 Subject: [PATCH] [Feature] Container and Tabs Components --- .../bclib/client/gui/modmenu/MainScreen.java | 5 +- .../bclib/client/gui/modmenu/TestScreen.java | 45 +++- .../modmenu/ModMenuEntryPoint.java | 8 +- .../components/AbstractHorizontalStack.java | 87 ++++++ .../ui/layout/components/AbstractStack.java | 115 ++++---- .../components/AbstractVerticalStack.java | 83 ++++++ .../betterx/ui/layout/components/Button.java | 46 +++- .../ui/layout/components/Checkbox.java | 24 +- .../ui/layout/components/ColorPicker.java | 16 +- .../ui/layout/components/ColorSwatch.java | 4 + .../ui/layout/components/Container.java | 248 ++++++++++++++++++ .../betterx/ui/layout/components/HLine.java | 4 + .../ui/layout/components/HorizontalStack.java | 227 +++++++++++----- .../betterx/ui/layout/components/Panel.java | 69 ----- .../betterx/ui/layout/components/Range.java | 26 +- .../betterx/ui/layout/components/Tabs.java | 133 ++++++++++ .../betterx/ui/layout/components/VLine.java | 4 + .../ui/layout/components/VerticalScroll.java | 16 +- .../ui/layout/components/VerticalStack.java | 240 ++++++++++++----- .../input/RelativeContainerEventHandler.java | 2 +- .../components/render/ButtonRenderer.java | 19 ++ .../org/betterx/ui/vanilla/LayoutScreen.java | 5 +- 22 files changed, 1125 insertions(+), 301 deletions(-) create mode 100644 src/main/java/org/betterx/ui/layout/components/AbstractHorizontalStack.java create mode 100644 src/main/java/org/betterx/ui/layout/components/AbstractVerticalStack.java create mode 100644 src/main/java/org/betterx/ui/layout/components/Container.java create mode 100644 src/main/java/org/betterx/ui/layout/components/Tabs.java diff --git a/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java b/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java index d25eaaef..2f101503 100644 --- a/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java +++ b/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java @@ -61,7 +61,8 @@ public class MainScreen extends LayoutScreenWithIcon { Checkbox cb = row.addCheckbox( Value.fit(), Value.fit(), getComponent(config, option, "title"), - config.getRaw(option.token), + config.getRaw(option.token) + ).onChange( (caller, state) -> { config.set(option.token, state); updateEnabledState(); @@ -104,7 +105,7 @@ public class MainScreen extends LayoutScreenWithIcon { VerticalStack grid = new VerticalStack(Value.fill(), Value.fill()).setDebugName("main grid"); grid.addScrollable(content); grid.addSpacer(8); - grid.addButton(Value.fit(), Value.fit(), CommonComponents.GUI_DONE, (button) -> { + grid.addButton(Value.fit(), Value.fit(), CommonComponents.GUI_DONE).onPress((button) -> { Configs.CLIENT_CONFIG.saveChanges(); Configs.GENERATOR_CONFIG.saveChanges(); Configs.MAIN_CONFIG.saveChanges(); diff --git a/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java b/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java index f098a604..61d897e2 100644 --- a/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java +++ b/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java @@ -25,6 +25,21 @@ public class TestScreen extends LayoutScreen { @Override protected LayoutComponent initContent() { + VerticalStack page1 = new VerticalStack(Value.fill(), Value.fit()); + page1.addText(Value.fit(), Value.fit(), Component.literal("Page 1")).alignLeft().centerVertical(); + page1.addButton(Value.fit(), Value.fit(), Component.literal("A")).onPress((bt) -> System.out.println("A")) + .centerHorizontal(); + + VerticalStack page2 = new VerticalStack(Value.fill(), Value.fit()); + page2.addText(Value.fit(), Value.fit(), Component.literal("Page 2")).alignRight().centerVertical(); + page1.addButton(Value.fit(), Value.fit(), Component.literal("B")).onPress((bt) -> System.out.println("B")) + .centerHorizontal(); + + Container c = new Container(Value.fill(), Value.fixed(40)); + c.addChild(new Button(Value.fit(), Value.fit(), Component.literal("Containerd")).onPress(bt -> { + System.out.println("Containerd"); + }).centerHorizontal().centerVertical()); + c.setBackgroundColor(0x77000000); VerticalStack rows = new VerticalStack(Value.fit(), Value.fitOrFill()); rows.addFiller(); @@ -39,6 +54,8 @@ public class TestScreen extends LayoutScreen { ).centerHorizontal() ); rows.addHorizontalSeparator(16).alignTop(); + rows.add(new Tabs(Value.fixed(300), Value.fit()).addPage(Component.literal("PAGE 1"), page1) + .addPage(Component.literal("PAGE 2"), page2)); rows.add(new Input(Value.fitOrFill(), Value.fit(), Component.literal("Input"), "0xff00ff")); rows.add(new ColorSwatch(Value.fit(), Value.fit(), ColorUtil.LIGHT_PURPLE).centerHorizontal()); rows.add(new ColorPicker( @@ -53,6 +70,16 @@ public class TestScreen extends LayoutScreen { ).centerHorizontal().setColor(ColorUtil.BLUE) ); rows.addHLine(Value.fixed(32), Value.fixed(16)); + rows.add(c); + rows.addCheckbox( + Value.fitOrFill(), + Value.fit(), + Component.literal("Hide"), + false + ).onChange( + (cb, state) -> c.setVisible(!state) + ); + rows.addSpacer(16); rows.add(new Image( Value.fixed(24), Value.fixed(24), BCLib.makeID("icon.png"), @@ -70,7 +97,8 @@ public class TestScreen extends LayoutScreen { rows.add(new Range<>( Value.fill(), Value.fit(), Component.literal("Integer"), - 10, 90, 20, + 10, 90, 20 + ).onChange( (slider, value) -> { System.out.println(value); } @@ -79,7 +107,8 @@ public class TestScreen extends LayoutScreen { rows.add(new Range<>( Value.fill(), Value.fit(), Component.literal("Float"), - 10f, 90f, 20f, + 10f, 90f, 20f + ).onChange( (slider, value) -> { System.out.println(value); } @@ -88,7 +117,8 @@ public class TestScreen extends LayoutScreen { Checkbox cb1 = new Checkbox( Value.fit(), Value.fit(), Component.literal("Some Sub-State"), - false, true, + false, true + ).onChange( (checkbox, value) -> { System.out.println(value); } @@ -96,7 +126,8 @@ public class TestScreen extends LayoutScreen { rows.add(new Checkbox( Value.fit(), Value.fit(), Component.literal("Some Selectable State"), - false, true, + false, true + ).onChange( (checkbox, value) -> { System.out.println(value); cb1.setEnabled(value); @@ -106,7 +137,8 @@ public class TestScreen extends LayoutScreen { rows.addSpacer(16); rows.add(new Button( Value.fit(), Value.fit(), - Component.literal("test"), + Component.literal("test") + ).onPress( (bt) -> { System.out.println("clicked test"); } @@ -115,7 +147,8 @@ public class TestScreen extends LayoutScreen { rows.addSpacer(8); rows.add(new Button( Value.fit(), Value.fit(), - Component.literal("Hello World"), + Component.literal("Hello World") + ).onPress( (bt) -> { System.out.println("clicked hello"); } diff --git a/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java index 2e2bbeb8..9ea1f0e3 100644 --- a/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java +++ b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java @@ -1,8 +1,6 @@ package org.betterx.bclib.integration.modmenu; -import org.betterx.bclib.client.gui.modmenu.TestScreen; - -import net.minecraft.network.chat.Component; +import org.betterx.bclib.client.gui.modmenu.MainScreen; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; @@ -28,8 +26,8 @@ public class ModMenuEntryPoint implements ModMenuApi { @Override public ConfigScreenFactory getModConfigScreenFactory() { - return (parent) -> new TestScreen(parent, Component.literal("Hello Test")); - //return (parent) -> new MainScreen(parent); + //return (parent) -> new TestScreen(parent, Component.literal("Hello Test")); + return (parent) -> new MainScreen(parent); } diff --git a/src/main/java/org/betterx/ui/layout/components/AbstractHorizontalStack.java b/src/main/java/org/betterx/ui/layout/components/AbstractHorizontalStack.java new file mode 100644 index 00000000..b34cf98a --- /dev/null +++ b/src/main/java/org/betterx/ui/layout/components/AbstractHorizontalStack.java @@ -0,0 +1,87 @@ +package org.betterx.ui.layout.components; + +import org.betterx.ui.layout.components.input.RelativeContainerEventHandler; +import org.betterx.ui.layout.components.render.NullRenderer; +import org.betterx.ui.layout.values.Alignment; +import org.betterx.ui.layout.values.Value; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class AbstractHorizontalStack> extends AbstractStack implements RelativeContainerEventHandler { + public AbstractHorizontalStack(Value width, Value height) { + super(width, height); + } + + @Override + public int updateContainerWidth(int containerWidth) { + int myWidth = width.calculateOrFill(containerWidth); + int fixedWidth = components.stream().map(c -> c.width.calculate(myWidth)).reduce(0, Integer::sum); + + int freeWidth = Math.max(0, myWidth - fixedWidth); + fillWidth(myWidth, freeWidth); + + for (LayoutComponent c : components) { + c.updateContainerWidth(c.width.calculatedSize()); + } + + return myWidth; + } + + @Override + protected int updateContainerHeight(int containerHeight) { + int myHeight = height.calculateOrFill(containerHeight); + components.stream().forEach(c -> c.height.calculateOrFill(myHeight)); + for (LayoutComponent c : components) { + c.updateContainerHeight(c.height.calculatedSize()); + } + return myHeight; + } + + + @Override + void setRelativeBounds(int left, int top) { + super.setRelativeBounds(left, top); + + int offset = 0; + for (LayoutComponent c : components) { + int delta = relativeBounds.height - c.height.calculatedSize(); + if (c.vAlign == Alignment.MIN) delta = 0; + else if (c.vAlign == Alignment.CENTER) delta /= 2; + c.setRelativeBounds(offset, delta); + offset += c.relativeBounds.width; + } + } + + @Override + public int getContentWidth() { + int fixedWidth = components.stream().map(c -> c.width.calculateFixed()).reduce(0, Integer::sum); + double percentage = components.stream().map(c -> c.width.calculateRelative()).reduce(0.0, Double::sum); + + return (int) (fixedWidth / (1 - percentage)); + } + + @Override + public int getContentHeight() { + return components.stream().map(c -> c.height.calculateFixed()).reduce(0, Integer::max); + } + + + protected S addEmpty(Value size) { + this.components.add(new Empty(size, Value.fixed(0))); + return (S) this; + } + + protected VerticalStack addColumn(Value width, Value height) { + VerticalStack stack = new VerticalStack(width, height); + add(stack); + return stack; + } + + + protected VerticalStack addColumn() { + return addColumn(Value.fit(), Value.fit()); + } +} + diff --git a/src/main/java/org/betterx/ui/layout/components/AbstractStack.java b/src/main/java/org/betterx/ui/layout/components/AbstractStack.java index 095e395f..83fe1b0b 100644 --- a/src/main/java/org/betterx/ui/layout/components/AbstractStack.java +++ b/src/main/java/org/betterx/ui/layout/components/AbstractStack.java @@ -6,7 +6,6 @@ import org.betterx.ui.layout.components.render.NullRenderer; import org.betterx.ui.layout.values.Rectangle; import org.betterx.ui.layout.values.Size; import org.betterx.ui.layout.values.Value; -import org.betterx.ui.vanilla.Slider; import org.betterx.ui.vanilla.VanillaScrollerRenderer; import com.mojang.blaze3d.vertex.PoseStack; @@ -64,25 +63,6 @@ public abstract class AbstractStack c) { - this.components.add(c); - return (T) this; - } - - protected abstract T addEmpty(Value size); - - public T addSpacer(int size) { - return addEmpty(Value.fixed(size)); - } - - public T addSpacer(float percentage) { - return addEmpty(Value.relative(percentage)); - } - - public T addFiller() { - return addEmpty(Value.fill()); - } - @Override public List children() { return components; @@ -118,44 +98,65 @@ public abstract class AbstractStack c) { + this.components.add(c); + return (T) this; + } + + protected T addSpacer(int size) { + return addEmpty(Value.fixed(size)); + } + + protected T addSpacer(float percentage) { + return addEmpty(Value.relative(percentage)); + } + + protected T addFiller() { + return addEmpty(Value.fill()); + } + + + protected Image addIcon(ResourceLocation location, Size resourceSize) { Image i = new Image(Value.fixed(24), Value.fixed(24), location, resourceSize); add(i); return i; } - public Image addImage(Value width, Value height, ResourceLocation location, Size resourceSize) { + protected Image addImage(Value width, Value height, ResourceLocation location, Size resourceSize) { Image i = new Image(width, height, location, resourceSize); add(i); return i; } - public Checkbox addCheckbox( + protected Checkbox addCheckbox( Value width, Value height, Component component, - boolean selected, - Checkbox.SelectionChanged onSelectionChange + boolean selected ) { - Checkbox c = new Checkbox(width, height, component, selected, true, onSelectionChange); + Checkbox c = new Checkbox(width, height, component, selected, true); add(c); return c; } - public Button addButton( + protected Button addButton( Value width, Value height, - Component component, - net.minecraft.client.gui.components.Button.OnPress onPress + Component component ) { - Button b = new Button(width, height, component, onPress); + Button b = new Button(width, height, component); add(b); return b; } - public VerticalScroll addScrollable(LayoutComponent content) { + protected VerticalScroll addScrollable(LayoutComponent content) { return addScrollable(Value.fill(), Value.fill(), content); } - public VerticalScroll addScrollable( + protected VerticalScroll addScrollable( Value width, Value height, LayoutComponent content @@ -165,52 +166,49 @@ public abstract class AbstractStack addRange( + protected Range addRange( Value width, Value height, net.minecraft.network.chat.Component titleOrNull, - int minValue, int maxValue, int initialValue, - Slider.SliderValueChanged onChange + int minValue, int maxValue, int initialValue ) { - Range r = new Range<>(width, height, titleOrNull, minValue, maxValue, initialValue, onChange); + Range r = new Range<>(width, height, titleOrNull, minValue, maxValue, initialValue); add(r); return r; } - public Range addRange( + protected Range addRange( Value width, Value height, net.minecraft.network.chat.Component titleOrNull, - float minValue, float maxValue, float initialValue, - Slider.SliderValueChanged onChange + float minValue, float maxValue, float initialValue ) { - Range r = new Range<>(width, height, titleOrNull, minValue, maxValue, initialValue, onChange); + Range r = new Range<>(width, height, titleOrNull, minValue, maxValue, initialValue); add(r); return r; } - public Range addRange( + protected Range addRange( Value width, Value height, net.minecraft.network.chat.Component titleOrNull, - double minValue, double maxValue, double initialValue, - Slider.SliderValueChanged onChange + double minValue, double maxValue, double initialValue ) { - Range r = new Range<>(width, height, titleOrNull, minValue, maxValue, initialValue, onChange); + Range r = new Range<>(width, height, titleOrNull, minValue, maxValue, initialValue); add(r); return r; } - public Input addInput( + protected Input addInput( Value width, Value height, net.minecraft.network.chat.Component titleOrNull, String initialValue ) { Input i = new Input(width, height, titleOrNull, initialValue); @@ -218,13 +216,13 @@ public abstract class AbstractStack content) { + Container c = new Container(width, height); + c.addChild(content); + add(c); + return c; + } } diff --git a/src/main/java/org/betterx/ui/layout/components/AbstractVerticalStack.java b/src/main/java/org/betterx/ui/layout/components/AbstractVerticalStack.java new file mode 100644 index 00000000..a42b69d7 --- /dev/null +++ b/src/main/java/org/betterx/ui/layout/components/AbstractVerticalStack.java @@ -0,0 +1,83 @@ +package org.betterx.ui.layout.components; + +import org.betterx.ui.layout.components.input.RelativeContainerEventHandler; +import org.betterx.ui.layout.components.render.NullRenderer; +import org.betterx.ui.layout.values.Alignment; +import org.betterx.ui.layout.values.Value; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class AbstractVerticalStack> extends AbstractStack implements RelativeContainerEventHandler { + public AbstractVerticalStack(Value width, Value height) { + super(width, height); + } + + @Override + protected int updateContainerWidth(int containerWidth) { + int myWidth = width.calculateOrFill(containerWidth); + components.stream().forEach(c -> c.width.calculateOrFill(myWidth)); + for (LayoutComponent c : components) { + c.updateContainerWidth(c.width.calculatedSize()); + } + return myWidth; + } + + @Override + public int updateContainerHeight(int containerHeight) { + int myHeight = height.calculateOrFill(containerHeight); + int fixedHeight = components.stream().map(c -> c.height.calculate(myHeight)).reduce(0, Integer::sum); + + int freeHeight = Math.max(0, myHeight - fixedHeight); + fillHeight(myHeight, freeHeight); + + for (LayoutComponent c : components) { + c.updateContainerHeight(c.height.calculatedSize()); + } + + return myHeight; + } + + @Override + void setRelativeBounds(int left, int top) { + super.setRelativeBounds(left, top); + + int offset = 0; + for (LayoutComponent c : components) { + 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; + } + } + + @Override + public int getContentWidth() { + return components.stream().map(c -> c.width.calculateFixed()).reduce(0, Integer::max); + } + + @Override + public int getContentHeight() { + int fixedHeight = components.stream().map(c -> c.height.calculateFixed()).reduce(0, Integer::sum); + double percentage = components.stream().map(c -> c.height.calculateRelative()).reduce(0.0, Double::sum); + + return (int) (fixedHeight / (1 - percentage)); + } + + protected S addEmpty(Value size) { + this.components.add(new Empty(Value.fixed(0), size)); + return (S) this; + } + + protected HorizontalStack addRow(Value width, Value height) { + HorizontalStack stack = new HorizontalStack(width, height); + add(stack); + return stack; + } + + protected HorizontalStack addRow() { + return addRow(Value.fit(), Value.fit()); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/ui/layout/components/Button.java b/src/main/java/org/betterx/ui/layout/components/Button.java index 6f7c5940..a63e0eff 100644 --- a/src/main/java/org/betterx/ui/layout/components/Button.java +++ b/src/main/java/org/betterx/ui/layout/components/Button.java @@ -3,40 +3,68 @@ package org.betterx.ui.layout.components; import org.betterx.ui.layout.components.render.ButtonRenderer; import org.betterx.ui.layout.values.Value; +import com.mojang.blaze3d.vertex.PoseStack; + import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @Environment(EnvType.CLIENT) public class Button extends AbstractVanillaComponent { - final net.minecraft.client.gui.components.Button.OnPress onPress; - net.minecraft.client.gui.components.Button.OnTooltip onTooltip; + public static final OnTooltip NO_TOOLTIP = (button, poseStack, i, j) -> { + }; + public static final OnPress NO_ACTION = (button) -> { + }; + + @Environment(EnvType.CLIENT) + public interface OnTooltip { + void onTooltip(Button button, PoseStack poseStack, int mouseX, int mouseY); + } + + @Environment(EnvType.CLIENT) + public interface OnPress { + void onPress(Button button); + } + + OnPress onPress; + OnTooltip onTooltip; + + boolean glow = false; public Button( Value width, Value height, - net.minecraft.network.chat.Component component, - net.minecraft.client.gui.components.Button.OnPress onPress + net.minecraft.network.chat.Component component ) { super(width, height, new ButtonRenderer(), component); - this.onPress = onPress; - this.onTooltip = net.minecraft.client.gui.components.Button.NO_TOOLTIP; + this.onPress = NO_ACTION; + this.onTooltip = NO_TOOLTIP; } - public Button setOnToolTip(net.minecraft.client.gui.components.Button.OnTooltip onTooltip) { + public Button onPress(OnPress onPress) { + this.onPress = onPress; + return this; + } + + public Button onToolTip(OnTooltip onTooltip) { this.onTooltip = onTooltip; return this; } @Override protected net.minecraft.client.gui.components.Button createVanillaComponent() { + Button self = this; return new net.minecraft.client.gui.components.Button( 0, 0, relativeBounds.width, relativeBounds.height, component, - onPress, - onTooltip + (bt) -> onPress.onPress(self), + (bt, stack, x, y) -> onTooltip.onTooltip(self, stack, x, y) ); } + + public boolean isGlowing() { + return glow; + } } diff --git a/src/main/java/org/betterx/ui/layout/components/Checkbox.java b/src/main/java/org/betterx/ui/layout/components/Checkbox.java index 62a0605b..0bcdad2c 100644 --- a/src/main/java/org/betterx/ui/layout/components/Checkbox.java +++ b/src/main/java/org/betterx/ui/layout/components/Checkbox.java @@ -10,36 +10,44 @@ import net.fabricmc.api.Environment; @Environment(EnvType.CLIENT) public class Checkbox extends AbstractVanillaComponent { + public static SelectionChanged IGNORE_CHANGE = (a, b) -> { + }; + @FunctionalInterface public interface SelectionChanged { - void now(net.minecraft.client.gui.components.Checkbox checkBox, boolean selected); + void now(Checkbox checkBox, boolean selected); } private final boolean selected; private final boolean showLabel; - private final SelectionChanged onSelectionChange; + private SelectionChanged onSelectionChange; public Checkbox( Value width, Value height, Component component, - boolean selected, boolean showLabel, - SelectionChanged onSelectionChange + boolean selected, boolean showLabel ) { super(width, height, new CheckboxRenderer(), component); + onSelectionChange = IGNORE_CHANGE; this.selected = selected; this.showLabel = showLabel; - this.onSelectionChange = onSelectionChange; } - public boolean selected() { + public Checkbox onChange(SelectionChanged onSelectionChange) { + this.onSelectionChange = onSelectionChange; + return this; + } + + public boolean isChecked() { if (vanillaComponent != null) return vanillaComponent.selected(); return selected; } @Override protected net.minecraft.client.gui.components.Checkbox createVanillaComponent() { + Checkbox self = this; net.minecraft.client.gui.components.Checkbox cb = new net.minecraft.client.gui.components.Checkbox( 0, 0, relativeBounds.width, relativeBounds.height, @@ -50,11 +58,11 @@ public class Checkbox extends AbstractVanillaComponent { ColorSwatch swatch; Input input; @@ -29,14 +33,4 @@ public class ColorPicker extends HorizontalStack { swatch.setBorderColor(ColorUtil.RED); } } - - @Override - public int getContentWidth() { - return swatch.getContentWidth() + input.getContentWidth(); - } - - @Override - public int getContentHeight() { - return Math.max(swatch.getContentHeight(), input.getContentHeight()); - } } diff --git a/src/main/java/org/betterx/ui/layout/components/ColorSwatch.java b/src/main/java/org/betterx/ui/layout/components/ColorSwatch.java index 0e5401fc..ce30ad0c 100644 --- a/src/main/java/org/betterx/ui/layout/components/ColorSwatch.java +++ b/src/main/java/org/betterx/ui/layout/components/ColorSwatch.java @@ -8,6 +8,10 @@ import org.betterx.ui.layout.values.Value; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.GuiComponent; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) public class ColorSwatch extends CustomRenderComponent { private int color; private int borderColor = ColorUtil.BLACK; diff --git a/src/main/java/org/betterx/ui/layout/components/Container.java b/src/main/java/org/betterx/ui/layout/components/Container.java new file mode 100644 index 00000000..fd58d97c --- /dev/null +++ b/src/main/java/org/betterx/ui/layout/components/Container.java @@ -0,0 +1,248 @@ +package org.betterx.ui.layout.components; + +import org.betterx.ui.layout.components.input.RelativeContainerEventHandler; +import org.betterx.ui.layout.components.render.ComponentRenderer; +import org.betterx.ui.layout.components.render.RenderHelper; +import org.betterx.ui.layout.values.Alignment; +import org.betterx.ui.layout.values.Rectangle; +import org.betterx.ui.layout.values.Value; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.components.events.GuiEventListener; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.LinkedList; +import java.util.List; +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public class Container extends LayoutComponent implements RelativeContainerEventHandler { + public static class ContainerRenderer implements ComponentRenderer { + Container linkedContainer; + + @Override + public void renderInBounds( + PoseStack stack, + int mouseX, + int mouseY, + float deltaTicks, + Rectangle bounds, + Rectangle clipRect + ) { + if (linkedContainer != null) { + if ((linkedContainer.backgroundColor & 0xFF000000) != 0) + GuiComponent.fill(stack, 0, 0, bounds.width, bounds.height, linkedContainer.backgroundColor); + + if ((linkedContainer.outlineColor & 0xFF000000) != 0) + RenderHelper.outline(stack, 0, 0, bounds.width, bounds.height, linkedContainer.outlineColor); + } + } + } + + private final List> children = new LinkedList<>(); + boolean dragging = false; + GuiEventListener focused = null; + boolean visible = true; + + int padding = 0; + + int backgroundColor = 0; + int outlineColor = 0; + + public Container( + Value width, + Value height + ) { + super(width, height, new ContainerRenderer()); + renderer.linkedContainer = this; + } + + public Container addChild(LayoutComponent child) { + children.add(child); + return this; + } + + public Container setVisible(boolean v) { + this.visible = v; + return this; + } + + public boolean getVisible() { + return this.visible; + } + + public Container setBackgroundColor(int color) { + this.backgroundColor = color; + return this; + } + + public int getBackgroundColor() { + return backgroundColor; + } + + public Container setOutlineColor(int color) { + this.outlineColor = color; + return this; + } + + public int getOutlineColor() { + return outlineColor; + } + + public Container setPadding(int padding) { + this.padding = padding; + return this; + } + + public int getPadding() { + return padding; + } + + @Override + public int getContentWidth() { + return children.stream().map(LayoutComponent::getContentWidth).reduce(0, Math::max) + 2 * padding; + } + + @Override + public int getContentHeight() { + return children.stream().map(LayoutComponent::getContentHeight).reduce(0, Math::max) + 2 * padding; + } + + @Override + public Rectangle getInputBounds() { + return relativeBounds; + } + + @Override + public List children() { + return children; + } + + @Override + public boolean isDragging() { + return dragging; + } + + @Override + public void setDragging(boolean bl) { + dragging = bl; + } + + @Nullable + @Override + public GuiEventListener getFocused() { + return focused; + } + + @Override + public void setFocused(@Nullable GuiEventListener guiEventListener) { + focused = guiEventListener; + } + + @Override + protected int updateContainerWidth(int containerWidth) { + int myWidth = width.calculateOrFill(containerWidth); + for (var child : children) { + child.width.calculateOrFill(myWidth - 2 * padding); + child.updateContainerWidth(child.width.calculatedSize()); + } + return myWidth; + } + + @Override + protected int updateContainerHeight(int containerHeight) { + int myHeight = height.calculateOrFill(containerHeight); + for (var child : children) { + child.height.calculateOrFill(myHeight - 2 * padding); + child.updateContainerHeight(child.height.calculatedSize()); + } + return myHeight; + } + + @Override + protected void renderInBounds( + PoseStack poseStack, + int mouseX, + int mouseY, + float deltaTicks, + Rectangle renderBounds, + Rectangle clipRect + ) { + if (visible) { + super.renderInBounds(poseStack, mouseX, mouseY, deltaTicks, renderBounds, clipRect); + + setClippingRect(clipRect); + for (var child : children) { + child.render( + poseStack, mouseX, mouseY, deltaTicks, + renderBounds, clipRect + ); + } + setClippingRect(null); + } + } + + @Override + void setRelativeBounds(int left, int top) { + super.setRelativeBounds(left, top); + + for (var child : children) { + int childLeft = (relativeBounds.width - 2 * padding) - child.width.calculatedSize(); + if (child.hAlign == Alignment.MIN) childLeft = 0; + else if (child.hAlign == Alignment.CENTER) childLeft /= 2; + + int childTop = (relativeBounds.height - 2 * padding) - child.height.calculatedSize(); + if (child.vAlign == Alignment.MIN) childTop = 0; + else if (child.vAlign == Alignment.CENTER) childTop /= 2; + + child.setRelativeBounds(padding + childLeft, padding + childTop); + } + } + + @Override + public void mouseMoved(double d, double e) { + if (visible) + RelativeContainerEventHandler.super.mouseMoved(d, e); + } + + @Override + public boolean mouseClicked(double d, double e, int i) { + if (visible) + return RelativeContainerEventHandler.super.mouseClicked(d, e, i); + return false; + } + + @Override + public boolean mouseReleased(double d, double e, int i) { + if (visible) + return RelativeContainerEventHandler.super.mouseReleased(d, e, i); + return false; + } + + @Override + public boolean mouseScrolled(double d, double e, double f) { + if (visible) + return RelativeContainerEventHandler.super.mouseScrolled(d, e, f); + return false; + } + + @Override + public boolean mouseDragged(double d, double e, int i, double f, double g) { + if (visible) + return RelativeContainerEventHandler.super.mouseDragged(d, e, i, f, g); + return false; + } + + @Override + public boolean isMouseOver(double x, double y) { + if (visible) { + for (var child : children) { + return child.isMouseOver(x, y); + } + } + return relativeBounds.contains(x, y); + } +} diff --git a/src/main/java/org/betterx/ui/layout/components/HLine.java b/src/main/java/org/betterx/ui/layout/components/HLine.java index 556baa13..fb2093ed 100644 --- a/src/main/java/org/betterx/ui/layout/components/HLine.java +++ b/src/main/java/org/betterx/ui/layout/components/HLine.java @@ -8,6 +8,10 @@ import org.betterx.ui.layout.values.Value; import com.mojang.blaze3d.vertex.PoseStack; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) public class HLine extends CustomRenderComponent { private int color = ColorUtil.DEFAULT_TEXT; 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 cc4d5030..49231aeb 100644 --- a/src/main/java/org/betterx/ui/layout/components/HorizontalStack.java +++ b/src/main/java/org/betterx/ui/layout/components/HorizontalStack.java @@ -1,71 +1,22 @@ package org.betterx.ui.layout.components; -import org.betterx.ui.layout.components.input.RelativeContainerEventHandler; import org.betterx.ui.layout.components.render.NullRenderer; -import org.betterx.ui.layout.values.Alignment; +import org.betterx.ui.layout.values.Size; import org.betterx.ui.layout.values.Value; +import org.betterx.ui.vanilla.VanillaScrollerRenderer; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @Environment(EnvType.CLIENT) -public class HorizontalStack extends AbstractStack implements RelativeContainerEventHandler { +public class HorizontalStack extends AbstractHorizontalStack { public HorizontalStack(Value width, Value height) { super(width, height); } - @Override - public int updateContainerWidth(int containerWidth) { - int myWidth = width.calculateOrFill(containerWidth); - int fixedWidth = components.stream().map(c -> c.width.calculate(myWidth)).reduce(0, Integer::sum); - - int freeWidth = Math.max(0, myWidth - fixedWidth); - fillWidth(myWidth, freeWidth); - - for (LayoutComponent c : components) { - c.updateContainerWidth(c.width.calculatedSize()); - } - - return myWidth; - } - - @Override - protected int updateContainerHeight(int containerHeight) { - int myHeight = height.calculateOrFill(containerHeight); - components.stream().forEach(c -> c.height.calculateOrFill(myHeight)); - for (LayoutComponent c : components) { - c.updateContainerHeight(c.height.calculatedSize()); - } - return myHeight; - } - - - @Override - void setRelativeBounds(int left, int top) { - super.setRelativeBounds(left, top); - - int offset = 0; - for (LayoutComponent c : components) { - 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; - } - } - - @Override - public int getContentWidth() { - int fixedWidth = components.stream().map(c -> c.width.calculateFixed()).reduce(0, Integer::sum); - double percentage = components.stream().map(c -> c.width.calculateRelative()).reduce(0.0, Double::sum); - - return (int) (fixedWidth / (1 - percentage)); - } - - @Override - public int getContentHeight() { - return components.stream().map(c -> c.height.calculateFixed()).reduce(0, Integer::max); - } public static HorizontalStack centered(LayoutComponent c) { return new HorizontalStack(Value.fill(), Value.fill()).addFiller().add(c).addFiller(); @@ -80,14 +31,170 @@ public class HorizontalStack extends AbstractStack c) { + return super.add(c); } + public VerticalStack addColumn(Value width, Value height) { + return super.addColumn(width, height); + } public VerticalStack addColumn() { - return addColumn(Value.fit(), Value.fit()); + return super.addColumn(); + } + + @Override + public HorizontalStack addSpacer(int size) { + return super.addSpacer(size); + } + + @Override + public HorizontalStack addSpacer(float percentage) { + return super.addSpacer(percentage); + } + + @Override + public HorizontalStack addFiller() { + return super.addFiller(); + } + + @Override + public Button addButton( + Value width, + Value height, + Component component + ) { + return super.addButton(width, height, component); + } + + @Override + public Checkbox addCheckbox( + Value width, + Value height, + Component component, + boolean selected + ) { + return super.addCheckbox(width, height, component, selected); + } + + @Override + public ColorPicker addColorPicker(Value width, Value height, Component titleOrNull, int color) { + return super.addColorPicker(width, height, titleOrNull, color); + } + + @Override + public ColorSwatch addColorSwatch(Value width, Value height, int color) { + return super.addColorSwatch(width, height, color); + } + + @Override + public Container addContainered(Value width, Value height, LayoutComponent content) { + return super.addContainered(width, height, content); + } + + @Override + public HLine addHLine(Value width, Value height) { + return super.addHLine(width, height); + } + + @Override + public HLine addHorizontalLine(int height) { + return super.addHorizontalLine(height); + } + + @Override + public HLine addHorizontalSeparator(int height) { + return super.addHorizontalSeparator(height); + } + + @Override + public Image addIcon(ResourceLocation location, Size resourceSize) { + return super.addIcon(location, resourceSize); + } + + @Override + public Image addImage(Value width, Value height, ResourceLocation location, Size resourceSize) { + return super.addImage(width, height, location, resourceSize); + } + + @Override + public Input addInput(Value width, Value height, Component titleOrNull, String initialValue) { + return super.addInput(width, height, titleOrNull, initialValue); + } + + @Override + public MultiLineText addMultilineText(Value width, Value height, Component text) { + return super.addMultilineText(width, height, text); + } + + @Override + public Range addRange( + Value width, + Value height, + Component titleOrNull, + double minValue, + double maxValue, + double initialValue + ) { + return super.addRange(width, height, titleOrNull, minValue, maxValue, initialValue); + } + + @Override + public Range addRange( + Value width, + Value height, + Component titleOrNull, + float minValue, + float maxValue, + float initialValue + ) { + return super.addRange(width, height, titleOrNull, minValue, maxValue, initialValue); + } + + @Override + public Range addRange( + Value width, + Value height, + Component titleOrNull, + int minValue, + int maxValue, + int initialValue + ) { + return super.addRange(width, height, titleOrNull, minValue, maxValue, initialValue); + } + + @Override + public Text addText(Value width, Value height, Component text) { + return super.addText(width, height, text); + } + + @Override + public VerticalScroll addScrollable(LayoutComponent content) { + return super.addScrollable(content); + } + + @Override + public VLine addVerticalLine(int width) { + return super.addVerticalLine(width); + } + + @Override + public VLine addVerticalSeparator(int width) { + return super.addVerticalSeparator(width); + } + + @Override + public VLine addVLine(Value width, Value height) { + return super.addVLine(width, height); + } + + @Override + public VerticalScroll addScrollable( + Value width, + Value height, + LayoutComponent content + ) { + return super.addScrollable(width, height, content); } } diff --git a/src/main/java/org/betterx/ui/layout/components/Panel.java b/src/main/java/org/betterx/ui/layout/components/Panel.java index 9dae2761..ff098ca1 100644 --- a/src/main/java/org/betterx/ui/layout/components/Panel.java +++ b/src/main/java/org/betterx/ui/layout/components/Panel.java @@ -99,73 +99,4 @@ public class Panel implements ComponentWithBounds, RelativeContainerEventHandler child.render(poseStack, mouseX - bounds.left, mouseY - bounds.top, deltaTicks, bounds, bounds); } } - -// @Override -// public void mouseMoved(double x, double y) { -// if (child != null) -// child.mouseMoved(x - bounds.left, y - bounds.top); -// } -// -// @Override -// public boolean mouseClicked(double x, double y, int button) { -// if (child != null) -// return child.mouseClicked(x - bounds.left, y - bounds.top, button); -// return false; -// } -// -// @Override -// public boolean mouseReleased(double x, double y, int button) { -// if (child != null) -// return child.mouseReleased(x - bounds.left, y - bounds.top, button); -// return false; -// } -// -// @Override -// public boolean mouseDragged(double x, double y, int button, double x2, double y2) { -// if (child != null) -// return child.mouseDragged(x - bounds.left, y - bounds.top, button, x2 - bounds.left, y2 - bounds.top); -// return false; -// } -// -// @Override -// public boolean mouseScrolled(double x, double y, double f) { -// if (child != null) -// return child.mouseScrolled(x - bounds.left, y - bounds.top, f); -// return false; -// } -// -// @Override -// public boolean keyPressed(int i, int j, int k) { -// if (child != null) -// return child.keyPressed(i, j, k); -// return false; -// } -// -// @Override -// public boolean keyReleased(int i, int j, int k) { -// if (child != null) -// return child.keyReleased(i, j, k); -// return false; -// } -// -// @Override -// public boolean charTyped(char c, int i) { -// if (child != null) -// return child.charTyped(c, i); -// return false; -// } -// -// @Override -// public boolean changeFocus(boolean bl) { -// if (child != null) -// return child.changeFocus(bl); -// return false; -// } -// -// @Override -// public boolean isMouseOver(double x, double y) { -// if (child != null) -// return child.isMouseOver(x, y); -// return false; -// } } diff --git a/src/main/java/org/betterx/ui/layout/components/Range.java b/src/main/java/org/betterx/ui/layout/components/Range.java index 9a1892ff..9e8d199d 100644 --- a/src/main/java/org/betterx/ui/layout/components/Range.java +++ b/src/main/java/org/betterx/ui/layout/components/Range.java @@ -11,7 +11,12 @@ import net.fabricmc.api.Environment; @Environment(EnvType.CLIENT) public class Range extends AbstractVanillaComponent, Range> { - private final Slider.SliderValueChanged onChange; + @FunctionalInterface + public interface ValueChanged { + void now(Range range, N newValue); + } + + private ValueChanged onChange; private final N minValue; private final N maxValue; private final N initialValue; @@ -22,11 +27,11 @@ public class Range extends AbstractVanillaComponent, Component component, N minValue, N maxValue, - N initialValue, - Slider.SliderValueChanged onChange + N initialValue ) { super(width, height, new RangeRenderer<>(), component); - this.onChange = onChange; + this.onChange = (a, b) -> { + }; this.minValue = minValue; this.maxValue = maxValue; this.initialValue = initialValue; @@ -37,14 +42,19 @@ public class Range extends AbstractVanillaComponent, Value height, N minValue, N maxValue, - N initialValue, - Slider.SliderValueChanged onChange + N initialValue ) { - this(width, height, null, minValue, maxValue, initialValue, onChange); + this(width, height, null, minValue, maxValue, initialValue); + } + + public Range onChange(ValueChanged onChange) { + this.onChange = onChange; + return this; } @Override protected Slider createVanillaComponent() { + Range self = this; return new Slider<>( 0, 0, @@ -54,7 +64,7 @@ public class Range extends AbstractVanillaComponent, minValue, maxValue, initialValue, - onChange + (s, v) -> onChange.now(self, v) ); } diff --git a/src/main/java/org/betterx/ui/layout/components/Tabs.java b/src/main/java/org/betterx/ui/layout/components/Tabs.java new file mode 100644 index 00000000..94679fff --- /dev/null +++ b/src/main/java/org/betterx/ui/layout/components/Tabs.java @@ -0,0 +1,133 @@ +package org.betterx.ui.layout.components; + +import org.betterx.ui.layout.values.Value; + +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.LinkedList; +import java.util.List; + +@Environment(EnvType.CLIENT) +public class Tabs extends AbstractVerticalStack { + private final HorizontalStack buttons; + private final Container content; + + private final List pageList = new LinkedList<>(); + private final List