312 lines
11 KiB
Java
312 lines
11 KiB
Java
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<? extends GuiEventListener> children();
|
|
|
|
/**
|
|
* Returns the first event listener that intersects with the mouse coordinates.
|
|
*/
|
|
default Optional<GuiEventListener> 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<GuiEventListener> 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.
|
|
* <p>
|
|
* @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<? extends GuiEventListener> 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<? extends GuiEventListener> listIterator = list.listIterator(j);
|
|
BooleanSupplier booleanSupplier = bl ? listIterator::hasNext : listIterator::hasPrevious;
|
|
Supplier<? extends GuiEventListener> 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.
|
|
* <p>
|
|
* @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.
|
|
* <p>
|
|
* @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<GuiEventListener> 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<GuiEventListener> comparator = Comparator.comparing(
|
|
guiEventListenerx -> guiEventListenerx.getRectangle().getBoundInDirection(direction.getOpposite()), direction.coordinateValueComparator()
|
|
);
|
|
Comparator<GuiEventListener> 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.
|
|
* <p>
|
|
* @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<Pair<GuiEventListener, Long>> 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<GuiEventListener, Long> pair : list) {
|
|
ComponentPath componentPath = pair.getFirst().nextFocusPath(event);
|
|
if (componentPath != null) {
|
|
return componentPath;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|