package net.minecraft.client.gui.components.events; import com.mojang.datafixers.util.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.navigation.ScreenAxis; import net.minecraft.client.gui.navigation.ScreenDirection; import net.minecraft.client.gui.navigation.ScreenPosition; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.navigation.FocusNavigationEvent.ArrowNavigation; import net.minecraft.client.gui.navigation.FocusNavigationEvent.TabNavigation; import org.jetbrains.annotations.Nullable; import org.joml.Vector2i; @Environment(EnvType.CLIENT) public interface ContainerEventHandler extends GuiEventListener { /** * {@return a List containing all GUI element children of this GUI element} */ List children(); /** * Returns the first event listener that intersects with the mouse coordinates. */ default Optional getChildAt(double mouseX, double mouseY) { for (GuiEventListener guiEventListener : this.children()) { if (guiEventListener.isMouseOver(mouseX, mouseY)) { return Optional.of(guiEventListener); } } return Optional.empty(); } @Override default boolean mouseClicked(double mouseX, double mouseY, int button) { Optional optional = this.getChildAt(mouseX, mouseY); if (optional.isEmpty()) { return false; } else { GuiEventListener guiEventListener = (GuiEventListener)optional.get(); if (guiEventListener.mouseClicked(mouseX, mouseY, button)) { this.setFocused(guiEventListener); if (button == 0) { this.setDragging(true); } } return true; } } @Override default boolean mouseReleased(double mouseX, double mouseY, int button) { if (button == 0 && this.isDragging()) { this.setDragging(false); if (this.getFocused() != null) { return this.getFocused().mouseReleased(mouseX, mouseY, button); } } return false; } @Override default boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { return this.getFocused() != null && this.isDragging() && button == 0 ? this.getFocused().mouseDragged(mouseX, mouseY, button, dragX, dragY) : false; } /** * {@return {@code true} if the GUI element is dragging, {@code false} otherwise} */ boolean isDragging(); /** * Sets if the GUI element is dragging or not. * * @param isDragging the dragging state of the GUI element. */ void setDragging(boolean isDragging); @Override default boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { return this.getChildAt(mouseX, mouseY).filter(guiEventListener -> guiEventListener.mouseScrolled(mouseX, mouseY, scrollX, scrollY)).isPresent(); } @Override default boolean keyPressed(int keyCode, int scanCode, int modifiers) { return this.getFocused() != null && this.getFocused().keyPressed(keyCode, scanCode, modifiers); } @Override default boolean keyReleased(int keyCode, int scanCode, int modifiers) { return this.getFocused() != null && this.getFocused().keyReleased(keyCode, scanCode, modifiers); } @Override default boolean charTyped(char codePoint, int modifiers) { return this.getFocused() != null && this.getFocused().charTyped(codePoint, modifiers); } /** * Gets the focused GUI element. */ @Nullable GuiEventListener getFocused(); /** * Sets the focus state of the GUI element. * * @param focused the focused GUI element. */ void setFocused(@Nullable GuiEventListener focused); @Override default void setFocused(boolean focused) { } @Override default boolean isFocused() { return this.getFocused() != null; } @Nullable @Override default ComponentPath getCurrentFocusPath() { GuiEventListener guiEventListener = this.getFocused(); return guiEventListener != null ? ComponentPath.path(this, guiEventListener.getCurrentFocusPath()) : null; } @Nullable @Override default ComponentPath nextFocusPath(FocusNavigationEvent event) { GuiEventListener guiEventListener = this.getFocused(); if (guiEventListener != null) { ComponentPath componentPath = guiEventListener.nextFocusPath(event); if (componentPath != null) { return ComponentPath.path(this, componentPath); } } if (event instanceof TabNavigation tabNavigation) { return this.handleTabNavigation(tabNavigation); } else { return event instanceof ArrowNavigation arrowNavigation ? this.handleArrowNavigation(arrowNavigation) : null; } } /** * Handles tab-based navigation events. *

* @return The next focus path for tab navigation, or {@code null} if no suitable path is found. * * @param tabNavigation The tab navigation event. */ @Nullable private ComponentPath handleTabNavigation(TabNavigation tabNavigation) { boolean bl = tabNavigation.forward(); GuiEventListener guiEventListener = this.getFocused(); List list = new ArrayList(this.children()); Collections.sort(list, Comparator.comparingInt(guiEventListenerx -> guiEventListenerx.getTabOrderGroup())); int i = list.indexOf(guiEventListener); int j; if (guiEventListener != null && i >= 0) { j = i + (bl ? 1 : 0); } else if (bl) { j = 0; } else { j = list.size(); } ListIterator listIterator = list.listIterator(j); BooleanSupplier booleanSupplier = bl ? listIterator::hasNext : listIterator::hasPrevious; Supplier supplier = bl ? listIterator::next : listIterator::previous; while (booleanSupplier.getAsBoolean()) { GuiEventListener guiEventListener2 = (GuiEventListener)supplier.get(); ComponentPath componentPath = guiEventListener2.nextFocusPath(tabNavigation); if (componentPath != null) { return ComponentPath.path(this, componentPath); } } return null; } /** * Handles arrow-based navigation events. *

* @return The next focus path for arrow navigation, or {@code null} if no suitable path is found. * * @param arrowNavigation The arrow navigation event. */ @Nullable private ComponentPath handleArrowNavigation(ArrowNavigation arrowNavigation) { GuiEventListener guiEventListener = this.getFocused(); if (guiEventListener == null) { ScreenDirection screenDirection = arrowNavigation.direction(); ScreenRectangle screenRectangle = this.getBorderForArrowNavigation(screenDirection.getOpposite()); return ComponentPath.path(this, this.nextFocusPathInDirection(screenRectangle, screenDirection, null, arrowNavigation)); } else { ScreenRectangle screenRectangle2 = guiEventListener.getRectangle(); return ComponentPath.path(this, this.nextFocusPathInDirection(screenRectangle2, arrowNavigation.direction(), guiEventListener, arrowNavigation)); } } /** * Calculates the next focus path in a specific direction. *

* @return The next focus path in the specified direction, or {@code null} if no suitable path is found. * * @param rectangle The screen rectangle. * @param direction The direction of navigation. * @param listener The currently focused GUI event listener. * @param event The focus navigation event. */ @Nullable private ComponentPath nextFocusPathInDirection( ScreenRectangle rectangle, ScreenDirection direction, @Nullable GuiEventListener listener, FocusNavigationEvent event ) { ScreenAxis screenAxis = direction.getAxis(); ScreenAxis screenAxis2 = screenAxis.orthogonal(); ScreenDirection screenDirection = screenAxis2.getPositive(); int i = rectangle.getBoundInDirection(direction.getOpposite()); List list = new ArrayList(); for (GuiEventListener guiEventListener : this.children()) { if (guiEventListener != listener) { ScreenRectangle screenRectangle = guiEventListener.getRectangle(); if (screenRectangle.overlapsInAxis(rectangle, screenAxis2)) { int j = screenRectangle.getBoundInDirection(direction.getOpposite()); if (direction.isAfter(j, i)) { list.add(guiEventListener); } else if (j == i && direction.isAfter(screenRectangle.getBoundInDirection(direction), rectangle.getBoundInDirection(direction))) { list.add(guiEventListener); } } } } Comparator comparator = Comparator.comparing( guiEventListenerx -> guiEventListenerx.getRectangle().getBoundInDirection(direction.getOpposite()), direction.coordinateValueComparator() ); Comparator comparator2 = Comparator.comparing( guiEventListenerx -> guiEventListenerx.getRectangle().getBoundInDirection(screenDirection.getOpposite()), screenDirection.coordinateValueComparator() ); list.sort(comparator.thenComparing(comparator2)); for (GuiEventListener guiEventListener2 : list) { ComponentPath componentPath = guiEventListener2.nextFocusPath(event); if (componentPath != null) { return componentPath; } } return this.nextFocusPathVaguelyInDirection(rectangle, direction, listener, event); } /** * Calculates the next focus path in a vague direction. *

* @return The next focus path in the vague direction, or {@code null} if no suitable path is found. * * @param rectangle The screen rectangle. * @param direction The direction of navigation. * @param listener The currently focused GUI event listener. * @param event The focus navigation event. */ @Nullable private ComponentPath nextFocusPathVaguelyInDirection( ScreenRectangle rectangle, ScreenDirection direction, @Nullable GuiEventListener listener, FocusNavigationEvent event ) { ScreenAxis screenAxis = direction.getAxis(); ScreenAxis screenAxis2 = screenAxis.orthogonal(); List> list = new ArrayList(); ScreenPosition screenPosition = ScreenPosition.of(screenAxis, rectangle.getBoundInDirection(direction), rectangle.getCenterInAxis(screenAxis2)); for (GuiEventListener guiEventListener : this.children()) { if (guiEventListener != listener) { ScreenRectangle screenRectangle = guiEventListener.getRectangle(); ScreenPosition screenPosition2 = ScreenPosition.of( screenAxis, screenRectangle.getBoundInDirection(direction.getOpposite()), screenRectangle.getCenterInAxis(screenAxis2) ); if (direction.isAfter(screenPosition2.getCoordinate(screenAxis), screenPosition.getCoordinate(screenAxis))) { long l = Vector2i.distanceSquared(screenPosition.x(), screenPosition.y(), screenPosition2.x(), screenPosition2.y()); list.add(Pair.of(guiEventListener, l)); } } } list.sort(Comparator.comparingDouble(Pair::getSecond)); for (Pair pair : list) { ComponentPath componentPath = pair.getFirst().nextFocusPath(event); if (componentPath != null) { return componentPath; } } return null; } }