From 9ea4a22e9a194fd8fdc2fb03226ab38ee175a6cc Mon Sep 17 00:00:00 2001 From: shedaniel Date: Mon, 26 Oct 2020 10:49:27 +0800 Subject: Favorites Dragging Signed-off-by: shedaniel --- .../java/me/shedaniel/rei/api/ConfigObject.java | 19 +- .../main/java/me/shedaniel/rei/api/EntryStack.java | 21 +- .../shedaniel/rei/api/favorites/FavoriteEntry.java | 109 +++ .../rei/api/favorites/FavoriteEntryType.java | 79 ++ .../rei/api/favorites/FavoriteMenuEntry.java | 46 ++ .../shedaniel/rei/api/favorites/package-info.java | 27 + .../main/java/me/shedaniel/rei/impl/Internals.java | 18 + .../me/shedaniel/rei/utils/CollectionUtils.java | 14 +- .../me/shedaniel/rei/plugin/DefaultPlugin.java | 9 + .../shedaniel/rei/plugin/favorites/Animator.java | 97 +++ .../plugin/favorites/GameModeFavoriteEntry.java | 273 ++++++ RoughlyEnoughItems-runtime/build.gradle | 5 + .../me/shedaniel/rei/RoughlyEnoughItemsCore.java | 108 ++- .../me/shedaniel/rei/compat/LBASupportPlugin.java | 59 ++ .../shedaniel/rei/gui/ContainerScreenOverlay.java | 235 +++--- .../me/shedaniel/rei/gui/OverlaySearchField.java | 3 + .../me/shedaniel/rei/gui/RecipeViewingScreen.java | 4 - .../rei/gui/VillagerRecipeViewingScreen.java | 6 +- .../java/me/shedaniel/rei/gui/modules/Menu.java | 6 +- .../entries/EntryStackSubsetsMenuEntry.java | 3 +- .../rei/gui/modules/entries/GameModeMenuEntry.java | 2 +- .../gui/modules/entries/SubSubsetsMenuEntry.java | 3 +- .../rei/gui/modules/entries/WeatherMenuEntry.java | 2 +- .../rei/gui/plugin/DefaultRuntimePlugin.java | 124 +++ .../rei/gui/widget/EntryListEntryWidget.java | 33 +- .../shedaniel/rei/gui/widget/EntryListWidget.java | 55 +- .../me/shedaniel/rei/gui/widget/EntryWidget.java | 28 +- .../rei/gui/widget/FavoritesListWidget.java | 915 ++++++++++++++++++--- .../main/java/me/shedaniel/rei/impl/Animator.java | 97 +++ .../me/shedaniel/rei/impl/ConfigManagerImpl.java | 32 +- .../me/shedaniel/rei/impl/ConfigObjectImpl.java | 17 +- .../rei/impl/FavoriteEntryTypeRegistryImpl.java | 107 +++ .../me/shedaniel/rei/impl/RecipeHelperImpl.java | 1 + gradle.properties | 2 +- .../assets/roughlyenoughitems/lang/en_us.json | 3 + 35 files changed, 2216 insertions(+), 346 deletions(-) create mode 100644 RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntry.java create mode 100644 RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntryType.java create mode 100644 RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteMenuEntry.java create mode 100644 RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/package-info.java create mode 100644 RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/Animator.java create mode 100644 RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/GameModeFavoriteEntry.java create mode 100644 RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/compat/LBASupportPlugin.java create mode 100644 RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/Animator.java create mode 100644 RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/FavoriteEntryTypeRegistryImpl.java diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ConfigObject.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ConfigObject.java index 948c969dc..62482d09b 100644 --- a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ConfigObject.java +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ConfigObject.java @@ -24,11 +24,13 @@ package me.shedaniel.rei.api; import me.shedaniel.clothconfig2.api.ModifierKeyCode; +import me.shedaniel.rei.api.favorites.FavoriteEntry; import me.shedaniel.rei.gui.config.*; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import org.jetbrains.annotations.ApiStatus; +import java.util.Collections; import java.util.List; @Environment(EnvType.CLIENT) @@ -57,6 +59,8 @@ public interface ConfigObject { boolean isGrabbingItems(); + boolean isReducedMotion(); + boolean isToastDisplayedOnCopyIdentifier(); @Deprecated @@ -123,7 +127,11 @@ public interface ConfigObject { boolean doDebugRenderTimeRequired(); - boolean doSearchFavorites(); + @Deprecated + @ApiStatus.ScheduledForRemoval + default boolean doSearchFavorites() { + return false; + } ModifierKeyCode getFavoriteKeyCode(); @@ -149,7 +157,14 @@ public interface ConfigObject { boolean isLowerConfigButton(); - List getFavorites(); + @Deprecated + @ApiStatus.ScheduledForRemoval + default List getFavorites() { + return Collections.emptyList(); + } + + @ApiStatus.Experimental + List getFavoriteEntries(); List getFilteredStacks(); diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/EntryStack.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/EntryStack.java index bb1ec1fda..70c8a06bc 100644 --- a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/EntryStack.java +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/EntryStack.java @@ -27,8 +27,8 @@ import com.google.common.collect.ImmutableList; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mojang.blaze3d.vertex.PoseStack; -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.JsonOps; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.fluid.FluidSupportProvider; @@ -40,9 +40,11 @@ import net.fabricmc.api.Environment; import net.minecraft.client.resources.language.I18n; import net.minecraft.core.Registry; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.TagParser; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; @@ -166,9 +168,11 @@ public interface EntryStack extends TextRepresentable { static EntryStack readFromJson(JsonElement jsonElement) { try { JsonObject obj = jsonElement.getAsJsonObject(); - switch (obj.getAsJsonPrimitive("type").getAsString()) { + switch (GsonHelper.getAsString(obj, "type")) { case "stack": return EntryStack.create(ItemStack.of(TagParser.parseTag(obj.get("nbt").getAsString()))); + case "item": + return EntryStack.create(ItemStack.of((CompoundTag) Dynamic.convert(JsonOps.INSTANCE, NbtOps.INSTANCE, obj))); case "fluid": return EntryStack.create(Registry.FLUID.get(ResourceLocation.tryParse(obj.get("id").getAsString()))); case "empty": @@ -188,9 +192,8 @@ public interface EntryStack extends TextRepresentable { try { switch (getType()) { case ITEM: - JsonObject obj1 = new JsonObject(); - obj1.addProperty("type", "stack"); - obj1.addProperty("nbt", getItemStack().save(new CompoundTag()).toString()); + JsonObject obj1 = Dynamic.convert(NbtOps.INSTANCE, JsonOps.INSTANCE, getItemStack().save(new CompoundTag())).getAsJsonObject(); + obj1.addProperty("type", "item"); return obj1; case FLUID: Optional optionalIdentifier = getIdentifier(); @@ -368,7 +371,7 @@ public interface EntryStack extends TextRepresentable { class Settings { @ApiStatus.Internal - private static final Short2ObjectMap> ID_TO_SETTINGS = new Short2ObjectOpenHashMap<>(); + private static final List> SETTINGS = new ArrayList<>(); public static final Supplier TRUE = () -> true; public static final Supplier FALSE = () -> false; @@ -389,12 +392,12 @@ public interface EntryStack extends TextRepresentable { public Settings(T defaultValue) { this.defaultValue = defaultValue; this.id = nextId++; - ID_TO_SETTINGS.put(this.id, this); + SETTINGS.add(this); } @ApiStatus.Internal public static Settings getById(short id) { - return (Settings) ID_TO_SETTINGS.get(id); + return (Settings) SETTINGS.get(id); } public T getDefaultValue() { diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntry.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntry.java new file mode 100644 index 000000000..211dbc915 --- /dev/null +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntry.java @@ -0,0 +1,109 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.favorites; + +import com.google.gson.JsonObject; +import me.shedaniel.rei.api.EntryStack; +import me.shedaniel.rei.impl.Internals; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Supplier; + +public abstract class FavoriteEntry { + public static final String TYPE_KEY = "type"; + private final UUID uuid = UUID.randomUUID(); + + @NotNull + public static FavoriteEntry delegate(@NotNull Supplier supplier, @Nullable Supplier toJson) { + return Internals.delegateFavoriteEntry(supplier, toJson); + } + + @Nullable + public static FavoriteEntry fromJson(@NotNull JsonObject object) { + return Internals.favoriteEntryFromJson(object); + } + + @NotNull + public static FavoriteEntry fromEntryStack(@NotNull EntryStack stack) { + return delegate(() -> FavoriteEntryType.registry().get(FavoriteEntryType.ENTRY_STACK).fromArgs(stack), null); + } + + public static boolean isEntryInvalid(@Nullable FavoriteEntry entry) { + return entry == null || entry.isInvalid(); + } + + @NotNull + public JsonObject toJson(@NotNull JsonObject object) { + object.addProperty(TYPE_KEY, getType().toString()); + return Objects.requireNonNull(Objects.requireNonNull(FavoriteEntryType.registry().get(getType())).toJson(this, object)); + } + + public UUID getUuid() { + return uuid; + } + + public abstract boolean isInvalid(); + + public abstract EntryStack getWidget(boolean showcase); + + public abstract boolean doAction(int button); + + @NotNull + public Optional>> getMenuEntries() { + return Optional.empty(); + } + + public abstract int hashIgnoreAmount(); + + public abstract FavoriteEntry copy(); + + public abstract ResourceLocation getType(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FavoriteEntry)) return false; + FavoriteEntry that = (FavoriteEntry) o; + FavoriteEntry unwrapped = getUnwrapped(); + FavoriteEntry thatUnwrapped = that.getUnwrapped(); + return unwrapped == thatUnwrapped || unwrapped.isSame(thatUnwrapped); + } + + @Override + public int hashCode() { + return hashIgnoreAmount(); + } + + public abstract boolean isSame(FavoriteEntry other); + + public FavoriteEntry getUnwrapped() { + return this; + } +} diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntryType.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntryType.java new file mode 100644 index 000000000..2df0332fc --- /dev/null +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteEntryType.java @@ -0,0 +1,79 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.favorites; + +import com.google.gson.JsonObject; +import me.shedaniel.rei.impl.Internals; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface FavoriteEntryType { + ResourceLocation ENTRY_STACK = new ResourceLocation("roughlyenoughitems", "entry_stack"); + + @NotNull + static Registry registry() { + return Internals.getFavoriteEntryTypeRegistry(); + } + + @NotNull + T fromJson(@NotNull JsonObject object); + + @NotNull + T fromArgs(Object... args); + + @NotNull + JsonObject toJson(@NotNull T entry, @NotNull JsonObject object); + + @ApiStatus.NonExtendable + interface Registry { + void register(ResourceLocation id, FavoriteEntryType type); + + @Nullable FavoriteEntryType get(ResourceLocation id); + + @Nullable + ResourceLocation getId(FavoriteEntryType type); + + @NotNull + Section getOrCrateSection(Component text); + + @NotNull + Iterable
sections(); + } + + @ApiStatus.NonExtendable + interface Section { + void add(@NotNull FavoriteEntry... entries); + + @NotNull + Component getText(); + + @NotNull + List getEntries(); + } +} diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteMenuEntry.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteMenuEntry.java new file mode 100644 index 000000000..575682f58 --- /dev/null +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/FavoriteMenuEntry.java @@ -0,0 +1,46 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.api.favorites; + +import me.shedaniel.rei.gui.widget.Widget; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public abstract class FavoriteMenuEntry extends Widget { + @Nullable + @ApiStatus.Internal + public Runnable closeMenu = null; + + public abstract int getEntryWidth(); + + public abstract int getEntryHeight(); + + public abstract void updateInformation(int xPos, int yPos, boolean selected, boolean containsMouse, boolean rendering, int width); + + public void closeMenu() { + if (closeMenu != null) { + closeMenu.run(); + } + } +} diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/package-info.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/package-info.java new file mode 100644 index 000000000..a2ba8f361 --- /dev/null +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/favorites/package-info.java @@ -0,0 +1,27 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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. + */ + +@ApiStatus.Experimental +package me.shedaniel.rei.api.favorites; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/impl/Internals.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/impl/Internals.java index d8fc9fc6d..29d9ddecc 100644 --- a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/impl/Internals.java +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/impl/Internals.java @@ -23,9 +23,12 @@ package me.shedaniel.rei.impl; +import com.google.gson.JsonObject; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.*; +import me.shedaniel.rei.api.favorites.FavoriteEntry; +import me.shedaniel.rei.api.favorites.FavoriteEntryType; import me.shedaniel.rei.api.fluid.FluidSupportProvider; import me.shedaniel.rei.api.fractions.Fraction; import me.shedaniel.rei.api.subsets.SubsetsRegistry; @@ -61,6 +64,9 @@ public final class Internals { private static Supplier displayHelper = Internals::throwNotSetup; private static Supplier widgetsProvider = Internals::throwNotSetup; private static Supplier viewSearchBuilder = Internals::throwNotSetup; + private static Supplier favoriteEntryTypeRegistry = Internals::throwNotSetup; + private static BiFunction, Supplier, FavoriteEntry> delegateFavoriteEntry = (supplier, toJson) -> throwNotSetup(); + private static Function favoriteEntryFromJson = (object) -> throwNotSetup(); private static Function<@NotNull Boolean, ClickAreaHandler.Result> clickAreaHandlerResult = (result) -> throwNotSetup(); private static BiFunction<@Nullable Point, Collection, Tooltip> tooltipProvider = (point, texts) -> throwNotSetup(); private static Supplier builtinPlugin = Internals::throwNotSetup; @@ -125,6 +131,10 @@ public final class Internals { return viewSearchBuilder.get(); } + public static FavoriteEntryType.Registry getFavoriteEntryTypeRegistry() { + return favoriteEntryTypeRegistry.get(); + } + @NotNull public static ClickAreaHandler.Result createClickAreaHandlerResult(boolean applicable) { return clickAreaHandlerResult.apply(applicable); @@ -161,6 +171,14 @@ public final class Internals { } } + public static FavoriteEntry delegateFavoriteEntry(Supplier supplier, Supplier toJoin) { + return delegateFavoriteEntry.apply(supplier, toJoin); + } + + public static FavoriteEntry favoriteEntryFromJson(JsonObject object) { + return favoriteEntryFromJson.apply(object); + } + public interface EntryStackProvider { EntryStack empty(); diff --git a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/utils/CollectionUtils.java b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/utils/CollectionUtils.java index 1060a6bdc..fd4cac467 100644 --- a/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/utils/CollectionUtils.java +++ b/RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/utils/CollectionUtils.java @@ -26,15 +26,15 @@ package me.shedaniel.rei.utils; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.collect.UnmodifiableIterator; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import me.shedaniel.rei.api.EntryStack; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.util.Mth; import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; +import java.util.function.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -162,6 +162,14 @@ public class CollectionUtils { return l; } + public static IntList mapToInt(Collection list, ToIntFunction function) { + IntList l = new IntArrayList(list.size() + 1); + for (T t : list) { + l.add(function.applyAsInt(t)); + } + return l; + } + public static List mapParallel(Collection list, Function function) { return list.parallelStream().map(function).collect(Collectors.toList()); } diff --git a/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java b/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java index b8c3b7a23..fe76dfcab 100644 --- a/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java +++ b/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java @@ -31,6 +31,8 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.*; +import me.shedaniel.rei.api.favorites.FavoriteEntry; +import me.shedaniel.rei.api.favorites.FavoriteEntryType; import me.shedaniel.rei.api.fluid.FluidSupportProvider; import me.shedaniel.rei.api.fractions.Fraction; import me.shedaniel.rei.api.plugins.REIPluginV0; @@ -52,6 +54,7 @@ import me.shedaniel.rei.plugin.crafting.DefaultCraftingCategory; import me.shedaniel.rei.plugin.crafting.DefaultCustomDisplay; import me.shedaniel.rei.plugin.crafting.DefaultShapedDisplay; import me.shedaniel.rei.plugin.crafting.DefaultShapelessDisplay; +import me.shedaniel.rei.plugin.favorites.GameModeFavoriteEntry; import me.shedaniel.rei.plugin.fuel.DefaultFuelCategory; import me.shedaniel.rei.plugin.fuel.DefaultFuelDisplay; import me.shedaniel.rei.plugin.information.DefaultInformationCategory; @@ -79,6 +82,7 @@ import net.minecraft.client.gui.screens.inventory.*; import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener; import net.minecraft.core.Registry; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.BlockTags; import net.minecraft.tags.ItemTags; @@ -91,6 +95,7 @@ import net.minecraft.world.item.alchemy.PotionUtils; import net.minecraft.world.item.crafting.*; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.GameType; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.block.ComposterBlock; import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; @@ -418,6 +423,10 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin { // SubsetsRegistry subsetsRegistry = SubsetsRegistry.INSTANCE; // subsetsRegistry.registerPathEntry("roughlyenoughitems:food", EntryStack.create(Items.MILK_BUCKET)); // subsetsRegistry.registerPathEntry("roughlyenoughitems:food/roughlyenoughitems:cookies", EntryStack.create(Items.COOKIE)); + + FavoriteEntryType.registry().register(GameModeFavoriteEntry.ID, GameModeFavoriteEntry.Type.INSTANCE); + FavoriteEntryType.registry().getOrCrateSection(new TranslatableComponent(GameModeFavoriteEntry.TRANSLATION_KEY)) + .add(Arrays.stream(GameType.values()).map(GameModeFavoriteEntry.Type.INSTANCE::fromArgs).toArray(FavoriteEntry[]::new)); } @Override diff --git a/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/Animator.java b/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/Animator.java new file mode 100644 index 000000000..3c7cf651c --- /dev/null +++ b/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/Animator.java @@ -0,0 +1,97 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.plugin.favorites; + +import me.shedaniel.clothconfig2.api.ScrollingContainer; +import net.minecraft.Util; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public final class Animator extends Number { + private double amount; + private double target; + private long start; + private long duration; + + public Animator() { + } + + public Animator(double amount) { + setAs(amount); + } + + public void setAs(double value) { + this.set(value, false, 0); + } + + public void setTo(double value, long duration) { + if (target != value) + this.set(value, true, duration); + } + + private void set(double value, boolean animated, long duration) { + this.target = value; + this.start = Util.getMillis(); + + if (animated) { + this.duration = duration; + } else { + this.duration = 0; + this.amount = this.target; + } + } + + public void update(double delta) { + if (duration != 0) { + if (amount < target) + this.amount = Math.min(ScrollingContainer.ease(amount, target + (target - amount), Math.min(((double) Util.getMillis() - start) / duration * delta * 3.0D, 1.0D), v -> v), target); + else if (amount > target) + this.amount = Math.max(ScrollingContainer.ease(amount, target - (amount - target), Math.min(((double) Util.getMillis() - start) / duration * delta * 3.0D, 1.0D), v -> v), target); + } + } + + @Override + public int intValue() { + return (int) amount; + } + + @Override + public long longValue() { + return (long) amount; + } + + @Override + public float floatValue() { + return (float) amount; + } + + @Override + public double doubleValue() { + return amount; + } + + public double target() { + return target; + } +} diff --git a/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/GameModeFavoriteEntry.java b/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/GameModeFavoriteEntry.java new file mode 100644 index 000000000..727dce65f --- /dev/null +++ b/RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/favorites/GameModeFavoriteEntry.java @@ -0,0 +1,273 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.plugin.favorites; + +import com.google.gson.JsonObject; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.api.ScissorsHandler; +import me.shedaniel.math.Point; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.ConfigObject; +import me.shedaniel.rei.api.EntryStack; +import me.shedaniel.rei.api.REIHelper; +import me.shedaniel.rei.api.favorites.FavoriteEntry; +import me.shedaniel.rei.api.favorites.FavoriteEntryType; +import me.shedaniel.rei.api.favorites.FavoriteMenuEntry; +import me.shedaniel.rei.api.widgets.Tooltip; +import me.shedaniel.rei.impl.RenderingEntry; +import me.shedaniel.rei.utils.CollectionUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.level.GameType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Supplier; + +public class GameModeFavoriteEntry extends FavoriteEntry { + public static final ResourceLocation ID = new ResourceLocation("roughlyenoughitems", "gamemode"); + public static final String TRANSLATION_KEY = "favorite.section.gamemode"; + public static final String KEY = "mode"; + private GameType gameMode; + + public GameModeFavoriteEntry(GameType gameMode) { + this.gameMode = Objects.requireNonNull(gameMode); + } + + @Override + public boolean isInvalid() { + return false; + } + + @Override + public EntryStack getWidget(boolean showcase) { + return new RenderingEntry() { + private Animator notSetOffset = new Animator(0); + private Rectangle notSetScissorArea = new Rectangle(); + private long nextSwitch = -1; + + @Override + public void render(PoseStack matrices, Rectangle bounds, int mouseX, int mouseY, float delta) { + int color = bounds.contains(mouseX, mouseY) ? 0xFFEEEEEE : 0xFFAAAAAA; + fillGradient(matrices, bounds.getX(), bounds.getY(), bounds.getMaxX(), bounds.getY() + 1, color, color); + fillGradient(matrices, bounds.getX(), bounds.getMaxY() - 1, bounds.getMaxX(), bounds.getMaxY(), color, color); + fillGradient(matrices, bounds.getX(), bounds.getY(), bounds.getX() + 1, bounds.getMaxY(), color, color); + fillGradient(matrices, bounds.getMaxX() - 1, bounds.getY(), bounds.getMaxX(), bounds.getMaxY(), color, color); + if (bounds.width > 4 && bounds.height > 4) { + if (gameMode == GameType.NOT_SET) { + updateAnimator(delta); + notSetScissorArea.setBounds(bounds.x + 2, bounds.y + 2, bounds.width - 4, bounds.height - 4); + ScissorsHandler.INSTANCE.scissor(notSetScissorArea); + int offset = Math.round(notSetOffset.floatValue() * bounds.getHeight()); + for (int i = 0; i <= 3; i++) { + GameType type = GameType.byId(i); + renderGameModeText(matrices, type, bounds.getCenterX(), bounds.getCenterY() + bounds.getHeight() * i - offset, color); + } + ScissorsHandler.INSTANCE.removeLastScissor(); + } else { + renderGameModeText(matrices, gameMode, bounds.getCenterX(), bounds.getCenterY(), color); + } + } + } + + private void updateAnimator(float delta) { + notSetOffset.update(delta); + if (showcase) { + if (nextSwitch == -1) { + nextSwitch = Util.getMillis(); + } + if (Util.getMillis() - nextSwitch > 1000) { + nextSwitch = Util.getMillis(); + notSetOffset.setTo(((int) notSetOffset.target() + 1) % 4, 500); + } + } else { + notSetOffset.setTo((Minecraft.getInstance().gameMode.getPlayerMode().getId() + 1) % 4, 500); + } + } + + private void renderGameModeText(PoseStack matrices, GameType type, int centerX, int centerY, int color) { + Component s = new TranslatableComponent("text.rei.short_gamemode." + type.getName()); + Font font = Minecraft.getInstance().font; + font.draw(matrices, s, centerX - font.width(s) / 2 + (type == GameType.NOT_SET ? 0 : 0.5f), centerY - 3.5f, color); + } + + @Override + public @Nullable Tooltip getTooltip(Point mouse) { + if (gameMode == GameType.NOT_SET) + return Tooltip.create(mouse, new TranslatableComponent("text.rei.gamemode_button.tooltip.all")); + return Tooltip.create(mouse, new TranslatableComponent("text.rei.gamemode_button.tooltip.entry", gameMode.getDisplayName().getString())); + } + }; + } + + @Override + public boolean doAction(int button) { + if (button == 0) { + GameType type = gameMode; + if (type == GameType.NOT_SET) { + type = GameType.byId(Minecraft.getInstance().gameMode.getPlayerMode().getId() + 1 % 4); + } + Minecraft.getInstance().player.chat(ConfigObject.getInstance().getGamemodeCommand().replaceAll("\\{gamemode}", type.name().toLowerCase(Locale.ROOT))); + Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + return true; + } + return false; + } + + @Override + public @NotNull Optional>> getMenuEntries() { + if (gameMode == GameType.NOT_SET) + return Optional.of(this::_getMenuEntries); + return Optional.empty(); + } + + private Collection _getMenuEntries() { + return CollectionUtils.filterAndMap(Arrays.asList(GameType.values()), mode -> mode != GameType.NOT_SET, GameModeMenuEntry::new); + } + + @Override + public int hashIgnoreAmount() { + return gameMode.ordinal(); + } + + @Override + public FavoriteEntry copy() { + return this; + } + + @Override + public ResourceLocation getType() { + return ID; + } + + @Override + public boolean isSame(FavoriteEntry other) { + if (!(other instanceof GameModeFavoriteEntry)) return false; + GameModeFavoriteEntry that = (GameModeFavoriteEntry) other; + return Objects.equals(gameMode, that.gameMode); + } + + public enum Type implements FavoriteEntryType { + INSTANCE; + + @Override + public @NotNull GameModeFavoriteEntry fromJson(@NotNull JsonObject object) { + return new GameModeFavoriteEntry(GameType.valueOf(GsonHelper.getAsString(object, KEY))); + } + + @Override + public @NotNull GameModeFavoriteEntry fromArgs(Object... args) { + return new GameModeFavoriteEntry((GameType) args[0]); + } + + @Override + public @NotNull JsonObject toJson(@NotNull GameModeFavoriteEntry entry, @NotNull JsonObject object) { + object.addProperty(KEY, entry.gameMode.name()); + return object; + } + } + + public static class GameModeMenuEntry extends FavoriteMenuEntry { + public final String text; + public final GameType gameMode; + private int x, y, width; + private boolean selected, containsMouse, rendering; + private int textWidth = -69; + + public GameModeMenuEntry(GameType gameMode) { + this.text = gameMode.getDisplayName().getString(); + this.gameMode = gameMode; + } + + private int getTextWidth() { + if (textWidth == -69) { + this.textWidth = Math.max(0, font.width(text)); + } + return this.textWidth; + } + + @Override + public int getEntryWidth() { + return getTextWidth() + 4; + } + + @Override + public int getEntryHeight() { + return 12; + } + + @Override + public List children() { + return Collections.emptyList(); + } + + @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 void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + boolean disabled = this.minecraft.gameMode.getPlayerMode() == gameMode; + if (selected && !disabled) { + fill(matrices, x, y, x + width, y + 12, -12237499); + } + if (!disabled && selected && containsMouse) { + REIHelper.getInstance().queueTooltip(Tooltip.create(new TranslatableComponent("text.rei.gamemode_button.tooltip.entry", text))); + } + String s = text; + if (disabled) { + s = ChatFormatting.STRIKETHROUGH.toString() + s; + } + font.draw(matrices, s, x + 2, y + 2, selected && !disabled ? 16777215 : 8947848); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + boolean disabled = this.minecraft.gameMode.getPlayerMode() == gameMode; + if (!disabled && rendering && mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + 12) { + Minecraft.getInstance().player.chat(ConfigObject.getInstance().getGamemodeCommand().replaceAll("\\{gamemode}", gameMode.name().toLowerCase(Locale.ROOT))); + minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + closeMenu(); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + } +} diff --git a/RoughlyEnoughItems-runtime/build.gradle b/RoughlyEnoughItems-runtime/build.gradle index 2fcc093d6..870d044d0 100644 --- a/RoughlyEnoughItems-runtime/build.gradle +++ b/RoughlyEnoughItems-runtime/build.gradle @@ -1,9 +1,14 @@ archivesBaseName = "RoughlyEnoughItems-runtime" +repositories { + maven { url "https://mod-buildcraft.com/maven" } +} + loom { accessWidener = file("src/main/resources/roughlyenoughitems-runtime.accessWidener") } dependencies { compile project(path: ':RoughlyEnoughItems-api', configuration: 'dev') + modCompileOnly("alexiil.mc.lib:libblockattributes-fluids:0.8.3-pre.3") } \ No newline at end of file diff --git a/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java index 2bb94e36e..9ea31b888 100644 --- a/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java +++ b/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java @@ -25,11 +25,17 @@ package me.shedaniel.rei; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import me.shedaniel.cloth.api.client.events.v0.ClothClientHooks; +import me.shedaniel.clothconfig2.api.LazyResettable; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.math.api.Executor; import me.shedaniel.rei.api.*; +import me.shedaniel.rei.api.favorites.FavoriteEntry; +import me.shedaniel.rei.api.favorites.FavoriteEntryType; +import me.shedaniel.rei.api.favorites.FavoriteMenuEntry; import me.shedaniel.rei.api.fluid.FluidSupportProvider; import me.shedaniel.rei.api.fractions.Fraction; import me.shedaniel.rei.api.plugins.REIPluginV0; @@ -66,6 +72,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.TextComponent; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; import net.minecraft.world.InteractionResult; import net.minecraft.world.inventory.CraftingMenu; import net.minecraft.world.inventory.Slot; @@ -77,6 +84,7 @@ import net.minecraft.world.level.material.Fluid; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -88,6 +96,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import static me.shedaniel.rei.impl.Internals.attachInstance; @@ -180,6 +189,94 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer { return new FillRectangleDrawableConsumer(rectangle, color); } }, Internals.WidgetsProvider.class); + attachInstance((Supplier) FavoriteEntryTypeRegistryImpl::getInstance, "favoriteEntryTypeRegistry"); + attachInstance((BiFunction, Supplier, FavoriteEntry>) (supplier, toJson) -> new FavoriteEntry() { + LazyResettable value = new LazyResettable<>(supplier); + + @Override + public FavoriteEntry getUnwrapped() { + FavoriteEntry entry = value.get(); + if (entry == null) { + value.reset(); + } + return Objects.requireNonNull(entry).getUnwrapped(); + } + + @Override + public UUID getUuid() { + return getUnwrapped().getUuid(); + } + + @Override + public boolean isInvalid() { + try { + return getUnwrapped().isInvalid(); + } catch (Exception e) { + return true; + } + } + + @Override + public EntryStack getWidget(boolean showcase) { + return getUnwrapped().getWidget(showcase); + } + + @Override + public boolean doAction(int button) { + return getUnwrapped().doAction(button); + } + + @Override + public @NotNull Optional>> getMenuEntries() { + return getUnwrapped().getMenuEntries(); + } + + @Override + public int hashIgnoreAmount() { + return getUnwrapped().hashIgnoreAmount(); + } + + @Override + public FavoriteEntry copy() { + return FavoriteEntry.delegate(supplier, toJson); + } + + @Override + public ResourceLocation getType() { + return getUnwrapped().getType(); + } + + @Override + public @NotNull JsonObject toJson(@NotNull JsonObject to) { + if (toJson == null) { + return getUnwrapped().toJson(to); + } + + JsonObject object = toJson.get(); + for (Map.Entry entry : object.entrySet()) { + to.add(entry.getKey(), entry.getValue()); + } + return to; + } + + @Override + public boolean isSame(FavoriteEntry other) { + return getUnwrapped().isSame(other.getUnwrapped()); + } + }, "delegateFavoriteEntry"); + attachInstance((Function) (object) -> { + String type = GsonHelper.getAsString(object, FavoriteEntry.TYPE_KEY); + switch (type) { + case "stack": + case "item": + case "fluid": + case "empty": + return FavoriteEntry.fromEntryStack(EntryStack.readFromJson(object)); + default: + ResourceLocation id = new ResourceLocation(type); + return Objects.requireNonNull(Objects.requireNonNull(FavoriteEntryType.registry().get(id)).fromJson(object)); + } + }, "favoriteEntryFromJson"); attachInstance((BiFunction<@Nullable Point, Collection, Tooltip>) QueuedTooltip::create, "tooltipProvider"); attachInstance((Function<@Nullable Boolean, ClickAreaHandler.Result>) successful -> new ClickAreaHandler.Result() { private List categories = Lists.newArrayList(); @@ -189,12 +286,12 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer { this.categories.add(category); return this; } - + @Override public boolean isSuccessful() { return successful; } - + @Override public Stream getCategories() { return categories.stream(); @@ -387,6 +484,13 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer { if (isDebugModeEnabled()) { registerPlugin(new REITestPlugin()); } + if (FabricLoader.getInstance().isModLoaded("libblockattributes-fluids")) { + try { + registerPlugin((REIPluginEntry) Class.forName("me.shedaniel.rei.compat.LBASupportPlugin").getConstructor().newInstance()); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } } private boolean shouldReturn(Screen screen) { diff --git a/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/compat/LBASupportPlugin.java b/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/compat/LBASupportPlugin.java new file mode 100644 index 000000000..d2e31a6f7 --- /dev/null +++ b/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/compat/LBASupportPlugin.java @@ -0,0 +1,59 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020 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.compat; + +import alexiil.mc.lib.attributes.fluid.FluidAttributes; +import alexiil.mc.lib.attributes.fluid.GroupedFluidInvView; +import alexiil.mc.lib.attributes.fluid.amount.FluidAmount; +import me.shedaniel.rei.api.EntryStack; +import me.shedaniel.rei.api.RecipeHelper; +import me.shedaniel.rei.api.fluid.FluidSupportProvider; +import me.shedaniel.rei.api.fractions.Fraction; +import me.shedaniel.rei.api.plugins.REIPluginV0; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.InteractionResultHolder; + +import java.util.stream.Stream; + +public class LBASupportPlugin implements REIPluginV0 { + @Override + public ResourceLocation getPluginIdentifier() { + return new ResourceLocation("roughlyenoughitems", "lba_support"); + } + + @Override + public void registerOthers(RecipeHelper recipeHelper) { + FluidSupportProvider.getInstance().registerProvider(itemStack -> { + GroupedFluidInvView view = FluidAttributes.GROUPED_INV_VIEW.get(itemStack.getItemStack()); + if (view.getStoredFluids().size() > 0) + return InteractionResultHolder.success(view.getStoredFluids().stream() + .filter(fluidKey -> !fluidKey.isEmpty() && fluidKey.getRawFluid() != null) + .map(fluidKey -> { + FluidAmount amount = view.getAmount_F(fluidKey); + return EntryStack.create(fluidKey.getRawFluid(), Fraction.of(amount.whole, amount.numerator, amount.denominator)); + })); + return InteractionResultHolder.pass(Stream.empty()); + }); + } +} diff --git a/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java b/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java index 4e0a5e06b..08132ae85 100644 --- a/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java +++ b/RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java @@ -35,6 +35,7 @@ import me.shedaniel.math.Rectangle; import me.shedaniel.math.impl.PointHelper; import me.shedaniel.rei.RoughlyEnoughItemsCore; import me.shedaniel.rei.api.*; +import me.shedaniel.rei.api.favorites.FavoriteEntry; import me.shedaniel.rei.api.widgets.Button; import me.shedaniel.rei.api.widgets.Tooltip; import me.shedaniel.rei.api.widgets.Widgets; @@ -72,11 +73,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; @ApiStatus.Internal public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverlay { - private static final ResourceLocation CHEST_GUI_TEXTURE = new ResourceLocation("roughlyenoughitems", "textures/gui/recipecontainer.png"); private static final List TOOLTIPS = Lists.newArrayList(); private static final List AFTER_RENDER = Lists.newArrayList(); @@ -120,22 +122,9 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl private Button leftButton, rightButton; @ApiStatus.Experimental private Rectangle subsetsButtonBounds; - @ApiStatus.Experimental - @Nullable - private Menu subsetsMenu = null; - private Widget wrappedSubsetsMenu = null; @Nullable - private Menu weatherMenu = null; - private Widget wrappedWeatherMenu = null; - private boolean renderWeatherMenu = false; - private Button weatherButton = null; - - @Nullable - private Menu gameModeMenu = null; - private Widget wrappedGameModeMenu = null; - private boolean renderGameModeMenu = false; - private Button gameModeButton = null; + private ContainerScreenOverlay.OverlayMenu overlayMenu = null; public static EntryListWidget getEntryListWidget() { return ENTRY_LIST_WIDGET; @@ -146,26 +135,67 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl return favoritesListWidget; } - @ApiStatus.Experimental - @Nullable - public Menu getSubsetsMenu() { - return subsetsMenu; + private static class OverlayMenu { + @NotNull + private UUID uuid; + @NotNull + private Menu menu; + @NotNull + private Widget wrappedMenu; + @NotNull + private Predicate inBounds; + + public OverlayMenu(@NotNull UUID uuid, @NotNull Menu menu, @NotNull Widget wrappedMenu, @NotNull Predicate inBounds) { + this.uuid = uuid; + this.menu = menu; + this.wrappedMenu = wrappedMenu; + this.inBounds = inBounds.or(point -> menu.getBounds().contains(point)); + } + } + + public boolean isMenuOpened(UUID uuid) { + return overlayMenu != null && overlayMenu.uuid.equals(uuid); + } + + public boolean isAnyMenuOpened() { + return overlayMenu != null; } - public void removeWeatherMenu() { - this.renderWeatherMenu = false; - Widget tmpWeatherMenu = wrappedWeatherMenu; - AFTER_RENDER.add(() -> this.widgets.remove(tmpWeatherMenu)); - this.weatherMenu = null; - this.wrappedWeatherMenu = null; + public boolean isMenuInBounds(UUID uuid) { + return isMenuOpened(uuid) && overlayMenu.inBounds.test(PointHelper.ofMouse()); } - public void removeGameModeMenu() { - this.renderGameModeMenu = false; - Widget tmpGameModeMenu = wrappedGameModeMenu; - AFTER_RENDER.add(() -> this.widgets.remove(tmpGameModeMenu)); - this.gameModeMenu = null; - this.wrappedGameModeMenu = null; + private void proceedOpenMenu(UUID uuid, Runnable runnable) { + proceedOpenMenuOrElse(uuid, runnable, menu -> {}); + } + + private void proceedOpenMenuOrElse(UUID uuid, Runnable runnable, Consumer orElse) { + if (overlayMenu == null || !overlayMenu.uuid.equals(uuid)) { + removeOverlayMenu(); + runnable.run(); + } else { + orElse.accept(this.overlayMenu); + } + } + + public void openMenu(UUID uuid, Menu menu, Predicate inPoint) { + this.overlayMenu = new OverlayMenu(uuid, menu, InternalWidgets.wrapTranslate(menu, 0, 0, 400), inPoint); + } + + @ApiStatus.Internal + @Nullable + public Menu getSubsetsMenu() { + if (isMenuOpened(Menu.SUBSETS)) + return this.overlayMenu.menu; + throw new IllegalStateException("Subsets menu accessed when subsets are not opened!"); + } + + @ApiStatus.Internal + public void removeOverlayMenu() { + OverlayMenu tmpOverlayMenu = this.overlayMenu; + if (tmpOverlayMenu != null) + AFTER_RENDER.add(() -> this.widgets.remove(tmpOverlayMenu.wrappedMenu)); + this.overlayMenu = null; } @Override @@ -181,17 +211,14 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl this.shouldReInit = false; //Update Variables this.children().clear(); - this.wrappedSubsetsMenu = null; - this.subsetsMenu = null; - this.weatherMenu = null; - this.renderWeatherMenu = false; - this.weatherButton = null; + this.removeOverlayMenu(); this.window = Minecraft.getInstance().getWindow(); this.bounds = DisplayHelper.getInstance().getOverlayBounds(ConfigObject.getInstance().getDisplayPanelLocation(), Minecraft.getInstance().screen); widgets.add(ENTRY_LIST_WIDGET); if (ConfigObject.getInstance().isFavoritesEnabled()) { if (favoritesListWidget == null) favoritesListWidget = new FavoritesListWidget(); +// favoritesListWidget.favoritePanel.resetRows(); widgets.add(favoritesListWidget); } ENTRY_LIST_WIDGET.updateArea(ScreenHelper.getSearchField() == null ? "" : null); @@ -268,20 +295,21 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl )); tmp.setZ(600); if (ConfigObject.getInstance().doesShowUtilsButtons()) { - widgets.add(gameModeButton = Widgets.createButton(ConfigObject.getInstance().isLowerConfigButton() ? new Rectangle(ConfigObject.getInstance().isLeftHandSidePanel() ? window.getGuiScaledWidth() - 30 : 10, 10, 20, 20) : new Rectangle(ConfigObject.getInstance().isLeftHandSidePanel() ? window.getGuiScaledWidth() - 55 : 35, 10, 20, 20), NarratorChatListener.NO_TITLE) + widgets.add(Widgets.createButton(ConfigObject.getInstance().isLowerConfigButton() ? new Rectangle(ConfigObject.getInstance().isLeftHandSidePanel() ? window.getGuiScaledWidth() - 30 : 10, 10, 20, 20) : new Rectangle(ConfigObject.getInstance().isLeftHandSidePanel() ? window.getGuiScaledWidth() - 55 : 35, 10, 20, 20), NarratorChatListener.NO_TITLE) .onRender((matrices, button) -> { - boolean tmpRender = renderGameModeMenu; - renderGameModeMenu = !renderWeatherMenu && (button.isFocused() || button.containsMouse(PointHelper.ofMouse()) || (wrappedGameModeMenu != null && wrappedGameModeMenu.containsMouse(PointHelper.ofMouse()))); - if (tmpRender != renderGameModeMenu) { - if (renderGameModeMenu) { - this.gameModeMenu = new Menu(new Point(button.getBounds().x, button.getBounds().getMaxY()), - CollectionUtils.filterAndMap(Arrays.asList(GameType.values()), mode -> mode != GameType.NOT_SET, GameModeMenuEntry::new)); - if (ConfigObject.getInstance().isLeftHandSidePanel()) - this.gameModeMenu.menuStartPoint.x -= this.gameModeMenu.getBounds().width - this.gameModeButton.getBounds().width; - this.wrappedGameModeMenu = InternalWidgets.wrapTranslate(InternalWidgets.wrapLateRenderable(gameModeMenu), 0, 0, 600); - AFTER_RENDER.add(() -> this.widgets.add(wrappedGameModeMenu)); - } else { - removeGameModeMenu(); + boolean isOpened = isMenuOpened(Menu.GAME_TYPE); + if (isOpened || !isAnyMenuOpened()) { + boolean inBounds = (button.isFocused() || button.containsMouse(PointHelper.ofMouse())) || isMenuInBounds(Menu.GAME_TYPE); + if (isOpened != inBounds) { + if (inBounds) { + Menu menu = new Menu(new Point(button.getBounds().x, button.getBounds().getMaxY()), + CollectionUtils.filterAndMap(Arrays.asList(GameType.values()), mode -> mode != GameType.NOT_SET, GameModeMenuEntry::new)); + if (ConfigObject.get