diff options
Diffstat (limited to 'src')
19 files changed, 1791 insertions, 59 deletions
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/CottonScreenController.java b/src/main/java/io/github/cottonmc/cotton/gui/CottonScreenController.java new file mode 100644 index 0000000..47b56f6 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/CottonScreenController.java @@ -0,0 +1,397 @@ +package io.github.cottonmc.cotton.gui; + +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import io.github.cottonmc.cotton.gui.client.BackgroundPainter; +import io.github.cottonmc.cotton.gui.widget.WGridPanel; +import io.github.cottonmc.cotton.gui.widget.WItemSlot; +import io.github.cottonmc.cotton.gui.widget.WPanel; +import io.github.cottonmc.cotton.gui.widget.WPlainPanel; +import io.github.cottonmc.cotton.gui.widget.WWidget; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.InventoryProvider; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.container.ArrayPropertyDelegate; +import net.minecraft.container.BlockContext; +import net.minecraft.container.CraftingContainer; +import net.minecraft.container.PropertyDelegate; +import net.minecraft.container.Slot; +import net.minecraft.container.SlotActionType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeFinder; +import net.minecraft.recipe.RecipeInputProvider; +import net.minecraft.recipe.RecipeType; +import net.minecraft.world.World; + +public abstract class CottonScreenController extends CraftingContainer<Inventory> { + + protected Inventory blockInventory; + protected PlayerInventory playerInventory; + protected RecipeType<?> recipeType; + protected World world; + protected PropertyDelegate propertyDelegate; + + protected WPanel rootPanel = new WGridPanel(); + protected int titleColor = 0xFF404040; + + public CottonScreenController(RecipeType<?> recipeType, int syncId, PlayerInventory playerInventory) { + super(null, syncId); + this.blockInventory = null; + this.playerInventory = playerInventory; + this.recipeType = recipeType; + this.world = playerInventory.player.world; + this.propertyDelegate = null;//new ArrayPropertyDelegate(1); + } + + public CottonScreenController(RecipeType<?> recipeType, int syncId, PlayerInventory playerInventory, Inventory blockInventory, PropertyDelegate propertyDelegate) { + super(null, syncId); + this.blockInventory = blockInventory; + this.playerInventory = playerInventory; + this.recipeType = recipeType; + this.world = playerInventory.player.world; + this.propertyDelegate = propertyDelegate; + if (propertyDelegate!=null && propertyDelegate.size()>0) this.addProperties(propertyDelegate); + } + + public WPanel getRootPanel() { + return rootPanel; + } + + public int getTitleColor() { + return titleColor; + } + + public CottonScreenController setRootPanel(WPanel panel) { + this.rootPanel = panel; + return this; + } + + public CottonScreenController setTitleColor(int color) { + this.titleColor = color; + return this; + } + + @Environment(EnvType.CLIENT) + public void addPainters() { + if (this.rootPanel!=null) { + this.rootPanel.setBackgroundPainter(BackgroundPainter.VANILLA); + } + } + + public void addSlotPeer(ValidatedSlot slot) { + this.addSlot(slot); + } + + @Override + public ItemStack onSlotClick(int slotNumber, int button, SlotActionType action, PlayerEntity player) { + if (action==SlotActionType.QUICK_MOVE) { + + if (slotNumber < 0) { + return ItemStack.EMPTY; + } + + if (slotNumber>=this.slotList.size()) return ItemStack.EMPTY; + Slot slot = this.slotList.get(slotNumber); + if (slot == null || !slot.canTakeItems(player)) { + return ItemStack.EMPTY; + } + + ItemStack remaining = ItemStack.EMPTY; + if (slot != null && slot.hasStack()) { + ItemStack toTransfer = slot.getStack(); + remaining = toTransfer.copy(); + //if (slot.inventory==blockInventory) { + if (blockInventory!=null) { + if (slot.inventory==blockInventory) { + //Try to transfer the item from the block into the player's inventory + if (!this.insertItem(toTransfer, this.playerInventory, true)) { + return ItemStack.EMPTY; + } + } else if (!this.insertItem(toTransfer, this.blockInventory, false)) { //Try to transfer the item from the player to the block + return ItemStack.EMPTY; + } + } else { + //There's no block, just swap between the player's storage and their hotbar + if (!swapHotbar(toTransfer, slotNumber, this.playerInventory)) { + return ItemStack.EMPTY; + } + } + + if (toTransfer.isEmpty()) { + slot.setStack(ItemStack.EMPTY); + } else { + slot.markDirty(); + } + } + + return remaining; + } else { + return super.onSlotClick(slotNumber, button, action, player); + } + } + + /** WILL MODIFY toInsert! Returns true if anything was inserted. */ + private boolean insertIntoExisting(ItemStack toInsert, Slot slot) { + ItemStack curSlotStack = slot.getStack(); + if (!curSlotStack.isEmpty() && canStacksCombine(toInsert, curSlotStack)) { + int combinedAmount = curSlotStack.getCount() + toInsert.getCount(); + if (combinedAmount <= toInsert.getMaxCount()) { + toInsert.setCount(0); + curSlotStack.setCount(combinedAmount); + slot.markDirty(); + return true; + } else if (curSlotStack.getCount() < toInsert.getMaxCount()) { + toInsert.decrement(toInsert.getMaxCount() - curSlotStack.getCount()); + curSlotStack.setCount(toInsert.getMaxCount()); + slot.markDirty(); + return true; + } + } + return false; + } + + /** WILL MODIFY toInsert! Returns true if anything was inserted. */ + private boolean insertIntoEmpty(ItemStack toInsert, Slot slot) { + ItemStack curSlotStack = slot.getStack(); + if (curSlotStack.isEmpty() && slot.canInsert(toInsert)) { + if (toInsert.getCount() > slot.getMaxStackAmount()) { + slot.setStack(toInsert.split(slot.getMaxStackAmount())); + } else { + slot.setStack(toInsert.split(toInsert.getCount())); + } + + slot.markDirty(); + return true; + } + + return false; + } + + private boolean insertItem(ItemStack toInsert, Inventory inventory, boolean walkBackwards) { + //Make a unified list of slots *only from this inventory* + ArrayList<Slot> inventorySlots = new ArrayList<>(); + for(Slot slot : slotList) { + if (slot.inventory==inventory) inventorySlots.add(slot); + } + if (inventorySlots.isEmpty()) return false; + + //Try to insert it on top of existing stacks + boolean inserted = false; + if (walkBackwards) { + for(int i=inventorySlots.size()-1; i>=0; i--) { + Slot curSlot = inventorySlots.get(i); + if (insertIntoExisting(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + } else { + for(int i=0; i<inventorySlots.size(); i++) { + Slot curSlot = inventorySlots.get(i); + if (insertIntoExisting(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + + } + + //If we still have any, shove them into empty slots + if (!toInsert.isEmpty()) { + if (walkBackwards) { + for(int i=inventorySlots.size()-1; i>=0; i--) { + Slot curSlot = inventorySlots.get(i); + if (insertIntoEmpty(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + } else { + for(int i=0; i<inventorySlots.size(); i++) { + Slot curSlot = inventorySlots.get(i); + if (insertIntoEmpty(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + + } + } + + return inserted; + } + + private boolean swapHotbar(ItemStack toInsert, int slotNumber, Inventory inventory) { + //Feel out the slots to see what's storage versus hotbar + ArrayList<Slot> storageSlots = new ArrayList<>(); + ArrayList<Slot> hotbarSlots = new ArrayList<>(); + boolean swapToStorage = true; + boolean inserted = false; + + for(Slot slot : slotList) { + if (slot.inventory==inventory && slot instanceof ValidatedSlot) { + int index = ((ValidatedSlot)slot).getInventoryIndex(); + if (PlayerInventory.isValidHotbarIndex(index)) { + hotbarSlots.add(slot); + } else { + storageSlots.add(slot); + if (index==slotNumber) swapToStorage = false; + } + } + } + if (storageSlots.isEmpty() || hotbarSlots.isEmpty()) return false; + + if (swapToStorage) { + //swap from hotbar to storage + for(int i=0; i<storageSlots.size(); i++) { + Slot curSlot = storageSlots.get(i); + if (insertIntoExisting(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + if (!toInsert.isEmpty()) { + for(int i=0; i<storageSlots.size(); i++) { + Slot curSlot = storageSlots.get(i); + if (insertIntoEmpty(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + } + } else { + //swap from storage to hotbar + for(int i=0; i<hotbarSlots.size(); i++) { + Slot curSlot = hotbarSlots.get(i); + if (insertIntoExisting(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + if (!toInsert.isEmpty()) { + for(int i=0; i<hotbarSlots.size(); i++) { + Slot curSlot = hotbarSlots.get(i); + if (insertIntoEmpty(toInsert, curSlot)) inserted = true; + if (toInsert.isEmpty()) break; + } + } + } + + return inserted; + } + + @Nullable + public WWidget doMouseUp(int x, int y, int state) { + if (rootPanel!=null) return rootPanel.onMouseUp(x, y, state); + return null; + } + + @Nullable + public WWidget doMouseDown(int x, int y, int button) { + if (rootPanel!=null) return rootPanel.onMouseDown(x, y, button); + return null; + } + + public void doMouseDrag(int x, int y, int button) { + if (rootPanel!=null) rootPanel.onMouseDrag(x, y, button); + } + + public void doClick(int x, int y, int button) { + if (rootPanel!=null) rootPanel.onClick(x, y, button); + } + + /** + * Gets the PropertyDelegate associated with this Controller. + */ + @Nullable + public PropertyDelegate getPropertyDelegate() { + // TODO Auto-generated method stub + return propertyDelegate; + } + + public WPanel createPlayerInventoryPanel() { + WPlainPanel inv = new WPlainPanel(); + inv.add(WItemSlot.ofPlayerStorage(playerInventory), 0, 0); + inv.add(WItemSlot.of(playerInventory, 0, 9, 1), 0, 16*4 - 6); + return inv; + } + + public static Inventory getBlockInventory(BlockContext ctx) { + return ctx.run((world, pos) -> { + BlockState state = world.getBlockState(pos); + Block b = state.getBlock(); + + if (b instanceof InventoryProvider) { + return ((InventoryProvider)b).getInventory(state, world, pos); + } + + BlockEntity be = world.getBlockEntity(pos); + if (be!=null) { + if (be instanceof InventoryProvider) { + return ((InventoryProvider)be).getInventory(state, world, pos); + } else if (be instanceof Inventory) { + return (Inventory)be; + } + } + + return EmptyInventory.INSTANCE; + }).orElse(EmptyInventory.INSTANCE); + } + + public static PropertyDelegate getBlockPropertyDelegate(BlockContext ctx) { + return ctx.run((world, pos) -> { + BlockState state = world.getBlockState(pos); + Block block = state.getBlock(); + if (block instanceof PropertyDelegateHolder) { + return ((PropertyDelegateHolder)block).getPropertyDelegate(); + } + BlockEntity be = world.getBlockEntity(pos); + if (be!=null && be instanceof PropertyDelegateHolder) { + return ((PropertyDelegateHolder)be).getPropertyDelegate(); + } + + return new ArrayPropertyDelegate(0); + }).orElse(new ArrayPropertyDelegate(0)); + } + + //extends CraftingContainer<Inventory> { + @Override + public void populateRecipeFinder(RecipeFinder recipeFinder) { + if (this.blockInventory instanceof RecipeInputProvider) { + ((RecipeInputProvider)this.blockInventory).provideRecipeInputs(recipeFinder); + } + } + + @Override + public void clearCraftingSlots() { + if (this.blockInventory!=null) this.blockInventory.clear(); + } + + @Override + public boolean matches(Recipe<? super Inventory> recipe) { + if (blockInventory==null || world==null) return false; + return false; //TODO recipe support + } + + @Override + public abstract int getCraftingResultSlotIndex(); + + @Override + public int getCraftingWidth() { + return 1; + } + + @Override + public int getCraftingHeight() { + return 1; + } + + @Override + @Environment(EnvType.CLIENT) + public int getCraftingSlotCount() { + return 1; + } + + //(implied) extends Container { + @Override + public boolean canUse(PlayerEntity entity) { + return (blockInventory!=null) ? blockInventory.canPlayerUseInv(entity) : true; + } + //} + //} +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/EmptyInventory.java b/src/main/java/io/github/cottonmc/cotton/gui/EmptyInventory.java new file mode 100644 index 0000000..a80b5da --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/EmptyInventory.java @@ -0,0 +1,53 @@ +package io.github.cottonmc.cotton.gui; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; + +public class EmptyInventory implements Inventory { + public static final EmptyInventory INSTANCE = new EmptyInventory(); + + private EmptyInventory() {} + + @Override + public void clear() {} + + @Override + public int getInvSize() { + return 0; + } + + @Override + public boolean isInvEmpty() { + return true; + } + + @Override + public ItemStack getInvStack(int var1) { + return ItemStack.EMPTY; + } + + @Override + public ItemStack takeInvStack(int var1, int var2) { + return ItemStack.EMPTY; + } + + @Override + public ItemStack removeInvStack(int var1) { + return ItemStack.EMPTY; + } + + @Override + public void setInvStack(int var1, ItemStack var2) { + } + + @Override + public void markDirty() { + } + + @Override + public boolean canPlayerUseInv(PlayerEntity var1) { + return false; + } + +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/PropertyDelegateHolder.java b/src/main/java/io/github/cottonmc/cotton/gui/PropertyDelegateHolder.java new file mode 100644 index 0000000..6ec1476 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/PropertyDelegateHolder.java @@ -0,0 +1,7 @@ +package io.github.cottonmc.cotton.gui; + +import net.minecraft.container.PropertyDelegate; + +public interface PropertyDelegateHolder { + public PropertyDelegate getPropertyDelegate(); +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java b/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java new file mode 100644 index 0000000..9301761 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java @@ -0,0 +1,46 @@ +package io.github.cottonmc.cotton.gui; + +import net.minecraft.container.Slot; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; + +public class ValidatedSlot extends Slot { + private final int slotNumber; + + public ValidatedSlot(Inventory inventoryIn, int index, int xPosition, int yPosition) { + super(inventoryIn, index, xPosition, yPosition); + if (inventoryIn==null) throw new IllegalArgumentException("Can't make an itemslot from a null inventory!"); + this.slotNumber = index; + } + + @Override + public boolean canInsert(ItemStack stack) { + return inventory.isValidInvStack(slotNumber, stack); + } + + @Override + public boolean canTakeItems(PlayerEntity player) { + return inventory.canPlayerUseInv(player); + } + + @Override + public ItemStack getStack() { + if (inventory==null) { + System.out.println("Prevented null-inventory from WItemSlot with slot #: "+slotNumber); + return ItemStack.EMPTY; + } + + ItemStack result = super.getStack(); + if (result==null) { + System.out.println("Prevented null-itemstack crash from: "+inventory.getClass().getCanonicalName()); + return ItemStack.EMPTY; + } + + return result; + } + + public int getInventoryIndex() { + return slotNumber; + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java b/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java new file mode 100644 index 0000000..38f9130 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java @@ -0,0 +1,37 @@ +package io.github.cottonmc.cotton.gui.client; + +import io.github.cottonmc.cotton.gui.widget.WWidget; + +public interface BackgroundPainter { + /** + * Paint the specified panel to the screen. + * @param left The absolute position of the left of the panel, in gui-screen coordinates + * @param top The absolute position of the top of the panel, in gui-screen coordinates + * @param panel The panel being painted + */ + public void paintBackground(int left, int top, WWidget panel); + + + + public static BackgroundPainter VANILLA = (left, top, panel) -> { + ScreenDrawing.drawGuiPanel(left-8, top-8, panel.getWidth()+16, panel.getHeight()+16); + + }; + + + + public static BackgroundPainter createColorful(int panelColor) { + return (left, top, panel) -> { + ScreenDrawing.drawGuiPanel(left-8, top-8, panel.getWidth()+16, panel.getHeight()+16, panelColor); + }; + } + + public static BackgroundPainter createColorful(int panelColor, float contrast) { + return (left, top, panel) -> { + int shadowColor = ScreenDrawing.multiplyColor(panelColor, 1.0f - contrast); + int hilightColor = ScreenDrawing.multiplyColor(panelColor, 1.0f + contrast); + + ScreenDrawing.drawGuiPanel(left-8, top-8, panel.getWidth()+16, panel.getHeight()+16, shadowColor, panelColor, hilightColor, 0xFF000000); + }; + } +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonScreen.java b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonScreen.java new file mode 100644 index 0000000..dc78a25 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonScreen.java @@ -0,0 +1,237 @@ +package io.github.cottonmc.cotton.gui.client; + +import io.github.cottonmc.cotton.gui.CottonScreenController; +import io.github.cottonmc.cotton.gui.widget.WPanel; +import io.github.cottonmc.cotton.gui.widget.WWidget; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Nameable; + +public class CottonScreen<T extends CottonScreenController> extends AbstractContainerScreen<T> { + protected CottonScreenController container; + public static final int PADDING = 8; + protected WWidget lastResponder = null; + + public CottonScreen(T container, PlayerEntity player) { + super(container, player.inventory, new LiteralText("")); + this.container = container; + width = 18*9; + height = 18*9; + this.containerWidth = 18*9; + this.containerHeight = 18*9; + } + + /* + * RENDERING NOTES: + * + * * "width" and "height" are the width and height of the overall screen + * * "containerWidth" and "containerHeight" are the width and height of the panel to render + * * "left" and "top" are *actually* self-explanatory + * * coordinates start at 0,0 at the topleft of the screen. + */ + + + /* + * These methods are called frequently and empty, meaning they're probably *meant* for subclasses to override to + * provide core GUI functionality. + */ + + @Override + public void init(MinecraftClient minecraftClient_1, int screenWidth, int screenHeight) { + super.init(minecraftClient_1, screenWidth, screenHeight); + + container.addPainters(); + + reposition(); + } + + public void reposition() { + WPanel basePanel = container.getRootPanel(); + if (basePanel!=null) { + basePanel.validate(container); + + containerWidth = basePanel.getWidth(); + containerHeight = basePanel.getHeight(); + + //DEBUG + if (containerWidth<16) containerWidth=300; + if (containerHeight<16) containerHeight=300; + //if (left<0 || left>300) left = 10; + //if (top<0 || top>300) top = 10; + } + left = (width / 2) - (containerWidth / 2); + top = (height / 2) - (containerHeight / 2); + } + + //Will probably re-activate for animation! + //@Override + //public void updateScreen() { + // System.out.println("updateScreen"); + //} + + @Override + public void onClose() { + super.onClose(); + } + + @Override + public boolean isPauseScreen() { + //...yeah, we're going to go ahead and override that. + return false; + } + + /* + * While these methods are implemented in GuiScreen, chances are we'll be shadowing a lot of the GuiScreen methods + * in order to implement our own button protocol and more advanced features. + */ + + + @Override + public boolean charTyped(char typedChar, int keyCode) { + if (MinecraftClient.getInstance().options.keyInventory.matchesKey(keyCode, keyCode)); + + return super.charTyped(typedChar, keyCode); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) { + boolean result = super.mouseClicked(mouseX, mouseY, mouseButton); + int containerX = (int)mouseX-left; + int containerY = (int)mouseY-top; + if (containerX<0 || containerY<0 || containerX>=width || containerY>=height) return result; + lastResponder = container.doMouseDown(containerX, containerY, mouseButton); + return result; + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int mouseButton) { //Testing shows that STATE IS ACTUALLY BUTTON + boolean result = super.mouseReleased(mouseX, mouseY, mouseButton); + int containerX = (int)mouseX-left; + int containerY = (int)mouseY-top; + if (containerX<0 || containerY<0 || containerX>=width || containerY>=height) return result; + + WWidget responder = container.doMouseUp(containerX, containerY, mouseButton); + if (responder!=null && responder==lastResponder) container.doClick(containerX, containerY, mouseButton); + lastResponder = null; + return result; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double unknown_1, double unknown_2) { + boolean result = super.mouseDragged(mouseX, mouseY, mouseButton, unknown_1, unknown_2); + + int containerX = (int)mouseX-left; + int containerY = (int)mouseY-top; + if (containerX<0 || containerY<0 || containerX>=width || containerY>=height) return result; + container.doMouseDrag(containerX, containerY, mouseButton); + return result; + } + + /* + @Override + protected void actionPerformed(GuiButton button) throws IOException { + super.actionPerformed(button); + }*/ + + /* + * We'll probably wind up calling some of this manually, but they do useful things for us so we may leave + * them unharmed. + */ + /* + @Override + public void setWorldAndResolution(Minecraft mc, int width, int height) { + super.setWorldAndResolution(mc, width, height); + + WPanel basePanel = container.getRootPanel(); + if (basePanel!=null) { + xSize = basePanel.getWidth(); + ySize = basePanel.getHeight(); + } + left = (width / 2) - (xSize / 2); + top = (height / 2) - (ySize / 2); + + } + */ + + @Override + public void resize(MinecraftClient minecraftClient_1, int int_1, int int_2) { + //super.onScaleChanged(minecraftClient_1, int_1, int_2); + this.width = int_1; + this.height = int_2; + reposition(); + } + + /* + * SPECIAL FUNCTIONS: Where possible, we want to draw everything based on *actual GUI state and composition* rather + * than relying on pre-baked textures that the programmer then needs to carefully match up their GUI to. + */ + + private int multiplyColor(int color, float amount) { + int a = color & 0xFF000000; + float r = (color >> 16 & 255) / 255.0F; + float g = (color >> 8 & 255) / 255.0F; + float b = (color & 255) / 255.0F; + + r = Math.min(r*amount, 1.0f); + g = Math.min(g*amount, 1.0f); + b = Math.min(b*amount, 1.0f); + + int ir = (int)(r*255); + int ig = (int)(g*255); + int ib = (int)(b*255); + + return a | + (ir << 16) | + (ig << 8) | + ib; + } + + @Override + protected void drawBackground(float partialTicks, int mouseX, int mouseY) { + if (this.container==null) { + System.out.println("CONTAINER IS NULL."); + return; + } + WPanel root = this.container.getRootPanel(); + if (root==null) return; + + root.paintBackground(left, top); + + //TODO: Change this to a label that lives in the rootPanel instead? + if (container instanceof Nameable) { + Text name = ((Nameable)container).getDisplayName(); + font.draw(name.asFormattedString(), left, top, container.getTitleColor()); + } else if (getTitle() != null) { + font.draw(getTitle().asFormattedString(), left, top, container.getTitleColor()); + } + } + + @Override + protected void drawForeground(int mouseX, int mouseY) { + //if (cursorDragSlots != null && this.container.getRootPanel() != null) { + if (this.container==null) { + System.out.println("CONTAINER IS NULL."); + return; + } + + if (this.container.getRootPanel()!=null) { + this.container.getRootPanel().paintForeground(left, top, mouseX, mouseY); + } + //} + } + + @Override + public void render(int mouseX, int mouseY, float partialTicks) { + // Render the background shadow + this.renderBackground(); + + this.drawBackground(partialTicks, mouseX, mouseY); + + super.render(mouseX, mouseY, partialTicks); + drawMouseoverTooltip(mouseX, mouseY); + } + +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java b/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java new file mode 100644 index 0000000..5592e5d --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java @@ -0,0 +1,235 @@ +package io.github.cottonmc.cotton.gui.client; + +import org.lwjgl.opengl.GL11; + +import com.mojang.blaze3d.platform.GlStateManager; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.BufferBuilder; +import net.minecraft.client.render.Tessellator; +import net.minecraft.client.render.VertexFormats; +import net.minecraft.util.Identifier; + +public class ScreenDrawing { + private ScreenDrawing() {} + + public static void rect(Identifier texture, int left, int top, int width, int height, int color) { + rect(texture, left, top, width, height, 0, 0, 1, 1, color, 0); + } + + public static void rect(Identifier texture, int left, int top, int width, int height, float u1, float v1, float u2, float v2, int color) { + rect(texture, left, top, width, height, u1, v1, u2, v2, 0xFFFFFFFF, 0); + } + + public static void rect(Identifier texture, int left, int top, int width, int height, float u1, float v1, float u2, float v2, int color, int z) { + MinecraftClient.getInstance().getTextureManager().bindTexture(texture); + + //float scale = 0.00390625F; + + if (width <= 0) width = 1; + if (height <= 0) height = 1; + + float r = (color >> 16 & 255) / 255.0F; + float g = (color >> 8 & 255) / 255.0F; + float b = (color & 255) / 255.0F; + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBufferBuilder(); + GlStateManager.enableBlend(); + //GlStateManager.disableTexture2D(); + GlStateManager.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.color4f(r, g, b, 1.0f); + buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION_UV); //I thought GL_QUADS was deprecated but okay, sure. + buffer.vertex(left, top + height, z).texture(u1, v2).next(); + buffer.vertex(left + width, top + height, z).texture(u2, v2).next(); + buffer.vertex(left + width, top, z).texture(u2, v1).next(); + buffer.vertex(left, top, z).texture(u1, v1).next(); + tessellator.draw(); + //GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + } + + /** + * Draws an untextured rectangle of the specified RGB color. + */ + public static void rect(int left, int top, int width, int height, int color) { + if (width <= 0) width = 1; + if (height <= 0) height = 1; + + float a = (color >> 24 & 255) / 255.0F; + float r = (color >> 16 & 255) / 255.0F; + float g = (color >> 8 & 255) / 255.0F; + float b = (color & 255) / 255.0F; + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBufferBuilder(); + GlStateManager.enableBlend(); + GlStateManager.disableTexture(); + GlStateManager.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.color4f(r, g, b, a); + buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION); //I thought GL_QUADS was deprecated but okay, sure. + buffer.vertex(left, top + height, 0.0D).next(); + buffer.vertex(left + width, top + height, 0.0D).next(); + buffer.vertex(left + width, top, 0.0D).next(); + buffer.vertex(left, top, 0.0D).next(); + tessellator.draw(); + GlStateManager.enableTexture(); + GlStateManager.disableBlend(); + } + + public static void maskedRect(Identifier mask, Identifier texture, int left, int top, int width, int height) { + + + rect(mask, left, top, width, height, 0, 0, 1, 1, 0xFFFFFFFF, 7); + + GlStateManager.enableDepthTest(); + GlStateManager.depthFunc(GL11.GL_EQUAL); + + rect(texture, left, top, width, height, 0, 0, 1, 1, 0xFFFFFFFF, 7); + + GlStateManager.depthFunc(GL11.GL_LESS); + GlStateManager.disableDepthTest(); + } + + /** + * Draws a rectangle for a Fluid, because fluids are tough. + */ + /* + public static void rect(Fluid fluid, int left, int top, int width, int height, float u1, float v1, float u2, float v2, int color) { + Identifier fluidTexture = fluid.getStill(); + + TextureAtlasSprite tas = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(fluidTexture.toString()); + Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); + + if (width <= 0) width = 1; + if (height <= 0) height = 1; + + float a = (color >> 24 & 255) / 255.0F; + float r = (color >> 16 & 255) / 255.0F; + float g = (color >> 8 & 255) / 255.0F; + float b = (color & 255) / 255.0F; + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBufferBuilder(); + GlStateManager.enableBlend(); + //GlStateManager.disableTexture2D(); + GlStateManager.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.color4f(r, g, b, a); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); //I thought GL_QUADS was deprecated but okay, sure. + buffer.pos(left, top + height, 0.0D).tex(tas.getInterpolatedU(u1), tas.getInterpolatedV(v2)).endVertex(); + buffer.pos(left + width, top + height, 0.0D).tex(tas.getInterpolatedU(u2), tas.getInterpolatedV(v2)).endVertex(); + buffer.pos(left + width, top, 0.0D).tex(tas.getInterpolatedU(u2), tas.getInterpolatedV(v1)).endVertex(); + buffer.pos(left, top, 0.0D).tex(tas.getInterpolatedU(u1), tas.getInterpolatedV(v1)).endVertex(); + tessellator.draw(); + //GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + }*/ + + /* + public static void rect(Fluid fluid, int left, int top, int width, int height, int color) { + rect(fluid, left, top, width, height, 0, 0, 16, 16, color); + }*/ + + /** + * Draws a beveled, round rectangle that is substantially similar to default Minecraft UI panels. + */ + public static void drawGuiPanel(int x, int y, int width, int height) { + drawGuiPanel(x, y, width, height, 0xFF555555, 0xFFC6C6C6, 0xFFFFFFFF, 0xFF000000); + } + + public static void drawGuiPanel(int x, int y, int width, int height, int panelColor) { + int shadowColor = multiplyColor(panelColor, 0.50f); + int hilightColor = multiplyColor(panelColor, 1.25f); + + drawGuiPanel(x, y, width, height, shadowColor, panelColor, hilightColor, 0xFF000000); + } + + public static void drawGuiPanel(int x, int y, int width, int height, int shadow, int panel, int hilight, int outline) { + rect(x + 3, y + 3, width - 6, height - 6, panel); //Main panel area + + rect(x + 2, y + 1, width - 4, 2, hilight); //Top hilight + rect(x + 2, y + height - 3, width - 4, 2, shadow); //Bottom shadow + rect(x + 1, y + 2, 2, height - 4, hilight); //Left hilight + rect(x + width - 3, y + 2, 2, height - 4, shadow); //Right shadow + rect(x + width - 3, y + 2, 1, 1, panel); //Topright non-hilight/non-shadow transition pixel + rect(x + 2, y + height - 3, 1, 1, panel); //Bottomleft non-hilight/non-shadow transition pixel + rect(x + 3, y + 3, 1, 1, hilight); //Topleft round hilight pixel + rect(x + width - 4, y + height - 4, 1, 1, shadow); //Bottomright round shadow pixel + + rect(x + 2, y, width - 4, 1, outline); //Top outline + rect(x, y + 2, 1, height - 4, outline); //Left outline + rect(x + width - 1, y + 2, 1, height - 4, outline); //Right outline + rect(x + 2, y + height - 1, width - 4, 1, outline); //Bottom outline + rect(x + 1, y + 1, 1, 1, outline); //Topleft round pixel + rect(x + 1, y + height - 2, 1, 1, outline); //Bottomleft round pixel + rect(x + width - 2, y + 1, 1, 1, outline); //Topright round pixel + rect(x + width - 2, y + height - 2, 1, 1, outline); //Bottomright round pixel + } + + /** + * Draws a default-sized recessed itemslot panel + */ + public static void drawBeveledPanel(int x, int y) { + drawBeveledPanel(x, y, 18, 18, 0xFF373737, 0xFF8b8b8b, 0xFFFFFFFF); + } + + /** + * Draws a default-color recessed itemslot panel of variable size + */ + public static void drawBeveledPanel(int x, int y, int width, int height) { + drawBeveledPanel(x, y, width, height, 0xFF373737, 0xFF8b8b8b, 0xFFFFFFFF); + } + + /** + * Draws a generalized-case beveled panel. Can be inset or outset depending on arguments. + * @param x x coordinate of the topleft corner + * @param y y coordinate of the topleft corner + * @param width width of the panel + * @param height height of the panel + * @param topleft color of the top/left bevel + * @param panel color of the panel area + * @param bottomright color of the bottom/right bevel + */ + public static void drawBeveledPanel(int x, int y, int width, int height, int topleft, int panel, int bottomright) { + rect(x, y, width, height, panel); //Center panel + rect(x, y, width - 1, 1, topleft); //Top shadow + rect(x, y + 1, 1, height - 2, topleft); //Left shadow + rect(x + width - 1, y + 1, 1, height - 1, bottomright); //Right hilight + rect(x + 1, y + height - 1, width - 1, 1, bottomright); //Bottom hilight + } + + public static void drawString(String s, int x, int y, int color) { + MinecraftClient.getInstance().getFontManager().getTextRenderer(MinecraftClient.DEFAULT_TEXT_RENDERER_ID).draw(s, x, y, color); + } + + public static void drawTooltip(String s, int x, int y) { + + } + + public static int colorAtOpacity(int opaque, float opacity) { + if (opacity<0.0f) opacity=0.0f; + if (opacity>1.0f) opacity=1.0f; + + int a = (int)(opacity * 255.0f); + + return (opaque & 0xFFFFFF) | (a << 24); + } + + public static int multiplyColor(int color, float amount) { + int a = color & 0xFF000000; + float r = (color >> 16 & 255) / 255.0F; + float g = (color >> 8 & 255) / 255.0F; + float b = (color & 255) / 255.0F; + + r = Math.min(r*amount, 1.0f); + g = Math.min(g*amount, 1.0f); + b = Math.min(b*amount, 1.0f); + + int ir = (int)(r*255); + int ig = (int)(g*255); + int ib = (int)(b*255); + + return + a | + (ir << 16) | + (ig << 8) | + ib; + } +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java new file mode 100644 index 0000000..e180276 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java @@ -0,0 +1,175 @@ +package io.github.cottonmc.cotton.gui.widget; + +import java.util.List; + +import io.github.cottonmc.cotton.gui.CottonScreenController; +import io.github.cottonmc.cotton.gui.client.ScreenDrawing; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.container.PropertyDelegate; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Identifier; + +public class WBar extends WWidget { + protected final Identifier bg; + protected final Identifier bar; + protected final int field; + protected final int max; + protected int maxValue; + protected PropertyDelegate properties; + protected final Direction direction; + protected String tooltipLabel; + protected Text tooltipTextComponent; + + public WBar(Identifier bg, Identifier bar, int field, int maxfield) { + this(bg, bar, field, maxfield, Direction.UP); + } + + public WBar(Identifier bg, Identifier bar, int field, int maxfield, Direction dir) { + this.bg = bg; + this.bar = bar; + this.field = field; + this.max = maxfield; + this.maxValue = 0; + this.direction = dir; + } + + /** + * Adds a tooltip to the WBar. + * + * Formatting Guide: The tooltip label is passed into String.Format and can recieve two integers + * (%d) - the first is the current value of the bar's focused field, and the second is the + * bar's focused maximum. + * @param label String to render on the tooltip. + * @return WBar with tooltip enabled and set. + */ + public WBar withTooltip(String label) { + this.tooltipLabel = label; + return this; + } + + + + public WBar withTooltip(Text label) { + this.tooltipTextComponent = label; + return this; + } + + @Override + public boolean canResize() { + return true; + } + + @Environment(EnvType.CLIENT) + @Override + public void paintBackground(int x, int y) { + if (bg!=null) { + ScreenDrawing.rect(bg, x, y, getWidth(), getHeight(), 0xFFFFFFFF); + } else { + ScreenDrawing.rect(x, y, getWidth(), getHeight(), ScreenDrawing.colorAtOpacity(0x000000, 0.25f)); + } + + float percent = properties.get(field) / (float) properties.get(max); + if (percent < 0) percent = 0f; + if (percent > 1) percent = 1f; + + int barMax = getWidth(); + if (direction == Direction.DOWN || direction == Direction.UP) barMax = getHeight(); + percent = ((int) (percent * barMax)) / (float) barMax; //Quantize to bar size + + int barSize = (int) (barMax * percent); + if (barSize <= 0) return; + + switch(direction) { //anonymous blocks in this switch statement are to sandbox variables + case UP: { + int left = x; + int top = y + getHeight(); + top -= barSize; + if (bar!=null) { + ScreenDrawing.rect(bar, left, top, getWidth(), barSize, 0, 1 - percent, 1, 1, 0xFFFFFFFF); + } else { + ScreenDrawing.rect(left, top, getWidth(), barSize, ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f)); + } + break; + } + case RIGHT: { + if (bar!=null) { + ScreenDrawing.rect(bar, x, y, barSize, getHeight(), 0, 0, percent, 1, 0xFFFFFFFF); + } else { + ScreenDrawing.rect(x, y, barSize, getHeight(), ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f)); + } + break; + } + case DOWN: { + if (bar!=null) { + ScreenDrawing.rect(bar, x, y, getWidth(), barSize, 0, 0, 1, percent, 0xFFFFFFFF); + } else { + ScreenDrawing.rect(x, y, getWidth(), barSize, ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f)); + } + break; + } + case LEFT: { + int left = x + getWidth(); + int top = y; + left -= barSize; + if (bar!=null) { + ScreenDrawing.rect(bar, left, top, barSize, getHeight(), 1 - percent, 0, 1, 1, 0xFFFFFFFF); + } else { + ScreenDrawing.rect(left, top, barSize, getHeight(), ScreenDrawing.colorAtOpacity(0xFFFFFF, 0.5f)); + } + break; + } + } + } + + @Override + public void addInformation(List<String> information) { + if (tooltipLabel!=null) { + int value = (field>=0) ? properties.get(field) : 0; + int valMax = (max>=0) ? properties.get(max) : maxValue; + String formatted = tooltipLabel; + try { + formatted = new TranslatableText(tooltipLabel, Integer.valueOf(value), Integer.valueOf(valMax)).asFormattedString(); + } catch (Throwable t) { + formatted = t.getLocalizedMessage(); + } //Fallback to raw tooltipLabel + information.add(formatted); + } + if (tooltipTextComponent!=null) { + try { + information.add(tooltipTextComponent.asFormattedString()); + } catch (Throwable t) { + information.add(t.getLocalizedMessage()); + } + } + } + + @Override + public void createPeers(CottonScreenController c) { + if (properties==null) properties = c.getPropertyDelegate(); + } + + /** + * Creates a WBar that has a constant maximum-value instead of getting the maximum from a field. + * @param bg the background image to use for the bar + * @param bar the foreground image that represents the filled bar + * @param properties the PropertyDelegate to pull bar values from + * @param field the field index for bar values + * @param maxValue the constant maximum value for the bar + * @param dir the direction the bar should grow towards + * @return a new WBar with a constant maximum value. + */ + public static WBar withConstantMaximum(Identifier bg, Identifier bar, int field, int maxValue, Direction dir) { + WBar result = new WBar(bg, bar, field, -1, dir); + result.maxValue = maxValue; + return result; + } + + public static enum Direction { + UP, + RIGHT, + DOWN, + LEFT; + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WGridPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WGridPanel.java new file mode 100644 index 0000000..a3fcf94 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WGridPanel.java @@ -0,0 +1,27 @@ +package io.github.cottonmc.cotton.gui.widget; + +public class WGridPanel extends WPanel { + public void add(WWidget w, int x, int y) { + children.add(w); + w.parent = this; + w.setLocation(x * 18, y * 18); + if (w.canResize()) { + w.setSize(18, 18); + } + + expandToFit(w); + //valid = false; + } + + public void add(WWidget w, int x, int y, int width, int height) { + children.add(w); + w.parent = this; + w.setLocation(x * 18, y * 18); + if (w.canResize()) { + w.setSize(width * 18, height * 18); + } + + expandToFit(w); + //valid = false; + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java new file mode 100644 index 0000000..0e6b981 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java @@ -0,0 +1,147 @@ +package io.github.cottonmc.cotton.gui.widget; + +import java.util.List; + +import com.google.common.collect.Lists; + +import io.github.cottonmc.cotton.gui.CottonScreenController; +import io.github.cottonmc.cotton.gui.ValidatedSlot; +import io.github.cottonmc.cotton.gui.client.BackgroundPainter; +import io.github.cottonmc.cotton.gui.client.ScreenDrawing; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.container.Slot; +import net.minecraft.inventory.Inventory; + +public class WItemSlot extends WWidget { + private final List<Slot> peers = Lists.newArrayList(); + private BackgroundPainter backgroundPainter; + private Inventory inventory; + //private CottonScreenController container; + private int startIndex = 0; + private int slotsWide = 1; + private int slotsHigh = 1; + private boolean big = false; + //private boolean ltr = true; + private float opacity = 0.2f; + + public WItemSlot(Inventory inventory, int startIndex, int slotsWide, int slotsHigh, boolean big, boolean ltr) { + this.inventory = inventory; + this.startIndex = startIndex; + this.slotsWide = slotsWide; + this.slotsHigh = slotsHigh; + this.big = big; + //this.ltr = ltr; + } + + private WItemSlot() {} + + public static WItemSlot of(Inventory inventory, int index) { + WItemSlot w = new WItemSlot(); + w.inventory = inventory; + w.startIndex = index; + + return w; + } + + public static WItemSlot of(Inventory inventory, int startIndex, int slotsWide, int slotsHigh) { + WItemSlot w = new WItemSlot(); + w.inventory = inventory; + w.startIndex = startIndex; + w.slotsWide = slotsWide; + w.slotsHigh = slotsHigh; + + return w; + } + + public static WItemSlot outputOf(Inventory inventory, int index) { + WItemSlot w = new WItemSlot(); + w.inventory = inventory; + w.startIndex = index; + w.big = true; + + return w; + } + + public static WItemSlot ofPlayerStorage(Inventory inventory) { + WItemSlot w = new WItemSlot(); + w.inventory = inventory; + w.startIndex = 9; + w.slotsWide = 9; + w.slotsHigh = 3; + //w.ltr = false; + + return w; + } + + @Override + public int getWidth() { + return slotsWide * 18; + } + + @Override + public int getHeight() { + return slotsHigh * 18; + } + + @Override + public void createPeers(CottonScreenController c) { + //this.container = c; + peers.clear(); + int index = startIndex; + + /*if (ltr) { + for (int x = 0; x < slotsWide; x++) { + for (int y = 0; y < slotsHigh; y++) { + ValidatedSlot slot = new ValidatedSlot(inventory, index, this.getAbsoluteX() + (x * 18), this.getAbsoluteY() + (y * 18)); + peers.add(slot); + c.addSlotPeer(slot); + index++; + } + } + } else {*/ + for (int y = 0; y < slotsHigh; y++) { + for (int x = 0; x < slotsWide; x++) { + ValidatedSlot slot = new ValidatedSlot(inventory, index, this.getAbsoluteX() + (x * 18), this.getAbsoluteY() + (y * 18)); + peers.add(slot); + c.addSlotPeer(slot); + index++; + } + } + //} + } + + @Environment(EnvType.CLIENT) + public void setBackgroundPainter(BackgroundPainter painter) { + this.backgroundPainter = painter; + } + + @Environment(EnvType.CLIENT) + @Override + public void paintBackground(int x, int y) { + if (backgroundPainter!=null) { + backgroundPainter.paintBackground(x, y, this); + } else { + for (int xi = 0; xi < slotsWide; xi++) { + for (int yi = 0; yi < slotsHigh; yi++) { + //int lo = ScreenDrawing.colorAtOpacity(0x000000, 0.72f); + //int bg = ScreenDrawing.colorAtOpacity(0x000000, 0.29f); + //int hi = ScreenDrawing.colorAtOpacity(0xFFFFFF, 1.0f); + //if (container!=null) { + int lo = ScreenDrawing.colorAtOpacity(0x000000, opacity); + int bg = ScreenDrawing.colorAtOpacity(0x000000, opacity/2.4f); + int hi = ScreenDrawing.colorAtOpacity(0xFFFFFF, opacity); + //} + + if (big) { + ScreenDrawing.drawBeveledPanel((xi * 18) + x - 4, (yi * 18) + y - 4, 24, 24, + lo, bg, hi); + } else { + ScreenDrawing.drawBeveledPanel((xi * 18) + x - 1, (yi * 18) + y - 1, 18, 18, + lo, bg, hi); + } + } + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WLabel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WLabel.java new file mode 100644 index 0000000..fb034b8 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WLabel.java @@ -0,0 +1,45 @@ +package io.github.cottonmc.cotton.gui.widget; + +import io.github.cottonmc.cotton.gui.client.ScreenDrawing; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +public class WLabel extends WWidget { + protected final Text text; + protected final int color; + + public static final int DEFAULT_TEXT_COLOR = 0x404040; + + public WLabel(String text, int color) { + this(new LiteralText(text), color); + } + + public WLabel(Text text, int color) { + this.text = text; + this.color = color; + } + + public WLabel(String text) { + this(text, DEFAULT_TEXT_COLOR); + } + + @Override + public void paintBackground(int x, int y) { + String translated = text.asFormattedString(); + ScreenDrawing.drawString(translated, x, y, color); + } + + @Override + public boolean canResize() { + return false; + } + + @Override + public int getWidth() { + return 8; //We don't actually clip to our boundaries so return a dummy value. + } + + @Override + public int getHeight() { + return 8; + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java new file mode 100644 index 0000000..1fa0510 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java @@ -0,0 +1,141 @@ +package io.github.cottonmc.cotton.gui.widget; + +import java.util.List; + +import com.google.common.collect.Lists; + +import io.github.cottonmc.cotton.gui.CottonScreenController; +import io.github.cottonmc.cotton.gui.client.BackgroundPainter; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class WPanel extends WWidget { + protected final List<WWidget> children = Lists.newArrayList(); + @Environment(EnvType.CLIENT) + private BackgroundPainter backgroundPainter = null; + + @Override + public void createPeers(CottonScreenController c) { + //System.out.println("Creating peers - before: "+c.slotList.size()); + for(WWidget child : children) { + child.createPeers(c); + } + //System.out.println("Peers created - after: "+c.slotList.size()); + } + + public void remove(WWidget w) { + children.remove(w); + } + + @Override + public boolean canResize() { + return true; + } + + @Environment(EnvType.CLIENT) + public WPanel setBackgroundPainter(BackgroundPainter painter) { + this.backgroundPainter = painter; + return this; + } + + /** + * Uses this Panel's layout rules to reposition and resize components to fit nicely in the panel. + */ + public void layout() { + for(WWidget child : children) { + if (child instanceof WPanel) ((WPanel) child).layout(); + expandToFit(child); + } + } + + protected void expandToFit(WWidget w) { + int pushRight = w.getX()+w.getWidth(); + int pushDown = w.getY()+w.getHeight(); + this.setSize(Math.max(this.getWidth(), pushRight), Math.max(this.getHeight(), pushDown)); + } + + @Override + public WWidget onMouseUp(int x, int y, int button) { + if (children.isEmpty()) return super.onMouseUp(x, y, button); + for(int i=children.size()-1; i>=0; i--) { //Backwards so topmost widgets get priority + WWidget child = children.get(i); + if ( x>=child.getX() && + y>=child.getY() && + x<child.getX()+child.getWidth() && + y<child.getY()+child.getHeight()) { + return child.onMouseUp(x-child.getX(), y-child.getY(), button); + } + } + return super.onMouseUp(x, y, button); + } + + @Override + public WWidget onMouseDown(int x, int y, int button) { + if (children.isEmpty()) return super.onMouseDown(x, y, button); + for(int i=children.size()-1; i>=0; i--) { //Backwards so topmost widgets get priority + WWidget child = children.get(i); + if ( x>=child.getX() && + y>=child.getY() && + x<child.getX()+child.getWidth() && + y<child.getY()+child.getHeight()) { + return child.onMouseDown(x-child.getX(), y-child.getY(), button); + } + } + return super.onMouseDown(x, y, button); + } + + @Override + public void onMouseDrag(int x, int y, int button) { + if (children.isEmpty()) return; + for(int i=children.size()-1; i>=0; i--) { //Backwards so topmost widgets get priority + WWidget child = children.get(i); + if ( x>=child.getX() && + y>=child.getY() && + x<child.getX()+child.getWidth() && + y<child.getY()+child.getHeight()) { + child.onMouseDrag(x-child.getX(), y-child.getY(), button); + return; //Only send the message to the first valid recipient + } + } + super.onMouseDrag(x, y, button); + } + + @Override + public void onClick(int x, int y, int button) { + if (children.isEmpty()) return; + for(int i=children.size()-1; i>=0; i--) { //Backwards so topmost widgets get priority + WWidget child = children.get(i); + if ( x>=child.getX() && + y>=child.getY() && + x<child.getX()+child.getWidth() && + y<child.getY()+child.getHeight()) { + child.onClick(x-child.getX(), y-child.getY(), button); + return; //Only send the message to the first valid recipient + } + } + } + + @Override + public void validate(CottonScreenController c) { + layout(); + createPeers(c); + } + + @Environment(EnvType.CLIENT) + @Override + public void paintBackground(int x, int y) { + if (backgroundPainter!=null) backgroundPainter.paintBackground(x, y, this); + + for(WWidget child : children) { + child.paintBackground(x + child.getX(), y + child.getY()); + } + } + + @Environment(EnvType.CLIENT) + @Override + public void paintForeground(int x, int y, int mouseX, int mouseY) { + for(WWidget child : children) { + child.paintForeground(x + child.getX(), y + child.getY(), mouseX, mouseY); + } + } +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WPlainPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WPlainPanel.java new file mode 100644 index 0000000..310ae69 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WPlainPanel.java @@ -0,0 +1,27 @@ +package io.github.cottonmc.cotton.gui.widget; + +public class WPlainPanel extends WPanel { + public void add(WWidget w, int x, int y) { + children.add(w); + w.parent = this; + w.setLocation(x, y); + if (w.canResize()) { + w.setSize(18, 18); + } + + expandToFit(w); + //valid = false; + } + + public void add(WWidget w, int x, int y, int width, int height) { + children.add(w); + w.parent = this; + w.setLocation(x, y); + if (w.canResize()) { + w.setSize(width, height); + } + + expandToFit(w); + //valid = false; + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java new file mode 100644 index 0000000..f1eb1d6 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java @@ -0,0 +1,210 @@ +package io.github.cottonmc.cotton.gui.widget; + +import java.util.ArrayList; +import java.util.List; + +import io.github.cottonmc.cotton.gui.CottonScreenController; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; + +public class WWidget { + protected WPanel parent; + private int x = 0; + private int y = 0; + private int width = 18; + private int height = 18; + //private boolean renderTooltip; + + public void setLocation(int x, int y) { + this.x = x; + this.y = y; + } + + public void setSize(int x, int y) { + this.width = x; + this.height = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getAbsoluteX() { + if (parent==null) { + return getX(); + } else { + return getX() + parent.getAbsoluteX(); + } + } + + public int getAbsoluteY() { + if (parent==null) { + return getY(); + } else { + return getY() + parent.getAbsoluteY(); + } + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public boolean canResize() { + return false; + } + + //public void setParent(WPanel parent) { + // this.parent = parent; + //} + + //public boolean getRenderTooltip() { + // return renderTooltip; + //} + + //public void setRenderTooltip(boolean renderTooltip) { + // this.renderTooltip = renderTooltip; + //} + + /** + * Draw this Widget at the specified coordinates. The coordinates provided are the top-level device coordinates of + * this widget's topleft corner, so don't translate by the widget X/Y! That's already been done. Your "valid" + * drawing space is from (x, y) to (x + width - 1, y + height - 1) inclusive. However, no scissor or depth masking + * is done, so please take care to respect your boundaries. + * @param x The X coordinate of the leftmost pixels of this widget in device (opengl) coordinates + * @param y The Y coordinate of the topmost pixels of this widget in device (opengl) coordinates + */ + public void paint(int x, int y) { + + } + + /** + * Notifies this widget that the mouse has been pressed while inside its bounds + * @param x The X coordinate of the event, in widget-space (0 is the left edge of this widget) + * @param y The Y coordinate of the event, in widget-space (0 is the top edge of this widget) + * @param button The mouse button that was used. Button numbering is consistent with LWJGL Mouse (0=left, 1=right, 2=mousewheel click) + */ + public WWidget onMouseDown(int x, int y, int button) { + return this; + } + + /** + * Notifies this widget that the mouse has been moved while pressed and inside its bounds + * @param x The X coordinate of the event, in widget-space (0 is the left edge of this widget) + * @param y The Y coordinate of the event, in widget-space (0 is the top edge of this widget) + * @param button The mouse button that was used. Button numbering is consistent with LWJGL Mouse (0=left, 1=right, 2=mousewheel click) + */ + public void onMouseDrag(int x, int y, int button) { + } + + /** + * Notifies this widget that the mouse has been released while inside its bounds + * @param x The X coordinate of the event, in widget-space (0 is the left edge of this widget) + * @param y The Y coordinate of the event, in widget-space (0 is the top edge of this widget) + * @param button The mouse button that was used. Button numbering is consistent with LWJGL Mouse (0=left, 1=right, 2=mousewheel click) + */ + public WWidget onMouseUp(int x, int y, int button) { + return this; + } + + /** + * Notifies this widget that the mouse has been pressed and released, both while inside its bounds. + * @param x The X coordinate of the event, in widget-space (0 is the left edge of this widget) + * @param y The Y coordinate of the event, in widget-space (0 is the top edge of this widget) + * @param button The mouse button that was used. Button numbering is consistent with LWJGL Mouse (0=left, 1=right, 2=mousewheel click) + */ + public void onClick(int x, int y, int button) { + } + + /** + * Creates "heavyweight" component peers + * @param c the top-level Container that will hold the peers + */ + public void createPeers(CottonScreenController c) { + } + + @Environment(EnvType.CLIENT) + public void paintBackground(int x, int y) { + } + + @Environment(EnvType.CLIENT) + public void paintForeground(int x, int y, int mouseX, int mouseY) { + if (mouseX >= x && mouseX < x+getWidth() && mouseY >= y && mouseY < y+getHeight()) { + renderTooltip(mouseX-x+getX(),mouseY-y+getY() ); + } + } + + /** + * Internal method to conditionally render tooltip data. This requires an overriden {@link #addInformation(List) + * addInformation} method to insert data into the tooltip - without this, the method returns early, because no work + * is needing to be done on an empty list. + * @param tX The adjusted X coordinate at which to render the tooltip. + * @param tY The adjusted X coordinate at which to render the tooltip. + */ + @Environment(EnvType.CLIENT) + protected void renderTooltip(int tX, int tY) { + List<String> info = new ArrayList<>(); + addInformation(info); + + if (info.size() == 0) + return; + + Screen screen = MinecraftClient.getInstance().currentScreen; + screen.renderTooltip(info, tX, tY); + /* + MinecraftClient mc = MinecraftClient.getInstance(); + int width = mc.window.getScaledWidth(); + int height = mc.window.getScaledHeight(); + //TODO: Hook into or copy Screen::drawStackTooltip or Screen::drawTooltip + TextRenderer renderer = mc.getFontManager().getTextRenderer(MinecraftClient.DEFAULT_TEXT_RENDERER_ID); + //Get width of the panel + int maxWidth = 0; + for(String s : info) { + maxWidth = Math.max(maxWidth, renderer.getStringWidth(s)); + } + //TODO: Draw background panel + + //Draw strings + for(int i=0; i<info.size(); i++) { + //renderer.draw(info, tX, tY, renderer.getStringWidth(str)) + //GuiUtils.drawHoveringText(info, tX, tY, width, height, -1, mc.fontRenderer); + }*/ + } + + //public boolean isValid() { + // return valid; + //} + + /** + * Creates component peers, lays out children, and initializes animation data for this Widget and all its children. + * The host container must clear any heavyweight peers from its records before this method is called. + */ + public void validate(CottonScreenController host) { + //valid = true; + } + + /** + * Marks this Widget as having dirty state; component peers may need to be recreated, children adapted to a new size, + * and animation data reset. + */ + //public void invalidate() { + // valid = false; + //} + + /** + * Adds information to this widget's tooltip. This requires a call to {@link #setRenderTooltip(boolean) + * setRenderTooltip} (obviously passing in {@code true}), in order to enable the rendering of your tooltip. + * @param information List containing all previous tooltip data. + */ + public void addInformation(List<String> information) { +} +} diff --git a/src/main/java/net/fabricmc/example/ExampleMod.java b/src/main/java/net/fabricmc/example/ExampleMod.java deleted file mode 100644 index e5ed082..0000000 --- a/src/main/java/net/fabricmc/example/ExampleMod.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.fabricmc.example; - -import net.fabricmc.api.ModInitializer; - -public class ExampleMod implements ModInitializer { - @Override - public void onInitialize() { - // This code runs as soon as Minecraft is in a mod-load-ready state. - // However, some things (like resources) may still be uninitialized. - // Proceed with mild caution. - - System.out.println("Hello Fabric world!"); - } -} diff --git a/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java b/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java deleted file mode 100644 index 0fc8f68..0000000 --- a/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.fabricmc.example.mixin; - -import net.minecraft.client.MinecraftClient; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MinecraftClient.class) -public class ExampleMixin { - @Inject(at = @At("HEAD"), method = "init()V") - private void init(CallbackInfo info) { - System.out.println("This line is printed by an example mod mixin!"); - } -} diff --git a/src/main/resources/assets/modid/icon.png b/src/main/resources/assets/libgui/icon.png Binary files differindex 047b91f..047b91f 100644 --- a/src/main/resources/assets/modid/icon.png +++ b/src/main/resources/assets/libgui/icon.png diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index f37c644..d803a84 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,31 +1,21 @@ { "schemaVersion": 1, - "id": "modid", + "id": "libgui", "version": "${version}", - "name": "Example Mod", - "description": "This is an example description! Tell everyone what your mod is about!", + "name": "LibGui", + "description": "Easy grid-based GUIs for Fabric", "authors": [ - "Me!" + "Falkreon" ], "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" + "sources": "https://github.com/CottonMC/LibGUI" }, - "license": "CC0-1.0", - "icon": "assets/modid/icon.png", + "license": "MIT", + "icon": "assets/libgui/icon.png", "environment": "*", - "entrypoints": { - "main": [ - "net.fabricmc.example.ExampleMod" - ] - }, - "mixins": [ - "modid.mixins.json" - ], - "depends": { "fabricloader": ">=0.4.0", "fabric": "*" diff --git a/src/main/resources/modid.mixins.json b/src/main/resources/modid.mixins.json deleted file mode 100644 index e243f0a..0000000 --- a/src/main/resources/modid.mixins.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "required": true, - "package": "net.fabricmc.example.mixin", - "compatibilityLevel": "JAVA_8", - "mixins": [ - ], - "client": [ - "ExampleMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} |