aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/common/gui/modularui/widget
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/common/gui/modularui/widget')
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/AESlotWidget.java40
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverCycleButtonWidget.java89
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataControllerWidget.java138
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_CycleButtonWidget.java38
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_SlotWidget.java102
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_TextFieldWidget.java110
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_ToggleButtonWidget.java84
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java162
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/FluidDisplaySlotWidget.java474
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/ItemWatcherSlotWidget.java47
10 files changed, 1284 insertions, 0 deletions
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/AESlotWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/AESlotWidget.java
new file mode 100644
index 0000000000..f3620d3234
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/AESlotWidget.java
@@ -0,0 +1,40 @@
+package gregtech.common.gui.modularui.widget;
+
+import appeng.client.render.AppEngRenderItem;
+import appeng.core.AELog;
+import appeng.util.Platform;
+import com.gtnewhorizons.modularui.common.internal.wrapper.BaseSlot;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.inventory.Slot;
+
+public class AESlotWidget extends SlotWidget {
+
+ public AESlotWidget(BaseSlot slot) {
+ super(slot);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ protected void drawSlot(Slot slotIn) {
+ final AppEngRenderItem aeRenderItem = new AppEngRenderItem();
+ final RenderItem pIR = this.setItemRender(aeRenderItem);
+ try {
+ aeRenderItem.setAeStack(Platform.getAEStackInSlot(slotIn));
+ super.drawSlot(slotIn, false);
+ } catch (final Exception err) {
+ AELog.warn("[AppEng] AE prevented crash while drawing slot: " + err);
+ }
+ this.setItemRender(pIR);
+ }
+
+ @SideOnly(Side.CLIENT)
+ private RenderItem setItemRender(final RenderItem item) {
+ final RenderItem ri = ModularGui.getItemRenderer();
+ ModularGui.setItemRenderer(item);
+ return ri;
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/CoverCycleButtonWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverCycleButtonWidget.java
new file mode 100644
index 0000000000..599ed28a5f
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverCycleButtonWidget.java
@@ -0,0 +1,89 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.common.widget.CycleButtonWidget;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.gui.modularui.GT_UITextures;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * Fires click action on mouse release, not on press.
+ * Draws different backgrounds depending on whether the mouse is being pressed or the widget is hovered.
+ */
+public class CoverCycleButtonWidget extends CycleButtonWidget {
+
+ private static final UITexture BUTTON_NORMAL_NOT_PRESSED =
+ GT_UITextures.BUTTON_COVER_NORMAL.getSubArea(0, 0, 1, 0.5f);
+ private static final UITexture BUTTON_NORMAL_PRESSED = GT_UITextures.BUTTON_COVER_NORMAL.getSubArea(0, 0.5f, 1, 1);
+ private static final UITexture BUTTON_HOVERED_NOT_PRESSED =
+ GT_UITextures.BUTTON_COVER_NORMAL_HOVERED.getSubArea(0, 0, 1, 0.5f);
+ private static final UITexture BUTTON_HOVERED_PRESSED =
+ GT_UITextures.BUTTON_COVER_NORMAL_HOVERED.getSubArea(0, 0.5f, 1, 1);
+
+ private boolean clickPressed;
+
+ private static final int TOOLTIP_DELAY = 5;
+
+ public CoverCycleButtonWidget() {
+ setSize(16, 16);
+ setTooltipShowUpDelay(TOOLTIP_DELAY);
+ }
+
+ @Override
+ public ClickResult onClick(int buttonId, boolean doubleClick) {
+ updateState();
+ if (!canClick()) return ClickResult.REJECT;
+ clickPressed = true;
+ return ClickResult.SUCCESS;
+ }
+
+ @Override
+ public boolean onClickReleased(int buttonId) {
+ clickPressed = false;
+ updateState();
+ if (!isHovering() || !canClick()) return false;
+ return onClickImpl(buttonId);
+ }
+
+ protected boolean onClickImpl(int buttonId) {
+ super.onClick(buttonId, false);
+ return true;
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ protected boolean canClick() {
+ return true;
+ }
+
+ @SideOnly(Side.CLIENT)
+ protected void updateState() {}
+
+ public boolean isClickPressed() {
+ return clickPressed;
+ }
+
+ @Override
+ public void drawBackground(float partialTicks) {
+ GL11.glColor4f(1, 1, 1, 1);
+ super.drawBackground(partialTicks);
+ }
+
+ @Override
+ public IDrawable[] getBackground() {
+ if (isHovering()) {
+ if (clickPressed) {
+ return new IDrawable[] {BUTTON_HOVERED_PRESSED};
+ } else {
+ return new IDrawable[] {BUTTON_HOVERED_NOT_PRESSED};
+ }
+ } else {
+ if (clickPressed) {
+ return new IDrawable[] {BUTTON_NORMAL_PRESSED};
+ } else {
+ return new IDrawable[] {BUTTON_NORMAL_NOT_PRESSED};
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/CoverDataControllerWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataControllerWidget.java
new file mode 100644
index 0000000000..d28117054a
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataControllerWidget.java
@@ -0,0 +1,138 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.GT_CoverBehaviorBase;
+import gregtech.api.util.ISerializableObject;
+import java.io.IOException;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import net.minecraft.network.PacketBuffer;
+
+public class CoverDataControllerWidget<T extends ISerializableObject> extends DataControllerWidget<T> {
+
+ protected final GT_CoverBehaviorBase<T> coverBehavior;
+
+ /**
+ * @param dataGetter () -> cover data this widget handles
+ * @param dataSetter data to set -> if setting cover data is successful
+ * @param coverBehavior cover this widget handles data update
+ */
+ public CoverDataControllerWidget(
+ Supplier<T> dataGetter, Function<T, Boolean> dataSetter, GT_CoverBehaviorBase<T> coverBehavior) {
+ super(dataGetter, dataSetter);
+ this.coverBehavior = coverBehavior;
+ }
+
+ @Override
+ public <U, W extends Widget & IDataFollowerWidget<T, U>> CoverDataControllerWidget<T> addFollower(
+ W widget, Function<T, U> dataToStateGetter, BiFunction<T, U, T> dataUpdater, Consumer<W> applyForWidget) {
+ super.addFollower(widget, dataToStateGetter, dataUpdater, applyForWidget);
+ return this;
+ }
+
+ @Override
+ protected void writeToPacket(PacketBuffer buffer, T data) {
+ try {
+ NetworkUtils.writeNBTBase(buffer, data.saveDataToNBT());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected T readFromPacket(PacketBuffer buffer) throws IOException {
+ return coverBehavior.createDataObject(NetworkUtils.readNBTBase(buffer));
+ }
+
+ /**
+ * Uses int index to determine toggle button behaviors.
+ */
+ public static class CoverDataIndexedControllerWidget_ToggleButtons<T extends ISerializableObject>
+ extends CoverDataControllerWidget<T> {
+
+ private final BiFunction<Integer, T, Boolean> dataToStateGetter;
+ private final BiFunction<Integer, T, T> dataUpdater;
+
+ /**
+ * @param coverDataGetter () -> cover data this widget handles
+ * @param coverDataSetter data to set -> if setting cover data is successful
+ * @param coverBehavior cover this widget handles data update
+ * @param dataToStateGetter (index of button, given cover data) -> button state
+ * @param dataUpdater (index of button, current cover data) -> new cover data
+ */
+ public CoverDataIndexedControllerWidget_ToggleButtons(
+ Supplier<T> coverDataGetter,
+ Function<T, Boolean> coverDataSetter,
+ GT_CoverBehaviorBase<T> coverBehavior,
+ BiFunction<Integer, T, Boolean> dataToStateGetter,
+ BiFunction<Integer, T, T> dataUpdater) {
+ super(coverDataGetter, coverDataSetter, coverBehavior);
+ this.dataToStateGetter = dataToStateGetter;
+ this.dataUpdater = dataUpdater;
+ }
+
+ /**
+ * @param index index of widget to add
+ * @param widget widget to add
+ * @param applyForWidget methods to call for widget to add
+ */
+ public <W extends CoverDataFollower_ToggleButtonWidget<T>>
+ CoverDataIndexedControllerWidget_ToggleButtons<T> addToggleButton(
+ int index, W widget, Consumer<CoverDataFollower_ToggleButtonWidget<T>> applyForWidget) {
+ addFollower(
+ widget,
+ data -> dataToStateGetter.apply(index, data),
+ (data, state) -> dataUpdater.apply(index, data),
+ applyForWidget);
+ return this;
+ }
+ }
+
+ /**
+ * Uses int index to determine cycle button behaviors.
+ */
+ public static class CoverDataIndexedControllerWidget_CycleButtons<T extends ISerializableObject>
+ extends CoverDataControllerWidget<T> {
+
+ private final BiFunction<Integer, T, Integer> dataToStateGetter;
+ private final BiFunction<Integer, T, T> dataUpdater;
+
+ /**
+ * @param coverDataGetter () -> cover data this widget handles
+ * @param coverDataSetter data to set -> if setting cover data is successful
+ * @param coverBehavior cover this widget handles data update
+ * @param dataToStateGetter (index of button, given cover data) -> button state
+ * @param dataUpdater (index of button, current cover data) -> new cover data
+ */
+ public CoverDataIndexedControllerWidget_CycleButtons(
+ Supplier<T> coverDataGetter,
+ Function<T, Boolean> coverDataSetter,
+ GT_CoverBehaviorBase<T> coverBehavior,
+ BiFunction<Integer, T, Integer> dataToStateGetter,
+ BiFunction<Integer, T, T> dataUpdater) {
+ super(coverDataGetter, coverDataSetter, coverBehavior);
+ this.dataToStateGetter = dataToStateGetter;
+ this.dataUpdater = dataUpdater;
+ }
+
+ /**
+ * @param index index of widget to add
+ * @param widget widget to add
+ * @param applyForWidget methods to call for the widget to add
+ */
+ public <W extends CoverDataFollower_CycleButtonWidget<T>>
+ CoverDataIndexedControllerWidget_CycleButtons<T> addCycleButton(
+ int index, W widget, Consumer<CoverDataFollower_CycleButtonWidget<T>> applyForWidget) {
+ addFollower(
+ widget,
+ data -> dataToStateGetter.apply(index, data),
+ (data, state) -> dataUpdater.apply(index, data),
+ applyForWidget);
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_CycleButtonWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_CycleButtonWidget.java
new file mode 100644
index 0000000000..d07165cc6e
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_CycleButtonWidget.java
@@ -0,0 +1,38 @@
+package gregtech.common.gui.modularui.widget;
+
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Determines button state with cover data.
+ */
+public class CoverDataFollower_CycleButtonWidget<T extends ISerializableObject> extends CoverCycleButtonWidget
+ implements IDataFollowerWidget<T, Integer> {
+
+ private Function<T, Integer> dataToStateGetter;
+
+ public CoverDataFollower_CycleButtonWidget() {
+ super();
+ setGetter(() -> 0); // fake getter; used only for init
+ setSynced(false, false);
+ }
+
+ @Override
+ public CoverDataFollower_CycleButtonWidget<T> setDataToStateGetter(Function<T, Integer> dataToStateGetter) {
+ this.dataToStateGetter = dataToStateGetter;
+ return this;
+ }
+
+ @Override
+ public CoverDataFollower_CycleButtonWidget<T> setStateSetter(Consumer<Integer> setter) {
+ super.setSetter(setter);
+ return this;
+ }
+
+ @Override
+ public void updateState(T data) {
+ setState(dataToStateGetter.apply(data), false, false);
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_SlotWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_SlotWidget.java
new file mode 100644
index 0000000000..c09c5b5279
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_SlotWidget.java
@@ -0,0 +1,102 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+import com.gtnewhorizons.modularui.api.widget.Interactable;
+import com.gtnewhorizons.modularui.common.internal.wrapper.BaseSlot;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+import java.io.IOException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import net.minecraft.item.ItemStack;
+
+public class CoverDataFollower_SlotWidget<T extends ISerializableObject> extends SlotWidget
+ implements IDataFollowerWidget<T, ItemStack> {
+
+ private Function<T, ItemStack> dataToStateGetter;
+ private Consumer<ItemStack> dataSetter;
+
+ public CoverDataFollower_SlotWidget(BaseSlot slot) {
+ super(slot);
+ }
+
+ public CoverDataFollower_SlotWidget(IItemHandlerModifiable handler, int index, boolean phantom) {
+ this(new BaseSlot(handler, index, phantom));
+ }
+
+ public CoverDataFollower_SlotWidget(IItemHandlerModifiable handler, int index) {
+ this(handler, index, false);
+ }
+
+ @Override
+ public CoverDataFollower_SlotWidget<T> setDataToStateGetter(Function<T, ItemStack> dataToStateGetter) {
+ this.dataToStateGetter = dataToStateGetter;
+ return this;
+ }
+
+ @Override
+ public CoverDataFollower_SlotWidget<T> setStateSetter(Consumer<ItemStack> setter) {
+ this.dataSetter = setter;
+ return this;
+ }
+
+ @Override
+ public void updateState(T data) {
+ getMcSlot().putStack(dataToStateGetter.apply(data));
+ }
+
+ @Override
+ public void detectAndSendChanges(boolean init) {}
+
+ // Slot sync is handled differently from other DataFollowers,
+ // so we need to also sync slot content directly to server.
+
+ @Override
+ public ClickResult onClick(int buttonId, boolean doubleClick) {
+ if (interactionDisabled) return ClickResult.REJECT;
+ if (isPhantom()) {
+ ClickData clickData = ClickData.create(buttonId, doubleClick);
+ syncToServer(2, clickData::writeToPacket);
+ phantomClick(clickData);
+ dataSetter.accept(getMcSlot().getStack());
+ return ClickResult.ACCEPT;
+ }
+ return ClickResult.REJECT;
+ }
+
+ @Override
+ public boolean onMouseScroll(int direction) {
+ if (interactionDisabled) return false;
+ if (isPhantom()) {
+ if (Interactable.hasShiftDown()) {
+ direction *= 8;
+ }
+ final int finalDirection = direction;
+ syncToServer(3, buffer -> buffer.writeVarIntToBuffer(finalDirection));
+ phantomScroll(finalDirection);
+ dataSetter.accept(getMcSlot().getStack());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean handleDragAndDrop(ItemStack draggedStack, int button) {
+ if (interactionDisabled) return false;
+ if (!isPhantom()) return false;
+ ClickData clickData = ClickData.create(button, false);
+ syncToServer(5, buffer -> {
+ try {
+ clickData.writeToPacket(buffer);
+ buffer.writeItemStackToBuffer(draggedStack);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ phantomClick(clickData, draggedStack);
+ dataSetter.accept(getMcSlot().getStack());
+ draggedStack.stackSize = 0;
+ return true;
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_TextFieldWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_TextFieldWidget.java
new file mode 100644
index 0000000000..9130f8e3d0
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_TextFieldWidget.java
@@ -0,0 +1,110 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.api.math.MathExpression;
+import com.gtnewhorizons.modularui.common.widget.textfield.TextFieldWidget;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import net.minecraft.client.gui.GuiScreen;
+
+public class CoverDataFollower_TextFieldWidget<T extends ISerializableObject> extends TextFieldWidget
+ implements IDataFollowerWidget<T, String> {
+
+ private Function<T, String> dataToStateGetter;
+
+ public CoverDataFollower_TextFieldWidget() {
+ super();
+ setGetter(() -> ""); // fake getter; used only for init
+ setSynced(false, false);
+ setTextColor(Color.WHITE.dark(1));
+ setTextAlignment(Alignment.CenterLeft);
+ setBackground(GT_UITextures.BACKGROUND_TEXT_FIELD.withOffset(-1, -1, 2, 2));
+ }
+
+ @Override
+ public void onPostInit() {
+ // Widget#onPostInit is called earlier than IDataFollowerWidget#onPostInit,
+ // so we make sure cursor is set after text is set
+ super.onPostInit();
+
+ // On first call #handler does not contain text.
+ // On second call, it contains correct text to update #lastText,
+ // but #shouldGetFocus call is skipped by Cursor#updateFocused,
+ // so we need to manually call this.
+ if (focusOnGuiOpen) {
+ shouldGetFocus();
+ }
+ }
+
+ @Override
+ public CoverDataFollower_TextFieldWidget<T> setDataToStateGetter(Function<T, String> dataToStateGetter) {
+ this.dataToStateGetter = dataToStateGetter;
+ return this;
+ }
+
+ @Override
+ public CoverDataFollower_TextFieldWidget<T> setStateSetter(Consumer<String> setter) {
+ super.setSetter(setter);
+ return this;
+ }
+
+ @Override
+ public void updateState(T data) {
+ setText(dataToStateGetter.apply(data));
+ }
+
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollNumbers(int baseStep, int ctrlStep, int shiftStep) {
+ setOnScrollNumbers((val, direction) -> {
+ int step = (GuiScreen.isShiftKeyDown() ? shiftStep : GuiScreen.isCtrlKeyDown() ? ctrlStep : baseStep)
+ * direction;
+ try {
+ val = Math.addExact(val, step);
+ } catch (ArithmeticException ignored) {
+ val = Integer.MAX_VALUE;
+ }
+ return val;
+ });
+ return this;
+ }
+
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollNumbers() {
+ return setOnScrollNumbers(1, 50, 1000);
+ }
+
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollText(int baseStep, int ctrlStep, int shiftStep) {
+ setOnScroll((text, direction) -> {
+ int val = (int) MathExpression.parseMathExpression(text, -1);
+ int step = (GuiScreen.isShiftKeyDown() ? shiftStep : GuiScreen.isCtrlKeyDown() ? ctrlStep : baseStep)
+ * direction;
+ try {
+ val = Math.addExact(val, step);
+ } catch (ArithmeticException ignored) {
+ val = Integer.MAX_VALUE;
+ }
+ return TextFieldWidget.format.format(val);
+ });
+ return this;
+ }
+
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollText() {
+ return setOnScrollText(1, 5, 50);
+ }
+
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollNumbersLong(long baseStep, long ctrlStep, long shiftStep) {
+ setOnScrollNumbersLong((val, direction) -> {
+ long step = (GuiScreen.isShiftKeyDown() ? shiftStep : GuiScreen.isCtrlKeyDown() ? ctrlStep : baseStep)
+ * direction;
+ try {
+ val = Math.addExact(val, step);
+ } catch (ArithmeticException ignored) {
+ val = Long.MAX_VALUE;
+ }
+ return val;
+ });
+ return this;
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_ToggleButtonWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_ToggleButtonWidget.java
new file mode 100644
index 0000000000..8e091d7bc6
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_ToggleButtonWidget.java
@@ -0,0 +1,84 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class CoverDataFollower_ToggleButtonWidget<T extends ISerializableObject> extends CoverCycleButtonWidget
+ implements IDataFollowerWidget<T, Boolean> {
+
+ private Function<T, Boolean> dataToStateGetter;
+
+ public CoverDataFollower_ToggleButtonWidget() {
+ super();
+ setGetter(() -> 0); // fake getter; used only for init
+ setSynced(false, false);
+ setLength(2);
+ }
+
+ @Override
+ public CoverDataFollower_ToggleButtonWidget<T> setDataToStateGetter(Function<T, Boolean> dataToStateGetter) {
+ this.dataToStateGetter = dataToStateGetter;
+ return this;
+ }
+
+ @Override
+ public CoverDataFollower_ToggleButtonWidget<T> setStateSetter(Consumer<Boolean> setter) {
+ super.setSetter(val -> setter.accept(val == 1));
+ return this;
+ }
+
+ @Override
+ public void updateState(T data) {
+ setState(dataToStateGetter.apply(data) ? 1 : 0, false, false);
+ }
+
+ public CoverDataFollower_ToggleButtonWidget<T> setToggleTexture(IDrawable active, IDrawable inactive) {
+ setTextureGetter(state -> state == 1 ? active : inactive);
+ return this;
+ }
+
+ public static <T extends ISerializableObject> CoverDataFollower_ToggleButtonWidget<T> ofCheckAndCross() {
+ return new CoverDataFollower_ToggleButtonWidget<T>()
+ .setToggleTexture(GT_UITextures.OVERLAY_BUTTON_CHECKMARK, GT_UITextures.OVERLAY_BUTTON_CROSS);
+ }
+
+ public static <T extends ISerializableObject> CoverDataFollower_ToggleButtonWidget<T> ofCheck() {
+ return new CoverDataFollower_ToggleButtonWidget<T>()
+ .setToggleTexture(GT_UITextures.OVERLAY_BUTTON_CHECKMARK, GT_UITextures.TRANSPARENT);
+ }
+
+ public static <T extends ISerializableObject> CoverDataFollower_ToggleButtonWidget<T> ofRedstone() {
+ return new CoverDataFollower_ToggleButtonWidget<T>()
+ .setToggleTexture(GT_UITextures.OVERLAY_BUTTON_REDSTONE_ON, GT_UITextures.OVERLAY_BUTTON_REDSTONE_OFF);
+ }
+
+ public static <T extends ISerializableObject> CoverDataFollower_ToggleButtonWidget<T> ofDisableable() {
+ return new CoverDataFollower_DisableableToggleButtonWidget<>();
+ }
+
+ /**
+ * Disables clicking if button is already pressed.
+ */
+ public static class CoverDataFollower_DisableableToggleButtonWidget<T extends ISerializableObject>
+ extends CoverDataFollower_ToggleButtonWidget<T> {
+
+ public CoverDataFollower_DisableableToggleButtonWidget() {
+ super();
+ }
+
+ @Override
+ protected boolean canClick() {
+ return getState() == 0;
+ }
+
+ @Override
+ public IDrawable[] getBackground() {
+ if (!canClick()) return new IDrawable[] {GT_UITextures.BUTTON_COVER_NORMAL_DISABLED};
+ return super.getBackground();
+ }
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java
new file mode 100644
index 0000000000..f29b8eeaf9
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java
@@ -0,0 +1,162 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.widget.ISyncedWidget;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+import com.gtnewhorizons.modularui.common.widget.MultiChildWidget;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+import java.io.IOException;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import net.minecraft.network.PacketBuffer;
+
+/**
+ * Controls state of child widgets with specific data, and allows centralized control of multiple widgets.
+ * e.g. clicking button B will set machine mode to B, so button A, whose state is bound to the mode,
+ * will be automatically deactivated by this widget.
+ * <br> This widget wraps data and handles validation, e.g. tell client to close GUI when tile is broken or cover is removed.
+ * <br> Data can be anything, e.g. {@link ISerializableObject} or machine recipe mode.
+ * @param <T> Data type stored in this widget
+ * @see IDataFollowerWidget
+ */
+public abstract class DataControllerWidget<T> extends MultiChildWidget implements ISyncedWidget {
+
+ private final Supplier<T> dataGetter;
+ private final Function<T, Boolean> dataSetter;
+
+ protected T lastData;
+
+ private boolean needsUpdate;
+
+ /**
+ * @param dataGetter () -> data this widget handles
+ * @param dataSetter data to set -> if setting data is successful
+ */
+ public DataControllerWidget(Supplier<T> dataGetter, Function<T, Boolean> dataSetter) {
+ this.dataGetter = dataGetter;
+ this.dataSetter = dataSetter;
+ }
+
+ protected T getLastData() {
+ return lastData;
+ }
+
+ @Override
+ public void onPostInit() {
+ super.onPostInit();
+ // client _should_ have received initial cover data from `GT_UIInfos#openCoverUI`
+ lastData = dataGetter.get();
+ if (NetworkUtils.isClient()) {
+ updateChildren(true);
+ }
+ }
+
+ @Override
+ public void detectAndSendChanges(boolean init) {
+ T actualValue = dataGetter.get();
+ if (actualValue == null) {
+ // data is in invalid state e.g. tile is broken, cover is removed
+ getWindow().tryClose();
+ return;
+ }
+ if (init || !actualValue.equals(getLastData())) {
+ // init sync or someone else edited data
+ lastData = actualValue;
+ syncDataToClient(actualValue);
+ }
+ }
+
+ protected void syncDataToClient(T data) {
+ syncToClient(0, buffer -> writeToPacket(buffer, data));
+ }
+
+ protected void syncDataToServer(T data) {
+ syncToServer(0, buffer -> writeToPacket(buffer, data));
+ updateChildren();
+ }
+
+ @Override
+ public void readOnClient(int id, PacketBuffer buf) throws IOException {
+ if (id == 0) {
+ lastData = readFromPacket(buf);
+ dataSetter.apply(getLastData());
+ updateChildren();
+ }
+ }
+
+ @Override
+ public void readOnServer(int id, PacketBuffer buf) throws IOException {
+ if (id == 0) {
+ lastData = readFromPacket(buf);
+ if (dataSetter.apply(getLastData())) {
+ markForUpdate();
+ } else {
+ getWindow().closeWindow();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @SideOnly(Side.CLIENT)
+ protected void updateChildren(boolean postInit) {
+ for (Widget child : getChildren()) {
+ if (child instanceof IDataFollowerWidget) {
+ ((IDataFollowerWidget<T, ?>) child).updateState(getLastData());
+ if (postInit) {
+ ((IDataFollowerWidget<T, ?>) child).onPostInit();
+ }
+ }
+ }
+ }
+
+ @SideOnly(Side.CLIENT)
+ protected void updateChildren() {
+ updateChildren(false);
+ }
+
+ protected abstract void writeToPacket(PacketBuffer buffer, T data);
+
+ protected abstract T readFromPacket(PacketBuffer buffer) throws IOException;
+
+ @Override
+ public void markForUpdate() {
+ needsUpdate = true;
+ }
+
+ @Override
+ public void unMarkForUpdate() {
+ needsUpdate = false;
+ }
+
+ @Override
+ public boolean isMarkedForUpdate() {
+ return needsUpdate;
+ }
+
+ /**
+ * @param widget widget to add that implements {@link IDataFollowerWidget}
+ * @param dataToStateGetter given data -> state of the widget to add
+ * @param dataUpdater (current data, state of the widget to add) -> new data to set
+ * @param applyForWidget methods to call for the widget to add
+ * @param <U> state type stored in the widget to add
+ * @param <W> widget type to add
+ */
+ public <U, W extends Widget & IDataFollowerWidget<T, U>> DataControllerWidget<T> addFollower(
+ W widget, Function<T, U> dataToStateGetter, BiFunction<T, U, T> dataUpdater, Consumer<W> applyForWidget) {
+ widget.setDataToStateGetter(dataToStateGetter);
+ widget.setStateSetter(state -> {
+ T newData = dataUpdater.apply(getLastData(), state);
+ lastData = newData;
+ dataSetter.apply(getLastData());
+ syncDataToServer(newData);
+ });
+ applyForWidget.accept(widget);
+ addChild(widget);
+ return this;
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/FluidDisplaySlotWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/FluidDisplaySlotWidget.java
new file mode 100644
index 0000000000..99200e5e8d
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/FluidDisplaySlotWidget.java
@@ -0,0 +1,474 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+import com.gtnewhorizons.modularui.common.internal.wrapper.BaseSlot;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import gregtech.GT_Mod;
+import gregtech.api.interfaces.IFluidAccess;
+import gregtech.api.interfaces.IHasFluidDisplayItem;
+import gregtech.api.interfaces.metatileentity.IFluidLockable;
+import gregtech.api.util.GT_Utility;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.IFluidContainerItem;
+
+public class FluidDisplaySlotWidget extends SlotWidget {
+
+ private IHasFluidDisplayItem iHasFluidDisplay;
+ private Supplier<IFluidAccess> fluidAccessConstructor;
+ private Supplier<Boolean> canDrainGetter;
+ private Supplier<Boolean> canFillGetter;
+ private Action actionRealClick = Action.NONE;
+ private Action actionDragAndDrop = Action.NONE;
+ private BiFunction<ClickData, FluidDisplaySlotWidget, Boolean> beforeRealClick;
+ private BiFunction<ClickData, FluidDisplaySlotWidget, Boolean> beforeDragAndDrop;
+ private Runnable updateFluidDisplayItem = () -> {
+ if (iHasFluidDisplay != null) {
+ iHasFluidDisplay.updateFluidDisplayItem();
+ }
+ };
+
+ public FluidDisplaySlotWidget(BaseSlot slot) {
+ super(slot);
+ setAccess(false, false);
+ disableShiftInsert();
+ }
+
+ public FluidDisplaySlotWidget(IItemHandlerModifiable handler, int index) {
+ this(new BaseSlot(handler, index, true));
+ }
+
+ // === client actions ===
+
+ @Override
+ public ClickResult onClick(int buttonId, boolean doubleClick) {
+ if (actionRealClick == Action.NONE) return ClickResult.REJECT;
+ if (interactionDisabled) return ClickResult.REJECT;
+
+ /*
+ * While a logical client don't really need to process fluid cells upon click (it could have just wait
+ * for server side to send the result), doing so would result in every fluid interaction having a
+ * noticeable delay between clicking and changes happening even on single player.
+ * I'd imagine this lag to become only more severe when playing MP over ethernet, which would have much more latency
+ * than a memory connection
+ */
+ ClickData clickData = ClickData.create(buttonId, doubleClick);
+ ItemStack verifyToken = executeRealClick(clickData);
+ syncToServer(2, buffer -> {
+ clickData.writeToPacket(buffer);
+ try {
+ buffer.writeItemStackToBuffer(verifyToken);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ return ClickResult.ACCEPT;
+ }
+
+ @Override
+ public boolean handleDragAndDrop(ItemStack draggedStack, int button) {
+ if (actionDragAndDrop == Action.NONE || actionDragAndDrop == Action.TRANSFER) return false;
+ if (interactionDisabled) return false;
+
+ ClickData clickData = ClickData.create(button, false);
+ executeDragAndDrop(clickData, draggedStack);
+ syncToServer(5, buffer -> {
+ try {
+ clickData.writeToPacket(buffer);
+ buffer.writeItemStackToBuffer(draggedStack);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ draggedStack.stackSize = 0;
+ return true;
+ }
+
+ @Override
+ public List<String> getExtraTooltip() {
+ return Collections.emptyList();
+ }
+
+ // === server actions ===
+
+ @Override
+ public void readOnServer(int id, PacketBuffer buf) throws IOException {
+ if (id == 1) {
+ getMcSlot().xDisplayPosition = buf.readVarIntFromBuffer();
+ getMcSlot().yDisplayPosition = buf.readVarIntFromBuffer();
+ } else if (id == 2) {
+ onClickServer(ClickData.readPacket(buf), buf.readItemStackFromBuffer());
+ } else if (id == 3) {
+ phantomScroll(buf.readVarIntFromBuffer());
+ } else if (id == 4) {
+ setEnabled(buf.readBoolean());
+ } else if (id == 5) {
+ executeDragAndDrop(ClickData.readPacket(buf), buf.readItemStackFromBuffer());
+ if (onDragAndDropComplete != null) {
+ onDragAndDropComplete.accept(this);
+ }
+ }
+ markForUpdate();
+ }
+
+ private void onClickServer(ClickData clickData, ItemStack clientVerifyToken) {
+ ItemStack serverVerifyToken = executeRealClick(clickData);
+ // similar to what NetHandlerPlayServer#processClickWindow does
+ if (!ItemStack.areItemStacksEqual(clientVerifyToken, serverVerifyToken)) {
+ ((EntityPlayerMP) getContext().getPlayer())
+ .sendContainerToPlayer(getContext().getContainer());
+ }
+ }
+
+ // === client/server actions ===
+
+ private ItemStack executeRealClick(ClickData clickData) {
+ if (actionRealClick == Action.NONE) return null;
+ if (beforeRealClick != null && !beforeRealClick.apply(clickData, this)) return null;
+
+ ItemStack ret = null;
+ if (actionRealClick == Action.TRANSFER) {
+ if (fluidAccessConstructor == null) {
+ GT_Mod.GT_FML_LOGGER.warn(
+ "FluidDisplaySlotWidget is asked to transfer fluid, but fluidAccessConstructor is null!");
+ return null;
+ }
+ ret = transferFluid(
+ fluidAccessConstructor.get(),
+ getContext().getPlayer(),
+ clickData.mouseButton == 0,
+ canDrainGetter != null ? canDrainGetter.get() : true,
+ canFillGetter != null ? canFillGetter.get() : true);
+ } else if (actionRealClick == Action.LOCK) {
+ lockFluid(getContext().getPlayer().inventory.getItemStack());
+ }
+
+ updateFluidDisplayItem.run();
+ return ret;
+ }
+
+ protected ItemStack transferFluid(
+ IFluidAccess aFluidAccess,
+ EntityPlayer aPlayer,
+ boolean aProcessFullStack,
+ boolean aCanDrain,
+ boolean aCanFill) {
+ ItemStack tStackHeld = aPlayer.inventory.getItemStack();
+ ItemStack tStackSizedOne = GT_Utility.copyAmount(1, tStackHeld);
+ if (tStackSizedOne == null || tStackHeld.stackSize == 0) return null;
+ FluidStack tInputFluid = aFluidAccess.get();
+ FluidStack tFluidHeld = GT_Utility.getFluidForFilledItem(tStackSizedOne, true);
+ if (tFluidHeld != null && tFluidHeld.amount <= 0) tFluidHeld = null;
+ if (tInputFluid == null) {
+ // tank empty, consider fill only from now on
+ if (!aCanFill)
+ // cannot fill and nothing to take, bail out
+ return null;
+ if (tFluidHeld == null)
+ // no fluid to fill
+ return null;
+ return fillFluid(aFluidAccess, aPlayer, tFluidHeld, aProcessFullStack);
+ }
+ // tank not empty, both action possible
+ if (tFluidHeld != null && tInputFluid.amount < aFluidAccess.getCapacity()) {
+ // both nonnull and have space left for filling.
+ if (aCanFill)
+ // actually both pickup and fill is reasonable, but I'll go with fill here
+ return fillFluid(aFluidAccess, aPlayer, tFluidHeld, aProcessFullStack);
+ if (!aCanDrain)
+ // cannot take AND cannot fill, why make this call then?
+ return null;
+ // the slot does not allow filling, so try take some
+ return drainFluid(aFluidAccess, aPlayer, aProcessFullStack);
+ } else {
+ // cannot fill and there is something to take
+ if (!aCanDrain)
+ // but the slot does not allow taking, so bail out
+ return null;
+ return drainFluid(aFluidAccess, aPlayer, aProcessFullStack);
+ }
+ }
+
+ protected static ItemStack drainFluid(IFluidAccess aFluidAccess, EntityPlayer aPlayer, boolean aProcessFullStack) {
+ FluidStack tTankStack = aFluidAccess.get();
+ if (tTankStack == null) return null;
+ ItemStack tStackHeld = aPlayer.inventory.getItemStack();
+ ItemStack tStackSizedOne = GT_Utility.copyAmount(1, tStackHeld);
+ if (tStackSizedOne == null || tStackHeld.stackSize == 0) return null;
+ int tOriginalFluidAmount = tTankStack.amount;
+ ItemStack tFilledContainer = GT_Utility.fillFluidContainer(tTankStack, tStackSizedOne, true, false);
+ if (tFilledContainer == null && tStackSizedOne.getItem() instanceof IFluidContainerItem) {
+ IFluidContainerItem tContainerItem = (IFluidContainerItem) tStackSizedOne.getItem();
+ int tFilledAmount = tContainerItem.fill(tStackSizedOne, tTankStack, true);
+ if (tFilledAmount > 0) {
+ tFilledContainer = tStackSizedOne;
+ tTankStack.amount -= tFilledAmount;
+ }
+ }
+ if (tFilledContainer != null) {
+ if (aProcessFullStack) {
+ int tFilledAmount = tOriginalFluidAmount - tTankStack.amount;
+ /*
+ work out how many more items we can fill
+ one cell is already used, so account for that
+ the round down behavior will left over a fraction of a cell worth of fluid
+ the user then get to decide what to do with it
+ it will not be too fancy if it spills out partially filled cells
+ */
+ int tAdditionalParallel = Math.min(tStackHeld.stackSize - 1, tTankStack.amount / tFilledAmount);
+ tTankStack.amount -= tFilledAmount * tAdditionalParallel;
+ tFilledContainer.stackSize += tAdditionalParallel;
+ }
+ replaceCursorItemStack(aPlayer, tFilledContainer);
+ }
+ aFluidAccess.verifyFluidStack();
+ return tFilledContainer;
+ }
+
+ protected static ItemStack fillFluid(
+ IFluidAccess aFluidAccess, EntityPlayer aPlayer, FluidStack aFluidHeld, boolean aProcessFullStack) {
+ // we are not using aMachine.fill() here any more, so we need to check for fluid type here ourselves
+ if (aFluidAccess.get() != null && !aFluidAccess.get().isFluidEqual(aFluidHeld)) return null;
+ ItemStack tStackHeld = aPlayer.inventory.getItemStack();
+ ItemStack tStackSizedOne = GT_Utility.copyAmount(1, tStackHeld);
+ if (tStackSizedOne == null) return null;
+
+ int tFreeSpace = aFluidAccess.getCapacity() - (aFluidAccess.get() != null ? aFluidAccess.get().amount : 0);
+ if (tFreeSpace <= 0)
+ // no space left
+ return null;
+
+ // find out how much fluid can be taken
+ // some cells cannot be partially filled
+ ItemStack tStackEmptied = null;
+ int tAmountTaken = 0;
+ if (tFreeSpace >= aFluidHeld.amount) {
+ // fully accepted - try take it from item now
+ // IFluidContainerItem is intentionally not checked here. it will be checked later
+ tStackEmptied = GT_Utility.getContainerForFilledItem(tStackSizedOne, false);
+ tAmountTaken = aFluidHeld.amount;
+ }
+ if (tStackEmptied == null && tStackSizedOne.getItem() instanceof IFluidContainerItem) {
+ // either partially accepted, or is IFluidContainerItem
+ IFluidContainerItem container = (IFluidContainerItem) tStackSizedOne.getItem();
+ FluidStack tDrained = container.drain(tStackSizedOne, tFreeSpace, true);
+ if (tDrained != null && tDrained.amount > 0) {
+ // something is actually drained - change the cell and drop it to player
+ tStackEmptied = tStackSizedOne;
+ tAmountTaken = tDrained.amount;
+ }
+ }
+ if (tStackEmptied == null)
+ // somehow the cell refuse to give out that amount of fluid, no op then
+ return null;
+
+ // find out how many fill can we do
+ // same round down behavior as above
+ // however here the fluid stack is not changed at all, so the exact code will slightly differ
+ int tParallel = aProcessFullStack ? Math.min(tFreeSpace / tAmountTaken, tStackHeld.stackSize) : 1;
+ if (aFluidAccess.get() == null) {
+ FluidStack tNewFillableStack = aFluidHeld.copy();
+ tNewFillableStack.amount = tAmountTaken * tParallel;
+ aFluidAccess.set(tNewFillableStack);
+ } else {
+ aFluidAccess.addAmount(tAmountTaken * tParallel);
+ }
+ tStackEmptied.stackSize = tParallel;
+ replaceCursorItemStack(aPlayer, tStackEmptied);
+ return tStackEmptied;
+ }
+
+ protected static void replaceCursorItemStack(EntityPlayer aPlayer, ItemStack tStackResult) {
+ int tStackResultMaxStackSize = tStackResult.getMaxStackSize();
+ while (tStackResult.stackSize > tStackResultMaxStackSize) {
+ aPlayer.inventory.getItemStack().stackSize -= tStackResultMaxStackSize;
+ GT_Utility.addItemToPlayerInventory(aPlayer, tStackResult.splitStack(tStackResultMaxStackSize));
+ }
+ if (aPlayer.inventory.getItemStack().stackSize == tStackResult.stackSize) {
+ // every cell is mutated. it could just stay on the cursor.
+ aPlayer.inventory.setItemStack(tStackResult);
+ } else {
+ // some cells not mutated. The mutated cells must go into the inventory
+ // or drop into the world if there isn't enough space.
+ ItemStack tStackHeld = aPlayer.inventory.getItemStack();
+ tStackHeld.stackSize -= tStackResult.stackSize;
+ GT_Utility.addItemToPlayerInventory(aPlayer, tStackResult);
+ }
+ }
+
+ protected void executeDragAndDrop(ClickData clickData, ItemStack draggedStack) {
+ if (actionDragAndDrop == Action.NONE || actionDragAndDrop == Action.TRANSFER) return;
+ if (beforeDragAndDrop != null && !beforeDragAndDrop.apply(clickData, this)) return;
+
+ if (actionDragAndDrop == Action.LOCK) {
+ lockFluid(draggedStack);
+ }
+ updateFluidDisplayItem.run();
+ }
+
+ protected void lockFluid(ItemStack cursorStack) {
+ if (!(iHasFluidDisplay instanceof IFluidLockable)) return;
+ IFluidLockable mteToLock = (IFluidLockable) iHasFluidDisplay;
+
+ if (cursorStack == null) {
+ if (!mteToLock.allowChangingLockedFluid(null)) return;
+
+ mteToLock.lockFluid(false);
+ mteToLock.setLockedFluidName(null);
+ GT_Utility.sendChatToPlayer(getContext().getPlayer(), GT_Utility.trans("300.1", "Fluid Lock Cleared."));
+
+ if (!isClient()) {
+ mteToLock.onFluidLockPacketReceived(null);
+ }
+ } else {
+ FluidStack fluidStack = GT_Utility.getFluidFromContainerOrFluidDisplay(cursorStack);
+ if (fluidStack == null) return;
+ Fluid tFluid = fluidStack.getFluid();
+ if (tFluid == null) return;
+
+ if (!mteToLock.allowChangingLockedFluid(tFluid.getName())) return;
+
+ mteToLock.lockFluid(true);
+ mteToLock.setLockedFluidName(tFluid.getName());
+ GT_Utility.sendChatToPlayer(
+ getContext().getPlayer(),
+ String.format(
+ GT_Utility.trans("151.4", "Successfully locked Fluid to %s"),
+ new FluidStack(tFluid, 1).getLocalizedName()));
+
+ if (!isClient()) {
+ mteToLock.onFluidLockPacketReceived(tFluid.getName());
+ }
+ }
+ }
+
+ protected void updateFluidDisplayItem() {
+ if (iHasFluidDisplay != null) {
+ iHasFluidDisplay.updateFluidDisplayItem();
+ }
+ }
+
+ // === setters ===
+
+ public FluidDisplaySlotWidget setFluidAccessConstructor(Supplier<IFluidAccess> fluidAccessConstructor) {
+ this.fluidAccessConstructor = fluidAccessConstructor;
+ return this;
+ }
+
+ public FluidDisplaySlotWidget setIHasFluidDisplay(IHasFluidDisplayItem iHasFluidDisplay) {
+ this.iHasFluidDisplay = iHasFluidDisplay;
+ return this;
+ }
+
+ public FluidDisplaySlotWidget setCanDrainGetter(Supplier<Boolean> canDrainGetter) {
+ this.canDrainGetter = canDrainGetter;
+ return this;
+ }
+
+ public FluidDisplaySlotWidget setCanDrain(boolean canDrain) {
+ return setCanDrainGetter(() -> canDrain);
+ }
+
+ public FluidDisplaySlotWidget setCanFillGetter(Supplier<Boolean> canFillGetter) {
+ this.canFillGetter = canFillGetter;
+ return this;
+ }
+
+ public FluidDisplaySlotWidget setCanFill(boolean canFill) {
+ return setCanFillGetter(() -> canFill);
+ }
+
+ /**
+ * Sets action called on click while holding the real item.
+ */
+ public FluidDisplaySlotWidget setActionRealClick(Action actionRealClick) {
+ this.actionRealClick = actionRealClick;
+ return this;
+ }
+
+ /**
+ * Sets action called on drag-and-drop from NEI.
+ * You can't use {@link Action#TRANSFER} here.
+ */
+ public FluidDisplaySlotWidget setActionDragAndDrop(Action actionDragAndDrop) {
+ this.actionDragAndDrop = actionDragAndDrop;
+ return this;
+ }
+
+ /**
+ * Sets function called before {@link #executeRealClick}.
+ * @param beforeRealClick (click data, this widget) -> if allow click
+ */
+ public FluidDisplaySlotWidget setBeforeRealClick(
+ BiFunction<ClickData, FluidDisplaySlotWidget, Boolean> beforeRealClick) {
+ this.beforeRealClick = beforeRealClick;
+ return this;
+ }
+
+ /**
+ * Sets function called before {@link #executeDragAndDrop}.
+ * @param beforeDragAndDrop (click data, this widget) -> if allow click
+ */
+ public FluidDisplaySlotWidget setBeforeDragAndDrop(
+ BiFunction<ClickData, FluidDisplaySlotWidget, Boolean> beforeDragAndDrop) {
+ this.beforeDragAndDrop = beforeDragAndDrop;
+ return this;
+ }
+
+ /**
+ * Sets function called before both of {@link #executeRealClick} and {@link #executeDragAndDrop}.
+ * @param beforeClick (click data, this widget) -> if allow click
+ */
+ public FluidDisplaySlotWidget setBeforeClick(BiFunction<ClickData, FluidDisplaySlotWidget, Boolean> beforeClick) {
+ setBeforeRealClick(beforeClick);
+ setBeforeDragAndDrop(beforeClick);
+ return this;
+ }
+
+ /**
+ * By default, this widget runs {@link IHasFluidDisplayItem#updateFluidDisplayItem} after click.
+ * You can specify custom update action with this method.
+ */
+ public FluidDisplaySlotWidget setUpdateFluidDisplayItem(Runnable updateFluidDisplayItem) {
+ this.updateFluidDisplayItem = updateFluidDisplayItem;
+ return this;
+ }
+
+ /**
+ * Action triggered on mouse click or NEI drag-and-drop.
+ */
+ public enum Action {
+
+ /**
+ * Fill/drain fluid into/from the tank.
+ * Uses fluid amount, so drag-and-drop cannot use this mode.
+ */
+ TRANSFER,
+
+ /**
+ * Lock fluid for {@link IFluidLockable}.
+ * Does not use fluid amount.
+ */
+ LOCK,
+
+ /**
+ * Set filter for the tank. (not implemented yet)
+ * Does not use fluid amount.
+ */
+ FILTER,
+
+ /**
+ * Does nothing.
+ */
+ NONE
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/ItemWatcherSlotWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/ItemWatcherSlotWidget.java
new file mode 100644
index 0000000000..7385208874
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/ItemWatcherSlotWidget.java
@@ -0,0 +1,47 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+import com.gtnewhorizons.modularui.api.forge.ItemStackHandler;
+import com.gtnewhorizons.modularui.common.internal.wrapper.BaseSlot;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import gregtech.api.util.GT_Utility;
+import java.util.function.Supplier;
+import net.minecraft.item.ItemStack;
+
+/**
+ * Watches specific ItemStack and pulls changes from it.
+ * Player cannot interact with slot, other than viewing NEI recipe or adding bookmark.
+ */
+public class ItemWatcherSlotWidget extends SlotWidget {
+
+ private ItemStack lastItem;
+ private Supplier<ItemStack> getter;
+
+ public ItemWatcherSlotWidget() {
+ super(BaseSlot.phantom(new ItemStackHandler(), 0));
+ disableInteraction();
+ }
+
+ public ItemWatcherSlotWidget setGetter(Supplier<ItemStack> getter) {
+ this.getter = getter;
+ return this;
+ }
+
+ @Override
+ public void detectAndSendChanges(boolean init) {
+ ItemStack target = getter.get();
+ if (init || !GT_Utility.areStacksEqual(lastItem, target)) {
+ ItemStack toPut;
+ if (target != null) {
+ toPut = target.copy();
+ toPut.stackSize = 1;
+ } else {
+ toPut = null;
+ }
+ ((IItemHandlerModifiable) getMcSlot().getItemHandler()).setStackInSlot(0, toPut);
+ lastItem = target;
+ getMcSlot().onSlotChanged();
+ }
+ super.detectAndSendChanges(init);
+ }
+}