diff options
| author | shedaniel <daniel@shedaniel.me> | 2022-08-28 18:50:24 +0900 |
|---|---|---|
| committer | shedaniel <daniel@shedaniel.me> | 2022-08-28 18:50:24 +0900 |
| commit | 94e323f75c17e297c33fba1d3afb5c47ae66a8ad (patch) | |
| tree | 3c6cb6c1721fbda792050b96707fad50d1db64e9 /runtime-frontend/overlay/src/main/java | |
| parent | f5061d043cac7f0217bbe9d4c041b77f9ef911a1 (diff) | |
| download | RoughlyEnoughItems-94e323f75c17e297c33fba1d3afb5c47ae66a8ad.tar.gz RoughlyEnoughItems-94e323f75c17e297c33fba1d3afb5c47ae66a8ad.tar.bz2 RoughlyEnoughItems-94e323f75c17e297c33fba1d3afb5c47ae66a8ad.zip | |
Separate EntryListWidget and FavoritesListWidget more
Diffstat (limited to 'runtime-frontend/overlay/src/main/java')
26 files changed, 2617 insertions, 20 deletions
diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/dragging/DraggingContextImpl.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/dragging/DraggingContextImpl.java new file mode 100644 index 000000000..ea78fa47a --- /dev/null +++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/dragging/DraggingContextImpl.java @@ -0,0 +1,311 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.dragging; + +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.api.animator.NumberAnimator; +import me.shedaniel.clothconfig2.api.animator.ValueAnimator; +import me.shedaniel.math.FloatingRectangle; +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.drag.DraggableBoundsProvider; +import me.shedaniel.rei.api.client.gui.drag.DraggableStack; +import me.shedaniel.rei.api.client.gui.drag.DraggedAcceptorResult; +import me.shedaniel.rei.api.client.gui.drag.DraggingContext; +import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponent; +import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentProvider; +import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentVisitor; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.impl.client.gui.InternalCursorState; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Supplier; + +@SuppressWarnings("UnstableApiUsage") +public class DraggingContextImpl extends Widget implements DraggingContext<Screen> { + private DraggableComponentProvider<Screen, ?> provider; + private DraggableComponentVisitor<Screen> visitor; + @Nullable + private DraggableEntry entry; + private final List<RenderBackEntry> backToOriginals = new ArrayList<>(); + private final Set<ShapeBounds> bounds = new HashSet<>(); + + public void set(DraggableComponentProvider<Screen, ?> provider, DraggableComponentVisitor<Screen> visitor) { + this.provider = provider; + this.visitor = visitor; + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + Integer hash = null; + + if (entry != null) { + if (!entry.dragging) { + Point startPoint = entry.start; + double xDistance = Math.abs(startPoint.x - mouseX); + double yDistance = Math.abs(startPoint.y - mouseY); + double requiredDistance = 8; + + if (xDistance * xDistance + yDistance * yDistance > requiredDistance * requiredDistance) { + entry.dragging = true; + entry.startDragging = Util.getMillis(); + entry.component.drag(); + } + } + + if (entry.dragging) { + matrices.pushPose(); + matrices.translate(0, 0, 600); + entry.bounds.update(delta); + int width = entry.component.getWidth(); + int height = entry.component.getHeight(); + Vec2 mouseStartOffset = entry.mouseStartOffset; + entry.bounds.setTo(new FloatingRectangle(mouseX - width / 2 - mouseStartOffset.x, mouseY - height / 2 - mouseStartOffset.y, width, height), + 30); + entry.component.render(matrices, entry.bounds.value().getBounds(), mouseX, mouseY, delta); + matrices.popPose(); + + VoxelShape shape = entry.getBoundsProvider().bounds(); + ShapeBounds shapeBounds = new ShapeBounds(shape); + shapeBounds.alpha.setTo(60, 300); + bounds.add(shapeBounds); + hash = shapeBounds.hash; + } + + if (!InternalCursorState.isLeftMousePressed) { + drop(); + } + } + + for (ShapeBounds bound : bounds) { + if ((hash == null || hash != bound.hash) && bound.alpha.target() != 0) { + bound.alpha.setTo(0, 300); + } + } + + { + Iterator<ShapeBounds> iterator = bounds.iterator(); + while (iterator.hasNext()) { + ShapeBounds bounds = iterator.next(); + bounds.update(delta); + if (bounds.alpha.target() == 0 && bounds.alpha.value() <= 2) { + iterator.remove(); + } else { + bounds.shape.forAllBoxes((x1, y1, z1, x2, y2, z2) -> { + matrices.pushPose(); + matrices.translate(0, 0, 500); + fillGradient(matrices, (int) x1, (int) y1, (int) x2, (int) y2, 0xfdff6b | (bounds.alpha.intValue() << 24), 0xfdff6b | (bounds.alpha.intValue() << 24)); + matrices.popPose(); + }); + } + } + } + + Iterator<RenderBackEntry> iterator = backToOriginals.iterator(); + while (iterator.hasNext()) { + RenderBackEntry renderBackEntry = iterator.next(); + renderBackEntry.update(delta); + FloatingRectangle value = renderBackEntry.bounds.value(); + FloatingRectangle target = renderBackEntry.bounds.target(); + if (value.width < 2 || value.height < 2 || (Math.abs(value.x - target.x) <= 1.3 && Math.abs(value.y - target.y) <= 1.3 && Math.abs(value.width - target.width) <= 1 && Math.abs(value.height - target.height) <= 1)) { + iterator.remove(); + } else { + matrices.pushPose(); + matrices.translate(0, 0, 600); + renderBackEntry.component.render(matrices, value.getBounds(), mouseX, mouseY, delta); + matrices.popPose(); + } + } + } + + @Override + public List<? extends GuiEventListener> children() { + return Collections.emptyList(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + drop(); + DraggableComponent<?> hoveredStack = provider.getHovered(this, mouseX, mouseY); + if (hoveredStack != null) { + entry = new DraggableEntry(hoveredStack, new Point(mouseX, mouseY)); + } + return false; + } + + @Override + public boolean mouseReleased(double d, double e, int i) { + return false; + } + + @Override + public boolean mouseDragged(double mouseX1, double mouseY1, int button, double mouseX2, double mouseY2) { + return entry != null && entry.dragging; + } + + private boolean drop() { + if (entry != null && entry.dragging) { + DraggedAcceptorResult result = visitor.acceptDragged(this, entry.component); + entry.component.release(result); + entry = null; + return true; + } + + entry = null; + return false; + } + + @Override + public Screen getScreen() { + return Minecraft.getInstance().screen; + } + + @Override + @Nullable + public DraggableStack getCurrentStack() { + DraggableComponent<?> dragged = getDragged(); + return dragged instanceof DraggableStack ? (DraggableStack) dragged : null; + } + + @Override + @Nullable + public DraggableComponent<?> getDragged() { + return entry != null && entry.dragging ? entry.component : null; + } + + @Override + @Nullable + public Point getCurrentPosition() { + if (!isDraggingComponent()) return null; + Vec2 mouseStartOffset = entry.mouseStartOffset; + FloatingRectangle rectangle = entry.bounds.value(); + return new Point(rectangle.getCenterX() + mouseStartOffset.x, rectangle.getCenterY() + mouseStartOffset.y); + } + + @Override + @Nullable + public Rectangle getCurrentBounds() { + if (!isDraggingComponent()) return null; + FloatingRectangle rectangle = entry.bounds.value(); + return rectangle.getBounds(); + } + + @Override + public void renderBack(DraggableComponent<?> component, Point initialPosition, Supplier<Point> position) { + int width = component.getWidth(); + int height = component.getHeight(); + backToOriginals.add(new RenderBackEntry(component, new Rectangle(initialPosition.x - width / 2, initialPosition.y - height / 2, width, height), () -> { + Point point = position.get(); + return new Rectangle(point.x, point.y, width, height); + })); + } + + @Override + public void renderBack(DraggableComponent<?> component, Rectangle initialPosition, Supplier<Rectangle> bounds) { + backToOriginals.add(new RenderBackEntry(component, initialPosition, bounds)); + } + + private class DraggableEntry { + private final DraggableComponent<?> component; + private final Point start; + private long startDragging = -1; + private final ValueAnimator<FloatingRectangle> bounds; + private final Vec2 mouseStartOffset; + private boolean dragging = false; + private DraggableBoundsProvider boundsProvider; + + private DraggableEntry(DraggableComponent<?> component, Point start) { + this.component = component; + this.start = start; + this.bounds = ValueAnimator.ofFloatingRectangle() + .setAs(component.getOriginBounds(start).getFloatingBounds()); + this.mouseStartOffset = new Vec2((float) (start.x - bounds.value().getCenterX()), (float) (start.y - bounds.value().getCenterY())); + } + + public DraggableBoundsProvider getBoundsProvider() { + if (boundsProvider == null) { + boundsProvider = DraggableBoundsProvider.concat(visitor.getDraggableAcceptingBounds(DraggingContextImpl.this, component).toList()); + } + + return boundsProvider; + } + } + + private static class ShapeBounds { + private final VoxelShape shape; + private final NumberAnimator<Double> alpha; + private final int hash; + + public ShapeBounds(VoxelShape shape) { + this.shape = shape; + this.alpha = ValueAnimator.ofDouble(0); + this.hash = shape.toAabbs().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ShapeBounds shapeBounds)) return false; + return hash == shapeBounds.hash; + } + + @Override + public int hashCode() { + return hash; + } + + public void update(double delta) { + this.alpha.update(delta); + } + } + + private static class RenderBackEntry { + private final DraggableComponent<?> component; + private final Supplier<Rectangle> position; + private final ValueAnimator<FloatingRectangle> bounds = ValueAnimator.ofFloatingRectangle(); + private final int lastDestination = -1; + + public RenderBackEntry(DraggableComponent<?> component, Rectangle initialPosition, Supplier<Rectangle> position) { + this.component = component; + this.bounds.setAs(new FloatingRectangle(initialPosition)); + this.position = position; + } + + public Rectangle getPosition() { + return position.get(); + } + + public void update(double delta) { + this.bounds.update(delta); + this.bounds.setTo(new FloatingRectangle(getPosition()), 200); + } + } +} diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/menu/AbstractMenuEntry.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/menu/AbstractMenuEntry.java new file mode 100644 index 000000000..0debff33b --- /dev/null +++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/menu/AbstractMenuEntry.java @@ -0,0 +1,77 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.menu; + +public abstract class AbstractMenuEntry extends MenuEntry { + private int x, y, width; + private boolean selected, containsMouse, rendering; + + @Override + public void updateInformation(int xPos, int yPos, boolean selected, boolean containsMouse, boolean rendering, int width) { + this.x = xPos; + this.y = yPos; + this.selected = selected; + this.containsMouse = containsMouse; + this.rendering = rendering; + this.width = width; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isRendering() && mouseX >= getX() && mouseX <= getX() + getWidth() && mouseY >= getY() && mouseY < getY() + getEntryHeight()) { + if (onClick(mouseX, mouseY, button)) { + return true; + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + + protected boolean onClick(double mouseX, double mouseY, int button) { + return false; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public boolean isSelected() { + return selected; + } + + public boolean containsMouse() { + return containsMouse; + } + + public boolean isRendering() { + return rendering; + } +} diff --git a/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/menu/Menu.java b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/menu/Menu.java new file mode 100644 index 000000000..4bbb47671 --- /dev/null +++ b/runtime-frontend/overlay/src/main/java/me/shedaniel/rei/impl/client/gui/menu/Menu.java @@ -0,0 +1,209 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.menu; + +import com.google.common.collect.Lists; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.ClothConfigInitializer; +import me.shedaniel.clothconfig2.api.ScissorsHandler; +import me.shedaniel.clothconfig2.api.scroll.ScrollingContainer; +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds; +import me.shedaniel.rei.impl.client.gui.menu.entries.SubMenuEntry; +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +@ApiStatus.Internal +public class Menu extends WidgetWithBounds { + public static final UUID WEATHER = UUID.randomUUID(); + public static final UUID GAME_TYPE = UUID.randomUUID(); + + public final Point menuStartPoint; + public final boolean facingRight; + public final boolean facingDownwards; + private final List<MenuEntry> entries = Lists.newArrayList(); + public final ScrollingContainer scrolling = new ScrollingContainer() { + @Override + public int getMaxScrollHeight() { + int i = 0; + for (MenuEntry entry : children()) { + i += entry.getEntryHeight(); + } + return i; + } + + @Override + public Rectangle getBounds() { + return Menu.this.getInnerBounds(); + } + + @Override + public boolean hasScrollBar() { + return Menu.this.hasScrollBar(); + } + }; + + public Menu(Rectangle menuStart, Collection<MenuEntry> entries, boolean sort) { + buildEntries(entries, sort); + int fullWidth = Minecraft.getInstance().screen.width; + int fullHeight = Minecraft.getInstance().screen.height; + boolean facingRight = true; + this.facingDownwards = fullHeight - menuStart.getMaxY() > menuStart.y; + int y = facingDownwards ? menuStart.getMaxY() : menuStart.y - (scrolling.getMaxScrollHeight() + 2); + boolean hasScrollBar = scrolling.getMaxScrollHeight() > getInnerHeight(y); + int menuWidth = getMaxEntryWidth() + 2 + (hasScrollBar ? 6 : 0); + if (facingRight && fullWidth - menuStart.getMaxX() < menuWidth + 10) { + facingRight = false; + } else if (!facingRight && menuStart.x < menuWidth + 10) { + facingRight = true; + } + this.facingRight = facingRight; + int x = facingRight ? menuStart.x : menuStart.getMaxX() - (getMaxEntryWidth() + 2 + (hasScrollBar ? 6 : 0)); + this.menuStartPoint = new Point(x, y); + } + + @SuppressWarnings("deprecation") + private void buildEntries(Collection<MenuEntry> entries, boolean sort) { + this.entries.clear(); + this.entries.addAll(entries); + if (sort) { + this.entries.sort(Comparator.comparing(entry -> entry instanceof SubMenuEntry ? 0 : 1) + .thenComparing(entry -> entry instanceof SubMenuEntry menuEntry ? menuEntry.text.getString() : "")); + } + for (MenuEntry entry : this.entries) { + entry.parent = this; + } + } + + @Override + public Rectangle getBounds() { + return new Rectangle(menuStartPoint.x, menuStartPoint.y, getMaxEntryWidth() + 2 + (hasScrollBar() ? 6 : 0), getInnerHeight(menuStartPoint.y) + 2); + } + + public Rectangle getInnerBounds() { + return new Rectangle(menuStartPoint.x + 1, menuStartPoint.y + 1, getMaxEntryWidth() + (hasScrollBar() ? 6 : 0), getInnerHeight(menuStartPoint.y)); + } + + public boolean hasScrollBar() { + return scrolling.getMaxScrollHeight() > getInnerHeight(menuStartPoint.y); + } + + public int getInnerHeight(int y) { + return Math.min(scrolling.getMaxScrollHeight(), minecraft.screen.height - 20 - y); + } + + public int getMaxEntryWidth() { + int i = 0; + for (MenuEntry entry : children()) { + if (entry.getEntryWidth() > i) + i = entry.getEntryWidth(); + } + return Math.max(10, i); + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + Rectangle bounds = getBounds(); + Rectangle innerBounds = getInnerBounds(); + fill(matrices, bounds.x, bounds.y, bounds.getMaxX(), bounds.getMaxY(), containsMouse(mouseX, mouseY) ? (ConfigObject.getInstance().isUsingDarkTheme() ? -17587 : -1) : -6250336); + fill(matrices, innerBounds.x, innerBounds.y, innerBounds.getMaxX(), innerBounds.getMaxY(), -16777216); + boolean contains = innerBounds.contains(mouseX, mouseY); + MenuEntry focused = getFocused() instanceof MenuEntry menuEntry ? menuEntry : null; + int currentY = innerBounds.y - scrolling.scrollAmountInt(); + for (MenuEntry child : children()) { + boolean containsMouse = contains && mouseY >= currentY && mouseY < currentY + child.getEntryHeight(); + if (containsMouse) { + focused = child; + } + currentY += child.getEntryHeight(); + } + currentY = innerBounds.y - scrolling.scrollAmountInt(); + ScissorsHandler.INSTANCE.scissor(scrolling.getScissorBounds()); + for (MenuEntry child : children()) { + boolean rendering = currentY + child.getEntryHeight() >= innerBounds.y && currentY <= innerBounds.getMaxY(); + boolean containsMouse = contains && mouseY >= currentY && mouseY < currentY + child.getEntryHeight(); + child.updateInformation(innerBounds.x, currentY, focused == child || containsMouse, containsMouse, rendering, getMaxEntryWidth()); + if (rendering) { + child.render(matrices, mouseX, mouseY, delta); + } + currentY += child.getEntryHeight(); + } + ScissorsHandler.INSTANCE.removeLastScissor(); + setFocused(focused); + scrolling.renderScrollBar(0, 1, ConfigObject.getInstance().isUsingDarkTheme() ? 0.8f : 1f); + scrolling.updatePosition(delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (scrolling.updateDraggingState(mouseX, mouseY, button)) + return true; + return super.mouseClicked(mouseX, mouseY, button) || getInnerBounds().contains(mouseX, mouseY); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (scrolling.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) + return true; + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + if (getInnerBounds().contains(mouseX, mouseY)) { + scrolling.offset(ClothConfigInitializer.getScrollStep() * -amount, true); + return true; + } + for (MenuEntry child : children()) { + if (child instanceof SubMenuEntry) { + if (child.mouseScrolled(mouseX, mouseY, amount)) + return true; + } + } + return super.mouseScrolled(mouseX, mouseY, amount); + } + + @Override + public boolean containsMouse(double mouseX, double mouseY) { + if (super.containsMouse(mouseX, mouseY)) return true; + for (M |
