diff --git a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java index f1860280a..d157a6d3c 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/WidgetWithBounds.java @@ -25,6 +25,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import me.shedaniel.math.Rectangle; +import org.jetbrains.annotations.ApiStatus; public abstract class WidgetWithBounds extends Widget { public abstract Rectangle getBounds(); @@ -42,4 +43,34 @@ public void render(PoseStack matrices, Rectangle bounds, int mouseX, int mouseY, render(matrices, mouseX, mouseY, delta); getBounds().setBounds(clone); } + + @ApiStatus.Experimental + public final WidgetWithBounds withPadding(int padding) { + return Widgets.padded(padding, this); + } + + @ApiStatus.Experimental + public final WidgetWithBounds withPadding(int padX, int padY) { + return Widgets.padded(padX, padY, this); + } + + @ApiStatus.Experimental + public final WidgetWithBounds withPaddingHorizontal(int padX) { + return Widgets.padded(padX, 0, this); + } + + @ApiStatus.Experimental + public final WidgetWithBounds withPaddingVertical(int padY) { + return Widgets.padded(0, padY, this); + } + + @ApiStatus.Experimental + public final WidgetWithBounds withPadding(int padLeft, int padRight, int padTop, int padBottom) { + return Widgets.padded(padLeft, padRight, padTop, padBottom, this); + } + + @ApiStatus.Experimental + public final WidgetWithBounds withScissors() { + return Widgets.scissored(this); + } } diff --git a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widgets.java b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widgets.java index 44e18b32e..1de228f80 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widgets.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Widgets.java @@ -52,7 +52,8 @@ @Environment(EnvType.CLIENT) public final class Widgets { - private Widgets() {} + private Widgets() { + } public static Widget createDrawableWidget(DrawableConsumer drawable) { return ClientInternals.getWidgetsProvider().createDrawableWidget(drawable); @@ -63,10 +64,11 @@ public static WidgetWithBounds withTooltip(WidgetWithBounds widget, Component... } public static WidgetWithBounds withTooltip(WidgetWithBounds widget, Collection texts) { - return withBounds(concat( + return concatWithBounds( + widget::getBounds, widget, createTooltip(widget::getBounds, texts) - ), widget::getBounds); + ); } public static Widget createTooltip(Rectangle bounds, Component... texts) { @@ -161,11 +163,11 @@ public static WidgetWithBounds wrapWidgetWithBoundsSupplier(Widget widget, Suppl } public static WidgetWithBounds withBounds(Widget widget) { - return wrapWidgetWithBounds(widget, null); + return withBounds(widget, (Rectangle) null); } public static WidgetWithBounds withBounds(Widget widget, Rectangle bounds) { - return wrapWidgetWithBoundsSupplier(widget, bounds == null ? null : () -> bounds); + return withBounds(widget, bounds == null ? null : () -> bounds); } public static WidgetWithBounds withBounds(Widget widget, Supplier bounds) { @@ -304,6 +306,22 @@ public static Widget concat(List widgets) { return ClientInternals.getWidgetsProvider().concatWidgets(widgets); } + public static WidgetWithBounds concatWithBounds(Rectangle bounds, Widget... widgets) { + return concatWithBounds(bounds, Arrays.asList(widgets)); + } + + public static WidgetWithBounds concatWithBounds(Rectangle bounds, List widgets) { + return ClientInternals.getWidgetsProvider().concatWidgetsWithBounds(() -> bounds, widgets); + } + + public static WidgetWithBounds concatWithBounds(Supplier bounds, Widget... widgets) { + return concatWithBounds(bounds, Arrays.asList(widgets)); + } + + public static WidgetWithBounds concatWithBounds(Supplier bounds, List widgets) { + return ClientInternals.getWidgetsProvider().concatWidgetsWithBounds(bounds, widgets); + } + public static WidgetWithBounds noOp() { return ClientInternals.getWidgetsProvider().noOp(); } @@ -318,6 +336,11 @@ public static WidgetWithBounds scissored(Rectangle bounds, Widget widget) { return ClientInternals.getWidgetsProvider().wrapScissored(bounds, widget); } + @ApiStatus.Experimental + public static WidgetWithBounds scissored(WidgetWithBounds widget) { + return ClientInternals.getWidgetsProvider().wrapScissored(widget); + } + @ApiStatus.Experimental public static WidgetWithBounds padded(int padding, WidgetWithBounds widget) { return padded(padding, padding, padding, padding, widget); diff --git a/api/src/main/java/me/shedaniel/rei/impl/ClientInternals.java b/api/src/main/java/me/shedaniel/rei/impl/ClientInternals.java index b34041e35..8d684a148 100644 --- a/api/src/main/java/me/shedaniel/rei/impl/ClientInternals.java +++ b/api/src/main/java/me/shedaniel/rei/impl/ClientInternals.java @@ -219,12 +219,16 @@ public interface WidgetsProvider { Widget concatWidgets(List widgets); + WidgetWithBounds concatWidgetsWithBounds(Supplier bounds, List widgets); + WidgetWithBounds noOp(); WidgetWithBounds wrapOverflow(Rectangle bounds, WidgetWithBounds widget); WidgetWithBounds wrapScissored(Rectangle bounds, Widget widget); + WidgetWithBounds wrapScissored(WidgetWithBounds widget); + WidgetWithBounds wrapPadded(int padLeft, int padRight, int padTop, int padBottom, WidgetWithBounds widget); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java index 46e48b290..237cd64f7 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoriesListWidget.java @@ -24,22 +24,25 @@ package me.shedaniel.rei.impl.client.gui.config.components; import dev.architectury.utils.value.IntValue; +import me.shedaniel.clothconfig2.api.scroll.ScrollingContainer; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; import me.shedaniel.rei.impl.client.gui.config.options.OptionCategory; import me.shedaniel.rei.impl.client.gui.widget.ListWidget; +import me.shedaniel.rei.impl.client.gui.widget.ScrollableViewWidget; +import me.shedaniel.rei.impl.common.util.RectangleUtils; import java.util.List; public class ConfigCategoriesListWidget { public static Widget create(Rectangle bounds, List categories, IntValue selected) { - return ListWidget.builderOf(bounds, categories, - (index, entry) -> ConfigCategoryEntryWidget.create(entry)) - .paddingHorizontal(3) - .paddingVertical(5) + WidgetWithBounds list = ListWidget.builderOf(RectangleUtils.inset(bounds, 3, 5), categories, + (index, entry) -> ConfigCategoryEntryWidget.create(entry)) .gap(3) .isSelectable((index, entry) -> true) .selected(selected) .build(); + return ScrollableViewWidget.create(bounds, list, true); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoryEntryWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoryEntryWidget.java index 1bc56d216..177c0104d 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoryEntryWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/components/ConfigCategoryEntryWidget.java @@ -38,9 +38,10 @@ public static WidgetWithBounds create(OptionCategory category) { .leftAligned(); Font font = Minecraft.getInstance().font; Rectangle bounds = new Rectangle(0, 0, label.getBounds().getMaxX(), 7 * 3); - return Widgets.withBounds(Widgets.concat( + return Widgets.concatWithBounds( + bounds, label, Widgets.createTexturedWidget(category.getIcon(), new Rectangle(3, 3, 16, 16), 0, 0, 1, 1, 1, 1) - ), bounds); + ); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DelegateWidgetWithTranslate.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DelegateWidgetWithTranslate.java index a7b67f197..05026ab8b 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DelegateWidgetWithTranslate.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/DelegateWidgetWithTranslate.java @@ -42,6 +42,11 @@ public DelegateWidgetWithTranslate(WidgetWithBounds widget, Supplier t this.translate = translate; } + public DelegateWidgetWithTranslate(Widget widget, Supplier translate) { + super(widget); + this.translate = translate; + } + protected Matrix4f translate() { return translate.get(); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java index 3b91f2936..feda7efb4 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java @@ -61,7 +61,8 @@ @ApiStatus.Internal @Environment(EnvType.CLIENT) public final class InternalWidgets { - private InternalWidgets() {} + private InternalWidgets() { + } public static Widget createAutoCraftingButtonWidget(Rectangle displayBounds, Rectangle rectangle, Component text, Supplier displaySupplier, Supplier> idsSupplier, List setupDisplay, DisplayCategory category) { Button autoCraftingButton = Widgets.createButton(rectangle, text) @@ -162,6 +163,10 @@ public static Widget concatWidgets(List widgets) { return new MergedWidget(widgets); } + public static WidgetWithBounds concatWidgetsWithBounds(Supplier bounds, List widgets) { + return new MergedWidgetWithBounds(bounds, widgets); + } + private static class LateRenderableWidget extends DelegateWidget implements LateRenderable { private LateRenderableWidget(Widget widget) { super(widget); @@ -272,6 +277,11 @@ public Widget concatWidgets(List widgets) { return InternalWidgets.concatWidgets(widgets); } + @Override + public WidgetWithBounds concatWidgetsWithBounds(Supplier bounds, List widgets) { + return InternalWidgets.concatWidgetsWithBounds(bounds, widgets); + } + @Override public WidgetWithBounds noOp() { return NoOpWidget.INSTANCE; @@ -287,6 +297,11 @@ public WidgetWithBounds wrapScissored(Rectangle bounds, Widget widget) { return new ScissoredWidget(bounds, widget); } + @Override + public WidgetWithBounds wrapScissored(WidgetWithBounds widget) { + return new ScissoredWidget(widget); + } + @Override public WidgetWithBounds wrapPadded(int padLeft, int padRight, int padTop, int padBottom, WidgetWithBounds widget) { return new PaddedWidget(padLeft, padRight, padTop, padBottom, widget); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ListWidget.java index af1ddcd75..7f66ff7cb 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ListWidget.java @@ -47,8 +47,6 @@ public static WidgetBuilder builderOfWidgets(Rec public static abstract class AbstractBuilder> { protected final Rectangle bounds; - protected int paddingHorizontal = 0; - protected int paddingVertical = 0; protected IntValue selected = new IntValue() { private int value; @@ -64,22 +62,11 @@ public int getAsInt() { }; protected int gap = 4; protected boolean calculateTotalHeightDynamically = false; - protected boolean background = true; protected AbstractBuilder(Rectangle bounds) { this.bounds = bounds; } - public SELF paddingHorizontal(int paddingHorizontal) { - this.paddingHorizontal = paddingHorizontal; - return (SELF) this; - } - - public SELF paddingVertical(int paddingVertical) { - this.paddingVertical = paddingVertical; - return (SELF) this; - } - public SELF selected(IntValue selected) { this.selected = selected; return (SELF) this; @@ -100,11 +87,6 @@ public SELF calculateTotalHeightDynamically(boolean calculateTotalHeightDynamica return (SELF) this; } - public SELF background(boolean background) { - this.background = background; - return (SELF) this; - } - public abstract WidgetWithBounds build(); } @@ -126,8 +108,8 @@ public Builder isSelectable(ListEntryPredicate isSelectable) { @Override public WidgetWithBounds build() { - return ListWidget.create(bounds, entries, paddingHorizontal, paddingVertical, selected, gap, calculateTotalHeightDynamically, - cellRenderer, isSelectable, background); + return ListWidget.create(bounds, entries, selected, gap, calculateTotalHeightDynamically, + cellRenderer, isSelectable); } } @@ -147,23 +129,22 @@ public WidgetBuilder isSelectable(ListEntryPredicate isSelectable) { @Override public WidgetWithBounds build() { - return ListWidget.create(bounds, entries, paddingHorizontal, paddingVertical, selected, gap, calculateTotalHeightDynamically, - isSelectable, background); + return ListWidget.create(bounds, entries, selected, gap, calculateTotalHeightDynamically, + isSelectable); } } - public static WidgetWithBounds create(Rectangle bounds, List entries, int paddingHorizontal, int paddingVertical, - IntValue selected, int gap, boolean calculateTotalHeightDynamically, ListCellRenderer cellRenderer, - ListEntryPredicate isSelectable, boolean background) { + public static WidgetWithBounds create(Rectangle bounds, List entries, IntValue selected, int gap, + boolean calculateTotalHeightDynamically, ListCellRenderer cellRenderer, + ListEntryPredicate isSelectable) { int[] i = {0}; - return create(bounds, CollectionUtils.map(entries, entry -> cellRenderer.create(i[0]++, entry)), paddingHorizontal, paddingVertical, - selected, gap, calculateTotalHeightDynamically, (index, entry) -> isSelectable.test(index, entries.get(index)), background); + return create(bounds, CollectionUtils.map(entries, entry -> cellRenderer.create(i[0]++, entry)), + selected, gap, calculateTotalHeightDynamically, (index, entry) -> isSelectable.test(index, entries.get(index))); } - public static WidgetWithBounds create(Rectangle bounds, List entries, int paddingHorizontal, int paddingVertical, - IntValue selected, int gap, boolean calculateTotalHeightDynamically, - ListEntryPredicate isSelectable, boolean background) { - int[] height = {collectTotalHeight(entries, gap) + paddingVertical * 2}; + public static WidgetWithBounds create(Rectangle bounds, List entries, IntValue selected, int gap, + boolean calculateTotalHeightDynamically, ListEntryPredicate isSelectable) { + int[] height = {collectTotalHeight(entries, gap)}; Rectangle innerBounds = bounds.clone(); if (height[0] > bounds.getHeight()) { @@ -171,22 +152,22 @@ public static WidgetWithBounds create(Rectangle bou } int[] i = {0}; - List> wrapped = CollectionUtils.map(entries, cell -> new CellWidget<>(innerBounds, paddingHorizontal, i[0]++, cell, selected, entries, isSelectable)); + List> wrapped = CollectionUtils.map(entries, cell -> new CellWidget<>(innerBounds, i[0]++, cell, selected, entries, isSelectable)); Widget update = Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> { if (calculateTotalHeightDynamically) { - height[0] = collectTotalHeight(entries, gap) + paddingVertical * 2; + height[0] = collectTotalHeight(entries, gap); innerBounds.width = height[0] > bounds.getHeight() ? bounds.width - 6 : bounds.width; } - int y = bounds.y + paddingVertical; + int y = bounds.y; for (CellWidget cell : wrapped) { - cell.position.move(bounds.x + paddingHorizontal, y); + cell.position.move(bounds.x, y); y += (calculateTotalHeightDynamically ? cell.getBounds().getHeight() : cell.height) + gap; } if (selected.getAsInt() != -1) { CellWidget cellWidget = wrapped.get(selected.getAsInt()); - int x1 = innerBounds.x + paddingHorizontal, x2 = innerBounds.getMaxX() - paddingHorizontal; + int x1 = innerBounds.x, x2 = innerBounds.getMaxX(); boolean contains = new Rectangle(x1 - 1, cellWidget.position.y - 1, x2 - x1 + 2, cellWidget.getBounds().height + 2).contains(mouseX, mouseY); GuiComponent.fill(matrices, x1 - 1, cellWidget.position.y - 1, x2 + 1, cellWidget.position.y + cellWidget.getBounds().height + 1, contains ? 0xFFD0D0D0 : 0xFF8F8F8F); @@ -197,13 +178,12 @@ public static WidgetWithBounds create(Rectangle bou List innerWidgets = new ArrayList<>(); innerWidgets.add(update); innerWidgets.addAll(wrapped); - return ScrollableViewWidget.create(bounds, Widgets.withBounds(Widgets.concat(innerWidgets), - () -> new Rectangle(bounds.x, bounds.y, bounds.width, height[0])), background); + return Widgets.concatWithBounds(() -> new Rectangle(bounds.x, bounds.y, bounds.width, height[0]), + innerWidgets); } private static class CellWidget extends DelegateWidgetWithTranslate { private final Rectangle bounds; - private final int paddingHorizontal; private final int index; private final Point position = new Point(); private final int height; @@ -211,10 +191,9 @@ private static class CellWidget extends DelegateWidgetWithTranslate { private final List list; private final ListEntryPredicate isSelectable; - public CellWidget(Rectangle bounds, int paddingHorizontal, int index, WidgetWithBounds widget, IntValue selected, List list, ListEntryPredicate isSelectable) { + public CellWidget(Rectangle bounds, int index, WidgetWithBounds widget, IntValue selected, List list, ListEntryPredicate isSelectable) { super(widget, Matrix4f::new); this.bounds = bounds; - this.paddingHorizontal = paddingHorizontal; this.index = index; this.height = widget.getBounds().getHeight(); this.selected = selected; @@ -232,7 +211,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { boolean clicked = super.mouseClicked(mouseX, mouseY, button); Rectangle bounds = delegate().getBounds(); - if (clicked || new Rectangle(position.x, position.y, this.bounds.width - paddingHorizontal * 2, bounds.height).contains(mouseX, mouseY)) { + if (clicked || new Rectangle(position.x, position.y, this.bounds.width, bounds.height).contains(mouseX, mouseY)) { if (isSelectable.test(index, list.get(index))) { selected.accept(index); if (!clicked) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/MergedWidgetWithBounds.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/MergedWidgetWithBounds.java new file mode 100644 index 000000000..99c7975c4 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/MergedWidgetWithBounds.java @@ -0,0 +1,132 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.widget; + +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; +import me.shedaniel.rei.api.common.util.CollectionUtils; +import net.minecraft.client.gui.components.events.GuiEventListener; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Supplier; + +public class MergedWidgetWithBounds extends WidgetWithBounds { + private final Supplier bounds; + private final List widgets; + + public MergedWidgetWithBounds(Supplier bounds, List widgets) { + this.bounds = bounds; + this.widgets = widgets; + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + for (Widget widget : widgets) { + widget.setZ(getZ()); + widget.render(matrices, mouseX, mouseY, delta); + } + } + + @Override + public List children() { + return widgets; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + for (Widget widget : this.widgets) { + if (widget.mouseScrolled(mouseX, mouseY, amount)) + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + for (Widget widget : this.widgets) { + if (widget.keyPressed(keyCode, scanCode, modifiers)) + return true; + } + return false; + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + for (Widget widget : this.widgets) { + if (widget.keyReleased(keyCode, scanCode, modifiers)) + return true; + } + return false; + } + + @Override + public boolean charTyped(char character, int modifiers) { + for (Widget widget : this.widgets) { + if (widget.charTyped(character, modifiers)) + return true; + } + return false; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + for (Widget widget : this.widgets) { + if (widget.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) + return true; + } + return false; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + for (Widget widget : this.widgets) { + if (widget.mouseReleased(mouseX, mouseY, button)) + return true; + } + return false; + } + + @Override + public double getZRenderingPriority() { + return CollectionUtils.max(widgets, Comparator.comparingDouble(Widget::getZRenderingPriority)) + .map(Widget::getZRenderingPriority).orElse(0.0); + } + + @Override + public Rectangle getBounds() { + return bounds.get(); + } + + @Deprecated + @Override + public void render(PoseStack matrices, Rectangle bounds, int mouseX, int mouseY, float delta) { + Rectangle clone = getBounds().clone(); + getBounds().setBounds(bounds); + render(matrices, mouseX, mouseY, delta); + getBounds().setBounds(clone); + } +} \ No newline at end of file diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScissoredWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScissoredWidget.java index c06dfe862..0e745f225 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScissoredWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScissoredWidget.java @@ -28,19 +28,28 @@ import me.shedaniel.rei.api.client.gui.widgets.CloseableScissors; import me.shedaniel.rei.api.client.gui.widgets.DelegateWidget; import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; + +import java.util.function.Supplier; public class ScissoredWidget extends DelegateWidget { - private final Rectangle bounds; + private final Supplier bounds; public ScissoredWidget(Rectangle bounds, Widget widget) { super(widget); - this.bounds = bounds; + this.bounds = () -> bounds; + } + + public ScissoredWidget(WidgetWithBounds widget) { + super(widget); + this.bounds = widget::getBounds; } @Override public void render(PoseStack poseStack, int mouseX, int mouseY, float delta) { - try (CloseableScissors scissors = scissor(poseStack, this.bounds)) { - boolean containsMouse = this.bounds.contains(mouseX, mouseY); + try (CloseableScissors scissors = scissor(poseStack, this.bounds.get())) { + boolean containsMouse = this.delegate() instanceof WidgetWithBounds withBounds ? withBounds.containsMouse(mouseX, mouseY) + : this.bounds.get().contains(mouseX, mouseY); if (containsMouse) { super.render(poseStack, mouseX, mouseY, delta); @@ -52,7 +61,7 @@ public void render(PoseStack poseStack, int mouseX, int mouseY, float delta) { @Override public Rectangle getBounds() { - return bounds; + return bounds.get(); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScrollableViewWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScrollableViewWidget.java index 8d4cf677d..b8f2552a5 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScrollableViewWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/ScrollableViewWidget.java @@ -39,7 +39,11 @@ public class ScrollableViewWidget { public static WidgetWithBounds create(Rectangle bounds, WidgetWithBounds inner, boolean background) { - ScrollingContainer scrolling = new ScrollingContainer() { + return create(bounds, inner, new ScrollingContainer[1], background); + } + + public static WidgetWithBounds create(Rectangle bounds, WidgetWithBounds inner, ScrollingContainer[] scrollingRef, boolean background) { + scrollingRef[0] = new ScrollingContainer() { @Override public Rectangle getBounds() { return bounds; @@ -54,16 +58,16 @@ public int getMaxScrollHeight() { List widgets = new ArrayList<>(); if (background) { - widgets.add(HoleWidget.create(bounds, scrolling::scrollAmountInt, 32)); + widgets.add(HoleWidget.create(bounds, scrollingRef[0]::scrollAmountInt, 32)); } - widgets.add(Widgets.scissored(scrolling.getScissorBounds(), Widgets.withTranslate(inner, - () -> Matrix4f.createTranslateMatrix(0, -scrolling.scrollAmountInt(), 0)))); + widgets.add(Widgets.scissored(scrollingRef[0].getScissorBounds(), Widgets.withTranslate(inner, + () -> Matrix4f.createTranslateMatrix(0, -scrollingRef[0].scrollAmountInt(), 0)))); widgets.add(Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> { - scrolling.updatePosition(delta); - scrolling.renderScrollBar(); + scrollingRef[0].updatePosition(delta); + scrollingRef[0].renderScrollBar(); })); - widgets.add(createScrollerWidget(bounds, scrolling)); + widgets.add(createScrollerWidget(bounds, scrollingRef[0])); return Widgets.withBounds(Widgets.concat(widgets), bounds); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VStackWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VStackWidget.java new file mode 100644 index 000000000..d82c2ba3b --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/VStackWidget.java @@ -0,0 +1,157 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.widget; + +import com.google.common.collect.ForwardingList; +import com.mojang.math.Matrix4f; +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; +import me.shedaniel.rei.api.client.gui.widgets.Widgets; +import me.shedaniel.rei.api.common.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +public class VStackWidget { + public static Builder builder(int x, int y) { + return builder(new Point(x, y)); + } + + public static Builder builder(Point point) { + return new Builder<>(point); + } + + public static class Builder extends ForwardingList { + private final Point point; + private final List entries = new ArrayList<>(); + private int gap; + private boolean calculateWidthDynamically = false; + private boolean calculateTotalHeightDynamically = false; + + public Builder(Point point) { + this.point = point; + } + + @Override + protected List delegate() { + return entries; + } + + public Builder gap(int gap) { + this.gap = gap; + return this; + } + + public Builder calculateWidthDynamically() { + this.calculateWidthDynamically = true; + return this; + } + + public Builder calculateTotalHeightDynamically() { + this.calculateTotalHeightDynamically = true; + return this; + } + + public WidgetWithBounds build() { + return VStackWidget.create(point, entries, gap, calculateWidthDynamically, calculateTotalHeightDynamically); + } + } + + public static WidgetWithBounds create(Point point, List entries, + int gap, boolean calculateWidthDynamically, boolean calculateTotalHeightDynamically) { + Rectangle bounds = new Rectangle(point.x, point.y, collectMaximumWidth(entries), collectTotalHeight(entries, gap)); + + List> wrapped = CollectionUtils.map(entries, CellWidget::new); + Widget update = Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> { + if (calculateWidthDynamically) { + bounds.width = collectMaximumWidth(entries); + } + + if (calculateTotalHeightDynamically) { + bounds.height = collectTotalHeight(entries, gap); + } + + bounds.move(point.x, point.y); + + int y = bounds.y; + for (CellWidget cell : wrapped) { + cell.position.move(bounds.x, y); + y += (calculateTotalHeightDynamically ? cell.getBounds().getHeight() : cell.height) + gap; + } + }); + List innerWidgets = new ArrayList<>(); + innerWidgets.add(update); + innerWidgets.addAll(wrapped); + return Widgets.concatWithBounds(bounds, innerWidgets); + } + + private static class CellWidget extends DelegateWidgetWithTranslate { + private final Point position = new Point(); + private final int height; + + public CellWidget(WidgetWithBounds widget) { + super(widget, Matrix4f::new); + this.height = widget.getBounds().getHeight(); + } + + @Override + public WidgetWithBounds delegate() { + return (WidgetWithBounds) super.delegate(); + } + + @Override + protected Matrix4f translate() { + Rectangle bounds = delegate().getBounds(); + return Matrix4f.createTranslateMatrix(position.x - bounds.x, position.y - bounds.y, 0); + } + } + + private static int collectMaximumWidth(List cells) { + int width = 0; + for (WidgetWithBounds cell : cells) { + width = Math.max(width, cell.getBounds().getWidth()); + } + return width; + } + + private static int collectTotalHeight(List cells, int gap) { + int height = Math.max(0, (cells.size() - 1) * gap); + for (WidgetWithBounds cell : cells) { + height += cell.getBounds().getHeight(); + } + return height; + } + + @FunctionalInterface + public interface ListCellRenderer { + WidgetWithBounds create(int index, T entry); + } + + @FunctionalInterface + public interface ListEntryPredicate { + boolean test(int index, T entry); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/util/RectangleUtils.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/util/RectangleUtils.java index 8918ad32d..1e54c1129 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/util/RectangleUtils.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/util/RectangleUtils.java @@ -29,6 +29,18 @@ import java.util.stream.Stream; public class RectangleUtils { + public static Rectangle inset(Rectangle rectangle, int inset) { + return inset(rectangle, inset, inset); + } + + public static Rectangle inset(Rectangle rectangle, int insetX, int insetY) { + return inset(rectangle, insetX, insetY, insetX, insetY); + } + + public static Rectangle inset(Rectangle rectangle, int insetLeft, int insetTop, int insetRight, int insetBottom) { + return new Rectangle(rectangle.x + insetLeft, rectangle.y + insetTop, rectangle.width - insetLeft - insetRight, rectangle.height - insetTop - insetBottom); + } + public static Rectangle excludeZones(Rectangle rectangle, Stream exclusionZones) { return exclusionZones .filter(rect -> rect.intersects(rectangle))