diff --git a/gradle.properties b/gradle.properties index 5c05bce0..5a806839 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,11 +7,11 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.2 loader_version=0.14.21 # Mod Properties -mod_version=0.11.2-pre.1 +mod_version=0.11.2-pre.5 maven_group=io.wispforest archives_base_name=owo-lib # Dependencies -fabric_version=0.83.1+1.20.1 +fabric_version=0.85.0+1.20.1 # https://maven.shedaniel.me/me/shedaniel/RoughlyEnoughItems-fabric/ rei_version=12.0.625 diff --git a/src/main/java/io/wispforest/owo/itemgroup/Icon.java b/src/main/java/io/wispforest/owo/itemgroup/Icon.java index a20b1a09..0a06f30a 100644 --- a/src/main/java/io/wispforest/owo/itemgroup/Icon.java +++ b/src/main/java/io/wispforest/owo/itemgroup/Icon.java @@ -6,19 +6,21 @@ import net.minecraft.item.ItemConvertible; import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; -import org.jetbrains.annotations.ApiStatus; /** * An icon used for rendering on buttons in {@link OwoItemGroup}s *

- * Default implementations provided for textures and itemstacks + * Default implementations provided for textures and item stacks */ +@FunctionalInterface public interface Icon { void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta); static Icon of(ItemStack stack) { - return new ItemIcon(stack); + return (context, x, y, mouseX, mouseY, delta) -> { + context.drawItemWithoutEntity(stack, x, y); + }; } static Icon of(ItemConvertible item) { @@ -26,7 +28,9 @@ static Icon of(ItemConvertible item) { } static Icon of(Identifier texture, int u, int v, int textureWidth, int textureHeight) { - return new TextureIcon(texture, u, v, textureWidth, textureHeight); + return (context, x, y, mouseX, mouseY, delta) -> { + context.drawTexture(texture, x, y, u, v, 16, 16, textureWidth, textureHeight); + }; } /** @@ -39,66 +43,9 @@ static Icon of(Identifier texture, int u, int v, int textureWidth, int textureHe * @return The created icon instance */ static Icon of(Identifier texture, int textureSize, int frameDelay, boolean loop) { - return new AnimatedTextureIcon(texture, new SpriteSheetMetadata(textureSize, 16), frameDelay, loop); - } - - /** - * Renders an {@link ItemStack} - */ - @ApiStatus.Internal - class ItemIcon implements Icon { - - private final ItemStack stack; - - private ItemIcon(ItemStack stack) { - this.stack = stack; - } - - @Override - public void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta) { - context.drawItemWithoutEntity(stack, x, y); - } - } - - /** - * Renders a 16x16 region of the given texture, starting at (u, v) - */ - @ApiStatus.Internal - class TextureIcon implements Icon { - - private final Identifier texture; - private final int u, v; - private final int textureWidth, textureHeight; - - public TextureIcon(Identifier texture, int u, int v, int textureWidth, int textureHeight) { - this.texture = texture; - this.u = u; - this.v = v; - this.textureWidth = textureWidth; - this.textureHeight = textureHeight; - } - - @Override - public void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta) { - context.drawTexture(texture, x, y, u, v, 16, 16, textureWidth, textureHeight); - } - } - - /** - * Similar to TextureIcon but allows 16x16 frame animated textures. - */ - @ApiStatus.Internal - class AnimatedTextureIcon implements Icon { - private final AnimatedTextureDrawable widget; - - public AnimatedTextureIcon(Identifier texture, SpriteSheetMetadata spriteSheetMetadata, int frameDelay, boolean loop) { - this.widget = new AnimatedTextureDrawable(0, 0, 16, 16, texture, spriteSheetMetadata, frameDelay, loop); - } - - @Override - public void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta) { + var widget = new AnimatedTextureDrawable(0, 0, 16, 16, texture, new SpriteSheetMetadata(textureSize, 16), frameDelay, loop); + return (context, x, y, mouseX, mouseY, delta) -> { widget.render(x, y, context, mouseX, mouseY, delta); - } + }; } - } diff --git a/src/main/java/io/wispforest/owo/itemgroup/OwoItemGroup.java b/src/main/java/io/wispforest/owo/itemgroup/OwoItemGroup.java index 721a820d..b3db6e59 100644 --- a/src/main/java/io/wispforest/owo/itemgroup/OwoItemGroup.java +++ b/src/main/java/io/wispforest/owo/itemgroup/OwoItemGroup.java @@ -5,6 +5,9 @@ import io.wispforest.owo.itemgroup.gui.ItemGroupTab; import io.wispforest.owo.mixin.itemgroup.ItemGroupAccessor; import io.wispforest.owo.util.pond.OwoItemExtensions; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSets; import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.item.*; @@ -23,15 +26,15 @@ import java.util.function.Supplier; /** - * A custom implementation of {@link ItemGroup} that supports multiple sub-tabs - * within itself, as well as arbitrary buttons with defaults provided for links - * like GitHub, Modrinth, etc. + * Extensions for {@link ItemGroup} which support multiple sub-tabs + * within, as well as arbitrary buttons with defaults provided for links + * to places like GitHub, Modrinth, etc. *

- * By default, Items are added via tags, however you can also use {@link OwoItemSettings} - * and set the tab for a given item via {@link OwoItemSettings#tab(int)} + * Tabs can be populated by using {@link OwoItemSettings} and setting the + * {@link OwoItemSettings#tab(int)}. Furthermore, tags can be used for easily populating + * tabs from data *

- * Credits to Lemonszz for originally writing this for Biome Makeover. - * Adapted from Azagwens implementation + * This concept originated in Biome Makeover, where it was written by Lemonszz */ public abstract class OwoItemGroup extends ItemGroup { @@ -42,13 +45,13 @@ public abstract class OwoItemGroup extends ItemGroup { public final List tabs = new ArrayList<>(); public final List buttons = new ArrayList<>(); - private final Identifier id; private final Consumer initializer; private final Supplier iconSupplier; private Icon icon; - private int selectedTab = 0; + private final IntSet activeTabs = new IntArraySet(); + private final IntSet activeTabsView = IntSets.unmodifiable(this.activeTabs); private boolean initialized = false; private final int tabStackHeight; @@ -56,10 +59,10 @@ public abstract class OwoItemGroup extends ItemGroup { private final Identifier customTexture; private final boolean useDynamicTitle; private final boolean displaySingleTab; + private final boolean allowMultiSelect; - protected OwoItemGroup(Identifier id, Consumer initializer, Supplier iconSupplier, int tabStackHeight, int buttonStackHeight, @Nullable Identifier customTexture, boolean useDynamicTitle, boolean displaySingleTab) { + protected OwoItemGroup(Identifier id, Consumer initializer, Supplier iconSupplier, int tabStackHeight, int buttonStackHeight, @Nullable Identifier customTexture, boolean useDynamicTitle, boolean displaySingleTab, boolean allowMultiSelect) { super(null, -1, Type.CATEGORY, Text.translatable("itemGroup.%s.%s".formatted(id.getNamespace(), id.getPath())), () -> ItemStack.EMPTY, (displayContext, entries) -> {}); - this.id = id; this.initializer = initializer; this.iconSupplier = iconSupplier; this.tabStackHeight = tabStackHeight; @@ -67,13 +70,17 @@ protected OwoItemGroup(Identifier id, Consumer initializer, Suppli this.customTexture = customTexture; this.useDynamicTitle = useDynamicTitle; this.displaySingleTab = displaySingleTab; + this.allowMultiSelect = allowMultiSelect; ((ItemGroupAccessor) this).owo$setEntryCollector((context, entries) -> { if (!this.initialized) { throw new IllegalStateException("oωo item group not initialized, was 'initialize()' called?"); } - this.getSelectedTab().contentSupplier().addItems(context, entries); - this.collectItemsFromRegistry(entries, true); + + this.activeTabs.forEach(tabIdx -> { + this.tabs.get(tabIdx).contentSupplier().addItems(context, entries); + this.collectItemsFromRegistry(entries, tabIdx); + }); }); } @@ -95,6 +102,15 @@ public void initialize() { if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) this.initializer.accept(this); if (tabs.size() == 0) this.tabs.add(PLACEHOLDER_TAB); + if (this.allowMultiSelect) { + for (int tabIdx = 0; tabIdx < this.tabs.size(); tabIdx++) { + if (!this.tabs.get(tabIdx).primary()) continue; + this.activeTabs.add(tabIdx); + } + } else { + this.activeTabs.add(0); + } + this.initialized = true; } @@ -179,31 +195,117 @@ public void updateEntries(DisplayContext context) { var searchEntries = new SearchOnlyEntries(this, context.enabledFeatures()); - this.collectItemsFromRegistry(searchEntries, false); + this.collectItemsFromRegistry(searchEntries, -1); this.tabs.forEach(tab -> tab.contentSupplier().addItems(context, searchEntries)); ((ItemGroupAccessor) this).owo$setSearchTabStacks(searchEntries.searchTabStacks); } - protected void collectItemsFromRegistry(Entries entries, boolean matchTab) { + protected void collectItemsFromRegistry(Entries entries, int tab) { Registries.ITEM.stream() - .filter(item -> ((OwoItemExtensions) item).owo$group() == this && (!matchTab || ((OwoItemExtensions) item).owo$tab() == this.selectedTab)) + .filter(item -> ((OwoItemExtensions) item).owo$group() == this && (tab < 0 || tab == ((OwoItemExtensions) item).owo$tab())) .forEach(item -> ((OwoItemExtensions) item).owo$stackGenerator().accept(item, entries)); } // Getters and setters + /** + * @deprecated Use {@link #selectSingleTab(int, DisplayContext)} instead + */ + @Deprecated(forRemoval = true) public void setSelectedTab(int selectedTab, DisplayContext context) { - this.selectedTab = selectedTab; - this.updateEntries(context); + this.selectSingleTab(selectedTab, context); } + /** + * @deprecated On an item group which allows multiple selection, this + * returns the first selected tab only. Instead, call {@link #selectedTabs()} + */ + @Deprecated(forRemoval = true) public ItemGroupTab getSelectedTab() { - return tabs.get(selectedTab); + return this.tabs.get(this.getSelectedTabIndex()); } + /** + * @deprecated On an item group which allows multiple selection, this + * returns the index of the first selected tab only. Instead, call {@link #selectedTabs()} + */ + @Deprecated(forRemoval = true) public int getSelectedTabIndex() { - return selectedTab; + return this.activeTabs.iterator().nextInt(); + } + + /** + * Select only {@code tab}, deselecting all other tabs, + * using {@code context} for re-population + */ + public void selectSingleTab(int tab, DisplayContext context) { + this.activeTabs.clear(); + this.activeTabs.add(tab); + + this.updateEntries(context); + } + + /** + * Select {@code tab} in addition to other currently selected + * tabs, using {@code context} for re-population. + *

+ * If this group does not allow multiple selection, behaves + * like {@link #selectSingleTab(int, DisplayContext)} + */ + public void selectTab(int tab, DisplayContext context) { + if (!this.allowMultiSelect) { + this.activeTabs.clear(); + } + + this.activeTabs.add(tab); + this.updateEntries(context); + } + + /** + * Deselect {@code tab} if it is currently selected, using {@code context} for + * re-population. If this results in no tabs being selected, all tabs are + * automatically selected instead + */ + public void deselectTab(int tab, DisplayContext context) { + if (!this.allowMultiSelect) return; + + this.activeTabs.remove(tab); + if (this.activeTabs.isEmpty()) { + for (int tabIdx = 0; tabIdx < this.tabs.size(); tabIdx++) { + this.activeTabs.add(tabIdx); + } + } + + this.updateEntries(context); + } + + /** + * Shorthand for {@link #selectTab(int, DisplayContext)} or + * {@link #deselectTab(int, DisplayContext)}, depending on the tabs + * current state + */ + public void toggleTab(int tab, DisplayContext context) { + if (this.isTabSelected(tab)) { + this.deselectTab(tab, context); + } else { + this.selectTab(tab, context); + } + } + + /** + * @return A set containing the indices of all currently + * selected tabs + */ + public IntSet selectedTabs() { + return this.activeTabsView; + } + + /** + * @return {@code true} if {@code tab} is currently selected + */ + public boolean isTabSelected(int tab) { + return this.activeTabs.contains(tab); } public Identifier getCustomTexture() { @@ -260,6 +362,7 @@ public static class Builder { private @Nullable Identifier customTexture = null; private boolean useDynamicTitle = true; private boolean displaySingleTab = false; + private boolean allowMultiSelect = true; private Builder(Identifier id, Supplier iconSupplier) { this.id = id; @@ -296,8 +399,13 @@ public Builder displaySingleTab() { return this; } + public Builder withoutMultipleSelection() { + this.allowMultiSelect = false; + return this; + } + public OwoItemGroup build() { - final var group = new OwoItemGroup(id, initializer, iconSupplier, tabStackHeight, buttonStackHeight, customTexture, useDynamicTitle, displaySingleTab) {}; + final var group = new OwoItemGroup(id, initializer, iconSupplier, tabStackHeight, buttonStackHeight, customTexture, useDynamicTitle, displaySingleTab, allowMultiSelect) {}; Registry.register(Registries.ITEM_GROUP, this.id, group); return group; } diff --git a/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButton.java b/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButton.java index 8addeada..c89db51d 100644 --- a/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButton.java +++ b/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButton.java @@ -75,16 +75,16 @@ public Identifier texture() { @Override public Icon icon() { - return icon; + return this.icon; } @Override public Text tooltip() { - return tooltip; + return this.tooltip; } public Runnable action() { - return action; + return this.action; } } diff --git a/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButtonWidget.java b/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButtonWidget.java index e05c3486..c2b0595a 100644 --- a/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButtonWidget.java +++ b/src/main/java/io/wispforest/owo/itemgroup/gui/ItemGroupButtonWidget.java @@ -2,42 +2,40 @@ import com.mojang.blaze3d.systems.RenderSystem; import io.wispforest.owo.itemgroup.OwoItemGroup; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.widget.ButtonWidget; import org.jetbrains.annotations.ApiStatus; +import java.util.function.Consumer; + @ApiStatus.Internal public class ItemGroupButtonWidget extends ButtonWidget { public boolean isSelected = false; private final OwoItemGroup.ButtonDefinition definition; - private final boolean hoverReactive; + private final int baseU; - public ItemGroupButtonWidget(int x, int y, boolean hoverReactive, OwoItemGroup.ButtonDefinition definition, PressAction onPress) { - super(x, y, 24, 24, definition.tooltip(), onPress, ButtonWidget.DEFAULT_NARRATION_SUPPLIER); + public ItemGroupButtonWidget(int x, int y, int baseU, OwoItemGroup.ButtonDefinition definition, Consumer onPress) { + super(x, y, 24, 24, definition.tooltip(), button -> onPress.accept((ItemGroupButtonWidget) button), ButtonWidget.DEFAULT_NARRATION_SUPPLIER); + this.baseU = baseU; this.definition = definition; - this.hoverReactive = hoverReactive; } @Override public void renderButton(DrawContext context, int mouseX, int mouseY, float delta) { - var client = MinecraftClient.getInstance(); - - drawTexture(context, definition.texture(), this.getX(), this.getY(), 0, 0, height, this.width, this.height, 64, 64); + RenderSystem.enableDepthTest(); + context.drawTexture(this.definition.texture(), this.getX(), this.getY(), this.baseU, this.isSelected() || this.isSelected ? this.height : 0, this.width, this.height, 64, 64); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); + if (!(this.isSelected() || this.isSelected)) RenderSystem.setShaderColor(.75f, .75f, .75f, 1f); this.definition.icon().render(context, this.getX() + 4, this.getY() + 4, mouseX, mouseY, delta); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); RenderSystem.disableBlend(); } - protected boolean shouldShowHighlight(boolean hovered) { - return (hoverReactive && hovered) || isSelected; - } - public boolean trulyHovered() { return this.hovered; } diff --git a/src/main/java/io/wispforest/owo/itemgroup/json/WrapperGroup.java b/src/main/java/io/wispforest/owo/itemgroup/json/WrapperGroup.java index 523c4b29..f8dac27c 100644 --- a/src/main/java/io/wispforest/owo/itemgroup/json/WrapperGroup.java +++ b/src/main/java/io/wispforest/owo/itemgroup/json/WrapperGroup.java @@ -30,7 +30,7 @@ public class WrapperGroup extends OwoItemGroup { @SuppressWarnings("unchecked") public WrapperGroup(ItemGroup parent, Identifier parentId, List tabs, List buttons) { - super(parentId, owoItemGroup -> {}, () -> Icon.of(parent.getIcon()), 4, 4, null, true, false); + super(parentId, owoItemGroup -> {}, () -> Icon.of(parent.getIcon()), 4, 4, null, true, false, false); int parentRawId = Registries.ITEM_GROUP.getRawId(parent); diff --git a/src/main/java/io/wispforest/owo/mixin/itemgroup/CreativeInventoryScreenMixin.java b/src/main/java/io/wispforest/owo/mixin/itemgroup/CreativeInventoryScreenMixin.java index 6539b543..940289d7 100644 --- a/src/main/java/io/wispforest/owo/mixin/itemgroup/CreativeInventoryScreenMixin.java +++ b/src/main/java/io/wispforest/owo/mixin/itemgroup/CreativeInventoryScreenMixin.java @@ -10,25 +10,26 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.AbstractInventoryScreen; import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; -import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemGroup; import net.minecraft.resource.featuretoggle.FeatureSet; import net.minecraft.text.Text; import net.minecraft.util.Identifier; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyArgs; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; @Mixin(CreativeInventoryScreen.class) public abstract class CreativeInventoryScreenMixin extends AbstractInventoryScreen implements OwoCreativeInventoryScreenExtensions { @@ -36,10 +37,6 @@ public abstract class CreativeInventoryScreenMixin extends AbstractInventoryScre @Shadow private static ItemGroup selectedTab; - @Shadow - @Final - private static Identifier TEXTURE; - @Shadow protected abstract void init(); @@ -49,9 +46,6 @@ public abstract class CreativeInventoryScreenMixin extends AbstractInventoryScre @Unique private final List owo$buttons = new ArrayList<>(); - @Unique - private OwoItemGroup owo$owoGroup = null; - @Unique private FeatureSet owo$enabledFeatures = null; @@ -77,50 +71,29 @@ private Identifier injectCustomGroupTexture(Identifier original) { // Scrollbar slider // ---------------- - @ModifyArg(method = "drawBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIII)V", ordinal = 1)) - private Identifier injectCustomScrollbarTexture(Identifier original) { - if (!(selectedTab instanceof OwoItemGroup owoGroup) || owoGroup.getCustomTexture() == null) return original; - this.owo$owoGroup = owoGroup; - return owoGroup.getCustomTexture(); - } - - @ModifyArg(method = "drawBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIII)V", ordinal = 1), index = 3) - private int injectCustomScrollbarTextureU(int original) { - if (owo$owoGroup == null) return original; - return original - 232; - } - - @ModifyArg(method = "drawBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIII)V", ordinal = 1), index = 4) - private int injectCustomScrollbarTextureV(int original) { - if (owo$owoGroup == null) return original; - return 136; - } + @ModifyArgs(method = "drawBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIII)V", ordinal = 1)) + private void injectCustomScrollbarTexture(Args args) { + if (!(selectedTab instanceof OwoItemGroup owoGroup) || owoGroup.getCustomTexture() == null) return; - @Inject(method = "drawBackground", at = @At("RETURN")) - private void releaseGroupInstance(DrawContext context, float delta, int mouseX, int mouseY, CallbackInfo ci) { - this.owo$owoGroup = null; + args.set(0, owoGroup.getCustomTexture()); + args.set(3, args.get(3) - 232); + args.set(4, 136); } // ------------- // Group headers // ------------- - @Inject(method = "renderTabIcon", at = @At("HEAD")) - private void captureTabGroup(DrawContext context, ItemGroup group, CallbackInfo ci) { - if (!(group instanceof OwoItemGroup owoGroup)) return; - this.owo$owoGroup = owoGroup; - } - @ModifyArg(method = "renderTabIcon", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIII)V")) private Identifier injectCustomTabTexture(Identifier texture) { - if (this.owo$owoGroup == null || this.owo$owoGroup.getCustomTexture() == null) return texture; - return owo$owoGroup.getCustomTexture(); + if (!(selectedTab instanceof OwoItemGroup owoGroup) || owoGroup.getCustomTexture() == null) return texture; + return owoGroup.getCustomTexture(); } @ModifyArg(method = "renderTabIcon", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIII)V"), index = 3) private int injectCustomTabTextureLocation(int original) { - if (this.owo$owoGroup == null || this.owo$owoGroup.getCustomTexture() == null) return original; - return this.owo$owoGroup.getColumn() == 0 ? 195 : 221; + if (!(selectedTab instanceof OwoItemGroup owoGroup) || owoGroup.getCustomTexture() == null) return original; + return owoGroup.getColumn() == 0 ? 195 : 221; } @Inject(method = "renderTabIcon", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemGroup;getIcon()Lnet/minecraft/item/ItemStack;"), locals = LocalCapture.CAPTURE_FAILHARD) @@ -133,27 +106,22 @@ private void renderOwoIcon(DrawContext context, ItemGroup group, CallbackInfo ci RenderSystem.disableBlend(); } - @Inject(method = "renderTabIcon", at = @At("RETURN")) - private void restoreTabTexture(DrawContext context, ItemGroup group, CallbackInfo ci) { - if (this.owo$owoGroup == null) return; - this.owo$owoGroup = null; - RenderSystem.setShaderTexture(0, TEXTURE); - } - // ------------- // oωo tab title // ------------- @ModifyArg(method = "drawForeground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I")) private Text injectTabNameAsTitle(Text original) { - if (!(selectedTab instanceof OwoItemGroup owoGroup) || !owoGroup.hasDynamicTitle()) return original; - if (owoGroup.getSelectedTab().primary()) { - return owoGroup.getSelectedTab().name(); + if (!(selectedTab instanceof OwoItemGroup owoGroup) || !owoGroup.hasDynamicTitle() || owoGroup.selectedTabs().size() != 1) return original; + + var singleActiveTab = owoGroup.getTab(owoGroup.selectedTabs().iterator().nextInt()); + if (singleActiveTab.primary()) { + return singleActiveTab.name(); } else { return Text.translatable( "text.owo.itemGroup.tab_template", owoGroup.getDisplayName(), - owoGroup.getSelectedTab().name() + singleActiveTab.name() ); } } @@ -164,28 +132,26 @@ private Text injectTabNameAsTitle(Text original) { @Inject(at = @At("HEAD"), method = "setSelectedTab(Lnet/minecraft/item/ItemGroup;)V") private void setSelectedTab(ItemGroup group, CallbackInfo ci) { - owo$buttons.forEach(this::remove); - owo$buttons.clear(); + this.owo$buttons.forEach(this::remove); + this.owo$buttons.clear(); if (group instanceof OwoItemGroup owoGroup) { - int tabRootY = this.y; final var tabStackHeight = owoGroup.getTabStackHeight(); tabRootY -= 13 * (tabStackHeight - 4); if (owoGroup.shouldDisplaySingleTab() || owoGroup.tabs.size() > 1) { - for (int i = 0; i < owoGroup.tabs.size(); i++) { - var tab = owoGroup.tabs.get(i); - - int xOffset = this.x - 27 - (i / tabStackHeight) * 26; - int yOffset = tabRootY + 10 + (i % tabStackHeight) * 30; + for (int tabIdx = 0; tabIdx < owoGroup.tabs.size(); tabIdx++) { + var tab = owoGroup.tabs.get(tabIdx); - var tabButton = new ItemGroupButtonWidget(xOffset, yOffset, false, tab, owo$createSelectAction(this, owoGroup, i)); + int xOffset = this.x - 27 - (tabIdx / tabStackHeight) * 26; + int yOffset = tabRootY + 10 + (tabIdx % tabStackHeight) * 30; - if (i == owoGroup.getSelectedTabIndex()) tabButton.isSelected = true; + var tabButton = new ItemGroupButtonWidget(xOffset, yOffset, 32, tab, owo$createSelectAction(owoGroup, tabIdx)); + if (owoGroup.isTabSelected(tabIdx)) tabButton.isSelected = true; - owo$buttons.add(tabButton); + this.owo$buttons.add(tabButton); this.addDrawableChild(tabButton); } } @@ -195,26 +161,26 @@ private void setSelectedTab(ItemGroup group, CallbackInfo ci) { var buttons = owoGroup.getButtons(); for (int i = 0; i < buttons.size(); i++) { - var button = buttons.get(i); + var buttonDefinition = buttons.get(i); int xOffset = this.x + 198 + (i / buttonStackHeight) * 26; int yOffset = tabRootY + 10 + (i % buttonStackHeight) * 30; - var tabButton = new ItemGroupButtonWidget(xOffset, yOffset, true, button, button1 -> button.action().run()); + var tabButton = new ItemGroupButtonWidget(xOffset, yOffset, 0, buttonDefinition, __ -> buttonDefinition.action().run()); - owo$buttons.add(tabButton); + this.owo$buttons.add(tabButton); this.addDrawableChild(tabButton); } } } @Inject(at = @At("TAIL"), method = "render") - private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo cbi) { + private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { boolean anyButtonHovered = false; - for (var button : owo$buttons) { + for (var button : this.owo$buttons) { if (button.trulyHovered()) { - context.drawTooltip(this.textRenderer, button.getMessage(), mouseX, mouseY); + context.drawTooltip(this.textRenderer, List.of(button.getMessage(), Text.translatable("text.owo.itemGroup.select_hint")), mouseX, mouseY); anyButtonHovered = true; } } @@ -243,11 +209,17 @@ private void disposeCursorAdapter(CallbackInfo ci) { } @Unique - private ButtonWidget.PressAction owo$createSelectAction(Screen targetScreen, OwoItemGroup group, int targetTabIndex) { + private Consumer owo$createSelectAction(OwoItemGroup group, int tabIdx) { return button -> { - group.setSelectedTab(targetTabIndex, new ItemGroup.DisplayContext(this.owo$enabledFeatures, this.shouldShowOperatorTab(this.handler.player()), this.handler.player().getWorld().getRegistryManager())); + var context = new ItemGroup.DisplayContext(this.owo$enabledFeatures, this.shouldShowOperatorTab(this.handler.player()), this.handler.player().getWorld().getRegistryManager()); + if (Screen.hasShiftDown()) { + group.toggleTab(tabIdx, context); + } else { + group.selectSingleTab(tabIdx, context); + } + this.clearAndInit(); - ((ItemGroupButtonWidget) button).isSelected = true; + button.isSelected = true; }; } diff --git a/src/main/resources/assets/owo/lang/en_us.json b/src/main/resources/assets/owo/lang/en_us.json index 8e491dc1..8cb8c185 100644 --- a/src/main/resources/assets/owo/lang/en_us.json +++ b/src/main/resources/assets/owo/lang/en_us.json @@ -4,6 +4,7 @@ { "text": " > ", "color": "gray" }, { "index": 1, "color": "dark_gray" } ], + "text.owo.itemGroup.select_hint": {"text": "Shift-click to select multiple", "color": "gray"}, "text.owo.configure_hot_reload.title": "Configure Hot Reload", "text.owo.configure_hot_reload.choose_file": "Choose file", "text.owo.configure_hot_reload.choose_ui_model_source": "Choose UI Model source", diff --git a/src/main/resources/assets/owo/textures/gui/tabs.png b/src/main/resources/assets/owo/textures/gui/tabs.png index 791d6570..c4fd916e 100644 Binary files a/src/main/resources/assets/owo/textures/gui/tabs.png and b/src/main/resources/assets/owo/textures/gui/tabs.png differ