aboutsummaryrefslogtreecommitdiff
path: root/runtime-engine/screens/src/main/java
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2022-07-27 23:25:27 +0800
committershedaniel <daniel@shedaniel.me>2022-08-26 10:52:27 +0900
commit05069aa62b09f02a8cd6e526ec58a30347a56500 (patch)
treeacb90d01f0a06de7c6b540eefeeee8259016f8ac /runtime-engine/screens/src/main/java
parent685861c91bbb8a8a882da51381d392f1310d061d (diff)
downloadRoughlyEnoughItems-05069aa62b09f02a8cd6e526ec58a30347a56500.tar.gz
RoughlyEnoughItems-05069aa62b09f02a8cd6e526ec58a30347a56500.tar.bz2
RoughlyEnoughItems-05069aa62b09f02a8cd6e526ec58a30347a56500.zip
WIP Module
Diffstat (limited to 'runtime-engine/screens/src/main/java')
-rw-r--r--runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ExclusionZonesImpl.java158
-rw-r--r--runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ScreenRegistryImpl.java332
2 files changed, 490 insertions, 0 deletions
diff --git a/runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ExclusionZonesImpl.java b/runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ExclusionZonesImpl.java
new file mode 100644
index 000000000..85b8090f1
--- /dev/null
+++ b/runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ExclusionZonesImpl.java
@@ -0,0 +1,158 @@
+/*
+ * 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.registry.screen;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.gui.config.DisplayPanelLocation;
+import me.shedaniel.rei.api.client.registry.screen.ExclusionZones;
+import me.shedaniel.rei.api.client.registry.screen.ExclusionZonesProvider;
+import me.shedaniel.rei.api.common.plugins.PluginManager;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.world.InteractionResult;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public class ExclusionZonesImpl implements ExclusionZones {
+ private static final Comparator<? super Rectangle> RECTANGLE_COMPARER = Comparator.comparingLong(Rectangle::hashCode);
+
+ private long lastArea = -1;
+ private final Multimap<Class<?>, Function<Screen, Collection<Rectangle>>> list = HashMultimap.create();
+
+ @Override
+ public <R extends Screen> boolean isHandingScreen(Class<R> screen) {
+ return Screen.class.isAssignableFrom(screen);
+ }
+
+ @Override
+ public double getPriority() {
+ return -5.0;
+ }
+
+ @Override
+ public InteractionResult isInZone(double mouseX, double mouseY) {
+ Screen screen = Minecraft.getInstance().screen;
+ Class<? extends Screen> screenClass = screen.getClass();
+
+ synchronized (list) {
+ for (Map.Entry<Class<?>, Collection<Function<Screen, Collection<Rectangle>>>> collectionEntry : list.asMap().entrySet()) {
+ if (collectionEntry.getKey().isAssignableFrom(screenClass)) {
+ for (Function<Screen, Collection<Rectangle>> listSupplier : collectionEntry.getValue()) {
+ for (Rectangle zone : listSupplier.apply(screen)) {
+ if (zone.contains(mouseX, mouseY)) {
+ return InteractionResult.FAIL;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return InteractionResult.PASS;
+ }
+
+ @Override
+ public boolean shouldRecalculateArea(DisplayPanelLocation location, Rectangle rectangle) {
+ long current = currentHashCode(location);
+ if (lastArea == current)
+ return false;
+ lastArea = current;
+ return true;
+ }
+
+ private long currentHashCode(DisplayPanelLocation location) {
+ return areasHashCode(getExclusionZones(Minecraft.getInstance().screen, false));
+ }
+
+ @Override
+ @Deprecated
+ public List<Rectangle> getExclusionZones(Class<?> currentScreenClass, boolean sort) {
+ return getExclusionZones(currentScreenClass, Minecraft.getInstance().screen, sort);
+ }
+
+ @Override
+ public List<Rectangle> getExclusionZones(Screen screen, boolean sort) {
+ if (screen == null) return Lists.newArrayList();
+ return getExclusionZones(screen.getClass(), screen, sort);
+ }
+
+ public List<Rectangle> getExclusionZones(Class<?> screenClass, Screen screen, boolean sort) {
+ if (screen == null || !screenClass.isAssignableFrom(screen.getClass())) {
+ return Lists.newArrayList();
+ }
+
+ List<Rectangle> rectangles = Lists.newArrayList();
+ synchronized (list) {
+ for (Map.Entry<Class<?>, Collection<Function<Screen, Collection<Rectangle>>>> collectionEntry : list.asMap().entrySet()) {
+ if (collectionEntry.getKey().isAssignableFrom(screenClass)) {
+ for (Function<Screen, Collection<Rectangle>> listSupplier : collectionEntry.getValue()) {
+ rectangles.addAll(listSupplier.apply(screen));
+ }
+ }
+ }
+ }
+ if (sort) {
+ rectangles.sort(RECTANGLE_COMPARER);
+ }
+ return rectangles;
+ }
+
+ @Override
+ public int getZonesCount() {
+ return list.size();
+ }
+
+ @Override
+ public <T> void register(Class<? extends T> screenClass, ExclusionZonesProvider<? extends T> provider) {
+ synchronized (list) {
+ list.put(screenClass, screen -> ((ExclusionZonesProvider<T>) provider).provide((T) screen));
+ }
+
+ InternalLogger.getInstance().debug("Added exclusion zones provider: %s for %s", provider, screenClass.getName());
+ if (!PluginManager.areAnyReloading()) {
+ InternalLogger.getInstance().warn("Detected ExclusionZonesImpl modification at runtime, this may cause issues, a single ExclusionZonesProvider can dynamically provide boundaries instead!", new RuntimeException());
+ }
+ }
+
+ private long areasHashCode(List<Rectangle> exclusionZones) {
+ int hashCode = 31;
+ for (Rectangle e : exclusionZones)
+ hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
+ return hashCode;
+ }
+
+}
diff --git a/runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ScreenRegistryImpl.java b/runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ScreenRegistryImpl.java
new file mode 100644
index 000000000..99532e670
--- /dev/null
+++ b/runtime-engine/screens/src/main/java/me/shedaniel/rei/impl/client/registry/screen/ScreenRegistryImpl.java
@@ -0,0 +1,332 @@
+/*
+ * 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.registry.screen;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.mojang.blaze3d.platform.Window;
+import dev.architectury.event.CompoundEventResult;
+import me.shedaniel.math.Point;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.gui.config.DisplayPanelLocation;
+import me.shedaniel.rei.api.client.gui.drag.DraggableStackProvider;
+import me.shedaniel.rei.api.client.gui.drag.DraggableStackVisitor;
+import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentProvider;
+import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentProviderWidget;
+import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentVisitor;
+import me.shedaniel.rei.api.client.gui.drag.component.DraggableComponentVisitorWidget;
+import me.shedaniel.rei.api.client.gui.screen.DisplayScreen;
+import me.shedaniel.rei.api.client.gui.widgets.Widgets;
+import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
+import me.shedaniel.rei.api.client.registry.screen.*;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.registry.ReloadStage;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.api.common.util.EntryStacks;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public class ScreenRegistryImpl implements ScreenRegistry {
+ private Multimap<Class<? extends Screen>, ClickArea<?>> clickAreas = HashMultimap.create();
+ private List<DraggableComponentProvider<Screen, Object>> draggableProviders = new ArrayList<>();
+ private List<DraggableComponentVisitor<Screen>> draggableVisitors = new ArrayList<>();
+ private List<FocusedStackProvider> focusedStackProviders = new ArrayList<>();
+ private List<OverlayDecider> deciders = new CopyOnWriteArrayList<>();
+ private Map<Class<?>, List<OverlayDecider>> cache = new HashMap<>();
+ private ExclusionZones exclusionZones = new ExclusionZonesImpl();
+ private final ThreadLocal<Class<? extends Screen>> tmpScreen = new ThreadLocal<>();
+
+ @Override
+ public ReloadStage getStage() {
+ return ReloadStage.START;
+ }
+
+ @Override
+ public void acceptPlugin(REIClientPlugin plugin) {
+ plugin.registerScreens(this);
+ plugin.registerExclusionZones(exclusionZones());
+ }
+
+ @Override
+ public <R extends Screen> List<OverlayDecider> getDeciders(R screen) {
+ if (screen == null) return Collections.emptyList();
+ Class<? extends Screen> screenClass = screen.getClass();
+ List<OverlayDecider> possibleCached = cache.get(screenClass);
+ if (possibleCached != null) {
+ return possibleCached;
+ }
+
+ tmpScreen.set(screenClass);
+ List<OverlayDecider> deciders = CollectionUtils.filterToList(this.deciders, this::filterResponsible);
+ cache.put(screenClass, deciders);
+ tmpScreen.remove();
+ return deciders;
+ }
+
+ private boolean filterResponsible(OverlayDecider handler) {
+ return handler.isHandingScreen(tmpScreen.get());
+ }
+
+ @Override
+ public List<OverlayDecider> getDeciders() {
+ return Collections.unmodifiableList(deciders);
+ }
+
+ @Override
+ public <T extends Screen> Rectangle getScreenBounds(T screen) {
+ for (OverlayDecider decider : getDeciders(screen)) {
+ if (decider instanceof DisplayBoundsProvider) {
+ Rectangle bounds = ((DisplayBoundsProvider<T>) decider).getScreenBounds(screen);
+
+ if (bounds != null) {
+ return bounds;
+ }
+ }
+ }
+ return new Rectangle();
+ }
+
+ @Override
+ public <T extends Screen> Rectangle getOverlayBounds(DisplayPanelLocation location, T screen) {
+ Window window = Minecraft.getInstance().getWindow();
+ int scaledWidth = window.getGuiScaledWidth();
+ int scaledHeight = window.getGuiScaledHeight();
+ Rectangle screenBounds = getScreenBounds(screen);
+ if (screenBounds.isEmpty()) return new Rectangle();
+ if (location == DisplayPanelLocation.LEFT) {
+ if (screenBounds.x < 10) return new Rectangle();
+ return new Rectangle(2, 0, screenBounds.x - 2, scaledHeight);
+ } else {
+ if (scaledWidth - screenBounds.getMaxX() < 10) return new Rectangle();
+ return new Rectangle(screenBounds.getMaxX() + 2, 0, scaledWidth - screenBounds.getMaxX() - 4, scaledHeight);
+ }
+ }
+
+ @Nullable
+ @Override
+ public <T extends Screen> EntryStack<?> getFocusedStack(T screen, Point mouse) {
+ for (FocusedStackProvider provider : focusedStackProviders) {
+ CompoundEventResult<EntryStack<?>> result = Objects.requireNonNull(provider.provide(screen, mouse));
+ if (result.isTrue()) {
+ if (result != null && !result.object().isEmpty())
+ return result.object();
+ return null;
+ } else if (result.isFalse())
+ return null;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void registerDecider(OverlayDecider decider) {
+ deciders.add(decider);
+ deciders.sort(Comparator.reverseOrder());
+ cache.clear();
+ tmpScreen.remove();
+ InternalLogger.getInstance().debug("Added overlay decider: %s [%.2f priority]", decider, decider.getPriority());
+ }
+
+ @Override
+ public void registerFocusedStack(FocusedStackProvider provider) {
+ focusedStackProviders.add(provider);
+ focusedStackProviders.sort(Comparator.reverseOrder());
+ InternalLogger.getInstance().debug("Added focused stack provider: %s [%.2f priority]", provider, provider.getPriority());
+ }
+
+ @Override
+ public <T extends Screen> void registerDraggableStackProvider(DraggableStackProvider<T> provider) {
+ registerDraggableComponentProvider(provider);
+ }
+
+ @Override
+ public <T extends Screen> void registerDraggableStackVisitor(DraggableStackVisitor<T> visitor) {
+ registerDraggableComponentVisitor(visitor);
+ }
+
+ @Override
+ public <T extends Screen, A> void registerDraggableComponentProvider(DraggableComponentProvider<T, A> provider) {
+ draggableProviders.add((DraggableComponentProvider<Screen, Object>) provider);
+ draggableProviders.sort(Comparator.reverseOrder());
+ InternalLogger.getInstance().debug("Added draggable component provider: %s [%.2f priority]", provider, provider.getPriority());
+ }
+
+ @Override
+ public <T extends Screen> void registerDraggableComponentVisitor(DraggableComponentVisitor<T> visitor) {
+ draggableVisitors.add((DraggableComponentVisitor<Screen>) visitor);
+ draggableVisitors.sort(Comparator.reverseOrder());
+ InternalLogger.getInstance().debug("Added draggable component visitor: %s [%.2f priority]", visitor, visitor.getPriority());
+ }
+
+ @Override
+ public Iterable<DraggableStackProvider<Screen>> getDraggableProviders() {
+ return Iterables.filter(Collections.unmodifiableList(draggableProviders), (Class<DraggableStackProvider<Screen>>) (Class<?>) DraggableStackVisitor.class);
+ }
+
+ @Override
+ public Iterable<DraggableStackVisitor<Screen>> getDraggableVisitors() {
+ return Iterables.filter(Collections.unmodifiableList(draggableVisitors), (Class<DraggableStackVisitor<Screen>>) (Class<?>) DraggableStackVisitor.class);
+ }
+
+ @Override
+ public Iterable<DraggableComponentProvider<Screen, Object>> getDraggableComponentProviders() {
+ return Collections.unmodifiableList(draggableProviders);
+ }
+
+ @Override
+ public Iterable<DraggableComponentVisitor<Screen>> getDraggableComponentVisitors() {
+ return Collections.unmodifiableList(draggableVisitors);
+ }
+
+ @Override
+ public ExclusionZones exclusionZones() {
+ return exclusionZones;
+ }
+
+ @Override
+ public <C extends AbstractContainerMenu, T extends AbstractContainerScreen<C>> void registerContainerClickArea(SimpleClickArea<T> area, Class<? extends T> screenClass, CategoryIdentifier<?>... categories) {
+ registerClickArea(screen -> {
+ Rectangle rectangle = area.provide(screen).clone();
+ rectangle.translate(screen.leftPos, screen.topPos);
+ return rectangle;
+ }, screenClass, categories);
+ }
+
+ @Override
+ public <T extends Screen> void registerClickArea(Class<? extends T> screenClass, ClickArea<T> area) {
+ clickAreas.put(screenClass, area);
+ InternalLogger.getInstance().debug("Added click area provider for %s: %s", screenClass.getName(), area);
+ }
+
+ @Override
+ public <T extends Screen> List<ClickArea.Result> evaluateClickArea(Class<T> screenClass, ClickArea.ClickAreaContext<T> context) {
+ List<ClickArea.Result> results = new ArrayList<>();
+ for (ClickArea<?> area : this.clickAreas.get(screenClass)) {
+ ClickArea.Result result = ((ClickArea<T>) area).handle(context);
+
+ if (result.isSuccessful()) {
+ results.add(result);
+ }
+ }
+ return results;
+ }
+
+ @Override
+ public void startReload() {
+ clickAreas.clear();
+ deciders.clear();
+ cache.clear();
+ focusedStackProviders.clear();
+ draggableProviders.clear();
+ draggableVisitors.clear();
+ tmpScreen.remove();
+
+ registerDefault();
+ }
+
+ @Override
+ public void endReload() {
+ InternalLogger.getInstance().debug("Registered %d overlay deciders and %d exclusion zones", deciders.size(), exclusionZones.getZonesCount());
+ }
+
+ private void registerDefault() {
+ registerDecider(this.exclusionZones = new ExclusionZonesImpl());
+ registerDecider(new DisplayBoundsProvider<AbstractContainerScreen<?>>() {
+ @Override
+ public Rectangle getScreenBounds(AbstractContainerScreen<?> screen) {
+ return new Rectangle(screen.leftPos, screen.topPos, screen.imageWidth, screen.imageHeight);
+ }
+
+ @Override
+ public <R extends Screen> boolean isHandingScreen(Class<R> screen) {
+ return AbstractContainerScreen.class.isAssignableFrom(screen);
+ }
+
+ @Override
+ public <R extends Screen> InteractionResult shouldScreenBeOverlaid(R screen) {
+ return screen instanceof AbstractContainerScreen<?> ? InteractionResult.SUCCESS : InteractionResult.PASS;
+ }
+
+ @Override
+ public double getPriority() {
+ return -10.0;
+ }
+ });
+ registerDecider(new DisplayBoundsProvider<DisplayScreen>() {
+ @Override
+ public Rectangle getScreenBounds(DisplayScreen screen) {
+ return screen.getBounds();
+ }
+
+ @Override
+ public <R extends Screen> boolean isHandingScreen(Class<R> screen) {
+ return DisplayScreen.class.isAssignableFrom(screen);
+ }
+
+ @Override
+ public <R extends Screen> InteractionResult shouldScreenBeOverlaid(R screen) {
+ return InteractionResult.SUCCESS;
+ }
+
+ @Override
+ public double getPriority() {
+ return -10.0;
+ }
+ });
+ registerFocusedStack(new FocusedStackProvider() {
+ @Override
+ public CompoundEventResult<EntryStack<?>> provide(Screen screen, Point mouse) {
+ if (screen instanceof AbstractContainerScreen<?> containerScreen) {
+ if (containerScreen.hoveredSlot != null && !containerScreen.hoveredSlot.getItem().isEmpty())
+ return CompoundEventResult.interruptTrue(EntryStacks.of(containerScreen.hoveredSlot.getItem()));
+ }
+ return CompoundEventResult.pass();
+ }
+
+ @Override
+ public double getPriority() {
+ return -10.0;
+ }
+ });
+ registerDraggableComponentProvider(DraggableComponentProviderWidget.from(context ->
+ Widgets.walk(context.getScreen().children(), DraggableComponentProviderWidget.class::isInstance)));
+ registerDraggableComponentVisitor(DraggableComponentVisitorWidget.from(context ->
+ Widgets.walk(context.getScreen().children(), DraggableComponentVisitorWidget.class::isInstance)));
+ }
+}