diff --git a/src/main/java/net/fabricmc/fabric/api/client/itemgroup/FabricItemGroupBuilder.java b/src/main/java/net/fabricmc/fabric/api/client/itemgroup/FabricItemGroupBuilder.java new file mode 100644 index 000000000..01168e31c --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/client/itemgroup/FabricItemGroupBuilder.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.itemgroup; + +import net.fabricmc.fabric.client.itemgroup.ItemGroupExtensions; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.util.DefaultedList; +import net.minecraft.util.Identifier; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class FabricItemGroupBuilder { + + private Identifier identifier; + private Supplier stackSupplier = () -> ItemStack.EMPTY; + private Consumer> stacksForDisplay; + + private FabricItemGroupBuilder(Identifier identifier) { + this.identifier = identifier; + } + + /** + * + * Create a new Item Group Builder + * + * @param identifier the id will become the name of the ItemGroup and will be used for the translation key + * @return a FabricItemGroupBuilder + */ + public static FabricItemGroupBuilder create(Identifier identifier) { + return new FabricItemGroupBuilder(identifier); + } + + /** + * + * This is used to add an icon to to the item group + * + * @param stackSupplier the supplier should return the item stack that you wish to show on the tab + * @return a reference to the FabricItemGroupBuilder + */ + public FabricItemGroupBuilder icon(Supplier stackSupplier) { + this.stackSupplier = stackSupplier; + return this; + } + + /** + * + * This allows for a custom list of items to be displayed in a tab, this enabled tabs to be created with a custom set of items + * + * @param stacksForDisplay Add ItemStack's to this list to show in the ItemGroup + * @return a reference to the FabricItemGroupBuilder + */ + public FabricItemGroupBuilder stacksForDisplay(Consumer> stacksForDisplay){ + this.stacksForDisplay = stacksForDisplay; + return this; + } + + /** + * + * This is a single method that makes creating an ItemGroup with an icon one call + * + * @param identifier the id will become the name of the ItemGroup and will be used for the translation key + * @param stackSupplier the supplier should return the item stack that you wish to show on the tab + * @return An instance of the built ItemGroup + */ + public static ItemGroup build(Identifier identifier, Supplier stackSupplier){ + return new FabricItemGroupBuilder(identifier).icon(stackSupplier).build(); + } + + /** + * + * Create an instance of the ItemGroup + * + * @return An instance of the built ItemGroup + */ + public ItemGroup build() { + ((ItemGroupExtensions) ItemGroup.BUILDING_BLOCKS).fabric_expandArray(); + return new ItemGroup(ItemGroup.GROUPS.length - 1, identifier.toString()) { + @Override + public ItemStack getIconItem() { + return stackSupplier.get(); + } + + @Override + public void getStacksForDisplay(DefaultedList stacks) { + if(stacksForDisplay != null){ + stacksForDisplay.accept(stacks); + return; + } + super.getStacksForDisplay(stacks); + } + }; + } + +} diff --git a/src/main/java/net/fabricmc/fabric/client/itemgroup/CreativeGuiExtensions.java b/src/main/java/net/fabricmc/fabric/client/itemgroup/CreativeGuiExtensions.java new file mode 100644 index 000000000..32786b6d7 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/client/itemgroup/CreativeGuiExtensions.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.client.itemgroup; + +public interface CreativeGuiExtensions { + + void fabric_nextPage(); + + void fabric_previousPage(); + + int fabric_currentPage(); + + boolean fabric_isButtonVisible(FabricCreativeGuiComponents.Type type); + + boolean fabric_isButtonEnabled(FabricCreativeGuiComponents.Type type); +} diff --git a/src/main/java/net/fabricmc/fabric/client/itemgroup/FabricCreativeGuiComponents.java b/src/main/java/net/fabricmc/fabric/client/itemgroup/FabricCreativeGuiComponents.java new file mode 100644 index 000000000..f4af6bb9e --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/client/itemgroup/FabricCreativeGuiComponents.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.client.itemgroup; + +import com.mojang.blaze3d.platform.GlStateManager; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.ingame.CreativePlayerInventoryGui; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.resource.language.I18n; +import net.minecraft.item.ItemGroup; +import net.minecraft.util.Identifier; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +public class FabricCreativeGuiComponents { + + private static final Identifier BUTTON_TEX = new Identifier("fabric", "textures/gui/creative_buttons.png"); + public static final Set COMMON_GROUPS = new HashSet<>(); + + static { + COMMON_GROUPS.add(ItemGroup.SEARCH); + COMMON_GROUPS.add(ItemGroup.INVENTORY); + COMMON_GROUPS.add(ItemGroup.HOTBAR); + } + + public static class ItemGroupButtonWidget extends ButtonWidget { + + CreativeGuiExtensions extensions; + CreativePlayerInventoryGui gui; + Type type; + + public ItemGroupButtonWidget(int id, int x, int y, Type type, CreativeGuiExtensions extensions) { + super(id, x, y, 10, 11, type.text); + this.extensions = extensions; + this.type = type; + this.gui = (CreativePlayerInventoryGui) extensions; + } + + @Override + public void onPressed(double double_1, double double_2) { + super.onPressed(double_1, double_2); + type.clickConsumer.accept(extensions); + } + + @Override + public void draw(int mouseX, int mouseY, float float_1) { + this.visible = extensions.fabric_isButtonVisible(type); + this.enabled = extensions.fabric_isButtonEnabled(type); + + if (this.visible) { + MinecraftClient minecraftClient = MinecraftClient.getInstance(); + minecraftClient.getTextureManager().bindTexture(BUTTON_TEX); + GlStateManager.color4f(1F, 1F, 1F, 1F); + this.drawTexturedRect(this.x, this.y, (type == Type.NEXT ? 12 : 0), (enabled ? 0 : 12), 12, 12); + + if(mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height){ + gui.drawTooltip(I18n.translate("fabric.gui.creativeTabPage", extensions.fabric_currentPage() + 1, ((ItemGroup.GROUPS.length - 12) / 9) + 2), mouseX, mouseY); + } + } + } + } + + public enum Type { + + NEXT(">", CreativeGuiExtensions::fabric_nextPage), + PREVIOUS("<", CreativeGuiExtensions::fabric_previousPage); + + String text; + Consumer clickConsumer; + + Type(String text, Consumer clickConsumer) { + this.text = text; + this.clickConsumer = clickConsumer; + } + } + +} diff --git a/src/main/java/net/fabricmc/fabric/client/itemgroup/ItemGroupExtensions.java b/src/main/java/net/fabricmc/fabric/client/itemgroup/ItemGroupExtensions.java new file mode 100644 index 000000000..f25dbff0f --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/client/itemgroup/ItemGroupExtensions.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.client.itemgroup; + +public interface ItemGroupExtensions { + + void fabric_expandArray(); + +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/client/itemgroup/MixinCreativePlayerInventoryGui.java b/src/main/java/net/fabricmc/fabric/mixin/client/itemgroup/MixinCreativePlayerInventoryGui.java new file mode 100644 index 000000000..ddab16ded --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/client/itemgroup/MixinCreativePlayerInventoryGui.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.itemgroup; + +import net.fabricmc.fabric.client.itemgroup.CreativeGuiExtensions; +import net.fabricmc.fabric.client.itemgroup.FabricCreativeGuiComponents; +import net.minecraft.client.gui.ingame.AbstractPlayerInventoryGui; +import net.minecraft.client.gui.ingame.CreativePlayerInventoryGui; +import net.minecraft.container.Container; +import net.minecraft.item.ItemGroup; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CreativePlayerInventoryGui.class) +public abstract class MixinCreativePlayerInventoryGui extends AbstractPlayerInventoryGui implements CreativeGuiExtensions { + + @Shadow + protected abstract void setSelectedTab(ItemGroup itemGroup_1); + + private int currentPage = 0; + + public MixinCreativePlayerInventoryGui(Container container_1) { + super(container_1); + } + + @Override + public void fabric_nextPage() { + if ((currentPage + 1) * 12 > ItemGroup.GROUPS.length) { + return; + } + currentPage++; + updateSelection(); + } + + @Override + public void fabric_previousPage() { + if (currentPage == 0) { + return; + } + currentPage--; + updateSelection(); + } + + @Override + public boolean fabric_isButtonVisible(FabricCreativeGuiComponents.Type type) { + return ItemGroup.GROUPS.length != 12; + } + + @Override + public boolean fabric_isButtonEnabled(FabricCreativeGuiComponents.Type type) { + if (type == FabricCreativeGuiComponents.Type.NEXT) { + return !((currentPage + 1) * 12 > ItemGroup.GROUPS.length); + } + if (type == FabricCreativeGuiComponents.Type.PREVIOUS) { + return currentPage != 0; + } + return false; + } + + private void updateSelection() { + int nextTab; + if (currentPage == 0) { + nextTab = 0; + } else { + nextTab = 12 + ((12 - FabricCreativeGuiComponents.COMMON_GROUPS.size()) * (currentPage - 1)); + } + + setSelectedTab(ItemGroup.GROUPS[nextTab]); + } + + @Inject(method = "onInitialized", at = @At("RETURN")) + private void onInitialized(CallbackInfo info) { + updateSelection(); + + int xpos = left + 170; + int ypos = top + 4; + + addButton(new FabricCreativeGuiComponents.ItemGroupButtonWidget(1001, xpos + 10, ypos, FabricCreativeGuiComponents.Type.NEXT, this)); + addButton(new FabricCreativeGuiComponents.ItemGroupButtonWidget(1002, xpos, ypos, FabricCreativeGuiComponents.Type.PREVIOUS, this)); + + } + + @Inject(method = "setSelectedTab", at = @At("HEAD"), cancellable = true) + private void setSelectedTab(ItemGroup itemGroup, CallbackInfo info) { + if (!isGroupVisible(itemGroup)) { + info.cancel(); + } + } + + @Inject(method = "method_2471", at = @At("HEAD"), cancellable = true) + private void method_2471(ItemGroup itemGroup, int mx, int my, CallbackInfoReturnable info) { + if (!isGroupVisible(itemGroup)) { + info.setReturnValue(false); + } + } + + @Inject(method = "isClickInTab", at = @At("HEAD"), cancellable = true) + private void isClickInTab(ItemGroup itemGroup, double mx, double my, CallbackInfoReturnable info) { + if (!isGroupVisible(itemGroup)) { + info.setReturnValue(false); + } + } + + @Inject(method = "method_2468", at = @At("HEAD"), cancellable = true) + private void method_2468(ItemGroup itemGroup, CallbackInfo info) { + if (!isGroupVisible(itemGroup)) { + info.cancel(); + } + } + + private boolean isGroupVisible(ItemGroup itemGroup) { + if (FabricCreativeGuiComponents.COMMON_GROUPS.contains(itemGroup)) { + return true; + } + if (itemGroup.getId() < 12) { + return currentPage == 0; + } + int page = (int) Math.floor((itemGroup.getId() - 12) / (12 - FabricCreativeGuiComponents.COMMON_GROUPS.size())); + return currentPage == page + 1; + + } + + @Override + public int fabric_currentPage() { + return currentPage; + } +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/client/itemgroup/MixinItemGroup.java b/src/main/java/net/fabricmc/fabric/mixin/client/itemgroup/MixinItemGroup.java new file mode 100644 index 000000000..630ca7fa5 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/client/itemgroup/MixinItemGroup.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.itemgroup; + +import net.fabricmc.fabric.client.itemgroup.FabricCreativeGuiComponents; +import net.fabricmc.fabric.client.itemgroup.ItemGroupExtensions; +import net.minecraft.item.ItemGroup; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ItemGroup.class) +public abstract class MixinItemGroup implements ItemGroupExtensions { + + @Shadow + @Final + @Mutable + public static ItemGroup[] GROUPS; + + @Shadow + public abstract int getId(); + + @Shadow + public abstract boolean isTopRow(); + + @Shadow + @Final + private int id; + + @Override + public void fabric_expandArray() { + ItemGroup[] tempGroups = GROUPS; + GROUPS = new ItemGroup[GROUPS.length + 1]; + for (ItemGroup group : tempGroups) { + GROUPS[group.getId()] = group; + } + } + + @Inject(method = "isTopRow", cancellable = true, at = @At("HEAD")) + private void isTopRow(CallbackInfoReturnable info) { + if (getId() > 11) { + info.setReturnValue((id - 12) % (12 - FabricCreativeGuiComponents.COMMON_GROUPS.size()) < 4); + } + } + + @Inject(method = "getColumn", cancellable = true, at = @At("HEAD")) + private void getColumn(CallbackInfoReturnable info) { + if (getId() > 11) { + if (isTopRow()) { + info.setReturnValue((id - 12) % (12 - FabricCreativeGuiComponents.COMMON_GROUPS.size())); + } else { + info.setReturnValue((id - 12) % (12 - FabricCreativeGuiComponents.COMMON_GROUPS.size()) - 4); + } + + } + } +} diff --git a/src/main/resources/assets/fabric/lang/en_us.json b/src/main/resources/assets/fabric/lang/en_us.json new file mode 100644 index 000000000..35c4d8b12 --- /dev/null +++ b/src/main/resources/assets/fabric/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "fabric.gui.creativeTabPage": "Page %d/%d" +} \ No newline at end of file diff --git a/src/main/resources/assets/fabric/textures/gui/creative_buttons.png b/src/main/resources/assets/fabric/textures/gui/creative_buttons.png new file mode 100644 index 000000000..1c28eaccd Binary files /dev/null and b/src/main/resources/assets/fabric/textures/gui/creative_buttons.png differ diff --git a/src/main/resources/net.fabricmc.fabric.mixins.client.json b/src/main/resources/net.fabricmc.fabric.mixins.client.json index 4c8fa0393..b4f122565 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.client.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.client.json @@ -5,6 +5,8 @@ "mixins": [ "block.entity.MixinClientPlayNetworkHandler", "bugfix.MixinBiomeColors", + "client.itemgroup.MixinItemGroup", + "client.itemgroup.MixinCreativePlayerInventoryGui", "client.render.MixinBlockColorMap", "client.render.MixinBlockEntityRenderManager", "client.render.MixinEntityRenderManager", diff --git a/src/test/java/net/fabricmc/fabric/itemgroup/ItemGroupMod.java b/src/test/java/net/fabricmc/fabric/itemgroup/ItemGroupMod.java new file mode 100644 index 000000000..a50b53dcd --- /dev/null +++ b/src/test/java/net/fabricmc/fabric/itemgroup/ItemGroupMod.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.itemgroup; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.itemgroup.FabricItemGroupBuilder; +import net.minecraft.block.Blocks; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + + +public class ItemGroupMod implements ClientModInitializer { + @Override + public void onInitializeClient() { + //This creates your standard Item Group + ItemGroup group = FabricItemGroupBuilder.build(new Identifier("fabric", "fabric_test_tab"), () -> new ItemStack(Items.IRON_CHESTPLATE)); + Item testItem = new Item(new Item.Settings().itemGroup(group)); + Registry.ITEM.register(new Identifier("fabric_test", "itemgroup"), testItem); + + + //Creates a tab with all items (including ones that dont show in search such as the command block) + FabricItemGroupBuilder.create(new Identifier("fabric", "all")).stacksForDisplay(itemStacks -> Registry.ITEM.forEach(item -> itemStacks.add(new ItemStack(item)))).build(); + + //Creates a group with all modded items in + FabricItemGroupBuilder.create(new Identifier("fabric", "modded")).stacksForDisplay(itemStacks -> Registry.ITEM.forEach(item -> { + if(!Registry.ITEM.getId(item).getNamespace().equals("minecraft")){ + itemStacks.add(new ItemStack(item)); + } + })).icon(() -> new ItemStack(Blocks.TNT)).build(); + + + //These are just padding to ensure more than one page works + FabricItemGroupBuilder.create(new Identifier("fabric", "test1")).icon(() -> new ItemStack(Items.APPLE)).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test2")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test3")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test4")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test5")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test6")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test7")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test8")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test9")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test10")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test11")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test12")).build(); + FabricItemGroupBuilder.create(new Identifier("fabric", "test13")).build(); + + } +}