aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/CottonScreenController.java397
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/EmptyInventory.java53
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/PropertyDelegateHolder.java7
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java46
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/BackgroundPainter.java37
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonScreen.java237
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/ScreenDrawing.java235
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WBar.java175
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WGridPanel.java27
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java147
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WLabel.java45
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java141
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WPlainPanel.java27
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java210
-rw-r--r--src/main/java/net/fabricmc/example/ExampleMod.java14
-rw-r--r--src/main/java/net/fabricmc/example/mixin/ExampleMixin.java15
16 files changed, 1784 insertions, 29 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!");
- }
-}