aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/common/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/common/gui')
-rw-r--r--src/main/java/gregtech/common/gui/ComplexParallelMachineGUIProvider.java32
-rw-r--r--src/main/java/gregtech/common/gui/MachineGUIProvider.java510
-rw-r--r--src/main/java/gregtech/common/gui/PartGUIProvider.java33
-rw-r--r--src/main/java/gregtech/common/gui/modularui/UIHelper.java200
-rw-r--r--src/main/java/gregtech/common/gui/modularui/uifactory/SelectItemUIFactory.java229
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/AESlotWidget.java64
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CheckRecipeResultSyncer.java26
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverCycleButtonWidget.java91
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataControllerWidget.java133
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_CycleButtonWidget.java39
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_NumericWidget.java60
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_SlotWidget.java101
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_TextFieldWidget.java134
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_ToggleButtonWidget.java86
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java166
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/FluidLockWidget.java17
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/ItemWatcherSlotWidget.java50
-rw-r--r--src/main/java/gregtech/common/gui/modularui/widget/ShutDownReasonSyncer.java26
18 files changed, 1997 insertions, 0 deletions
diff --git a/src/main/java/gregtech/common/gui/ComplexParallelMachineGUIProvider.java b/src/main/java/gregtech/common/gui/ComplexParallelMachineGUIProvider.java
new file mode 100644
index 0000000000..89b42c8500
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/ComplexParallelMachineGUIProvider.java
@@ -0,0 +1,32 @@
+package gregtech.common.gui;
+
+import javax.annotation.Nonnull;
+
+import com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+
+import gregtech.api.gui.GUIHost;
+import gregtech.api.logic.ComplexParallelProcessingLogic;
+import gregtech.api.logic.interfaces.PowerLogicHost;
+import gregtech.api.logic.interfaces.ProcessingLogicHost;
+
+/**
+ * Default GUI class for machines with complex parallel
+ */
+public class ComplexParallelMachineGUIProvider<T extends GUIHost & ProcessingLogicHost<? extends ComplexParallelProcessingLogic<?>> & PowerLogicHost>
+ extends MachineGUIProvider<T> {
+
+ public ComplexParallelMachineGUIProvider(@Nonnull T host) {
+ super(host);
+ }
+
+ @Override
+ protected void attachSynchHandlers(@Nonnull Builder builder, @Nonnull UIBuildContext uiContext) {
+
+ }
+
+ @Override
+ protected void addWidgets(@Nonnull Builder builder, @Nonnull UIBuildContext uiContext) {
+
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/MachineGUIProvider.java b/src/main/java/gregtech/common/gui/MachineGUIProvider.java
new file mode 100644
index 0000000000..3cc889f000
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/MachineGUIProvider.java
@@ -0,0 +1,510 @@
+package gregtech.common.gui;
+
+import static gregtech.api.metatileentity.BaseTileEntity.BUTTON_FORBIDDEN_TOOLTIP;
+import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.util.StatCollector;
+
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.NumberFormatMUI;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.ItemDrawable;
+import com.gtnewhorizons.modularui.api.drawable.UITexture;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.api.widget.IWidgetBuilder;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.DrawableWidget;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.MultiChildWidget;
+import com.gtnewhorizons.modularui.common.widget.TabButton;
+import com.gtnewhorizons.modularui.common.widget.TabContainer;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+
+import gregtech.api.enums.InventoryType;
+import gregtech.api.enums.SoundResource;
+import gregtech.api.enums.VoidingMode;
+import gregtech.api.gui.GUIHost;
+import gregtech.api.gui.GUIProvider;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.modularui.GUITextureSet;
+import gregtech.api.logic.MuTEProcessingLogic;
+import gregtech.api.logic.PowerLogic;
+import gregtech.api.logic.interfaces.PowerLogicHost;
+import gregtech.api.logic.interfaces.ProcessingLogicHost;
+
+/**
+ * Default GUI a machine will use to show its information
+ */
+public class MachineGUIProvider<T extends GUIHost & ProcessingLogicHost<? extends MuTEProcessingLogic<?>> & PowerLogicHost>
+ extends GUIProvider<T> {
+
+ private static final int LOGO_SIZE = 17;
+ @Nonnull
+ protected static final Pos2d POWER_SWITCH_BUTTON_DEFAULT_POS = new Pos2d(144, 0);
+ @Nonnull
+ protected static final Pos2d VOIDING_MODE_BUTTON_DEFAULT_POS = new Pos2d(54, 0);
+ @Nonnull
+ protected static final Pos2d INPUT_SEPARATION_BUTTON_DEFAULT_POS = new Pos2d(36, 0);
+ @Nonnull
+ protected static final Pos2d BATCH_MODE_BUTTON_DEFAULT_POS = new Pos2d(18, 0);
+ @Nonnull
+ protected static final Pos2d RECIPE_LOCKING_BUTTON_DEFAULT_POS = new Pos2d(0, 0);
+
+ protected static final NumberFormatMUI numberFormat = new NumberFormatMUI();
+
+ public MachineGUIProvider(@Nonnull T host) {
+ super(host);
+ }
+
+ @Override
+ protected void attachSynchHandlers(@Nonnull Builder builder, @Nonnull UIBuildContext uiContext) {
+
+ }
+
+ @Override
+ protected void addWidgets(@Nonnull Builder builder, @Nonnull UIBuildContext uiContext) {
+ int page = 0;
+ builder.setBackground(GT_UITextures.BACKGROUND_SINGLEBLOCK_DEFAULT);
+ MultiChildWidget mainTab = new MultiChildWidget();
+ mainTab.setSize(host.getWidth(), host.getHeight());
+ createMainTab(mainTab, builder, uiContext);
+ TabContainer tabs = new TabContainer().setButtonSize(20, 24);
+ tabs.addTabButton(
+ new TabButton(page++)
+ .setBackground(
+ false,
+ ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0, 1f, 0.5f),
+ new ItemDrawable(host.getAsItem()).withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .setBackground(
+ true,
+ ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0.5f, 1f, 1f),
+ new ItemDrawable(host.getAsItem()).withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .addTooltip(host.getMachineName())
+ .setPos(20 * (page - 1), -20))
+ .addPage(mainTab);
+ if (host.hasItemInput()) {
+ MultiChildWidget itemInputTab = new MultiChildWidget();
+ itemInputTab.setSize(host.getWidth(), host.getHeight());
+ createItemInputTab(itemInputTab, builder, uiContext);
+ tabs.addTabButton(
+ new TabButton(page++)
+ .setBackground(
+ false,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f),
+ GT_UITextures.PICTURE_ITEM_IN.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .setBackground(
+ true,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f),
+ GT_UITextures.PICTURE_ITEM_IN.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .addTooltip("Item Input Inventory")
+ .setPos(20 * (page - 1), -20))
+ .addPage(itemInputTab.addChild(getLogo().setPos(147, 86)));
+ }
+
+ if (host.hasItemOutput()) {
+ MultiChildWidget itemOutputTab = new MultiChildWidget();
+ itemOutputTab.setSize(host.getWidth(), host.getHeight());
+ createItemOutputTab(itemOutputTab, builder, uiContext);
+ tabs.addTabButton(
+ new TabButton(page++)
+ .setBackground(
+ false,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f),
+ GT_UITextures.PICTURE_ITEM_OUT.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .setBackground(
+ true,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f),
+ GT_UITextures.PICTURE_ITEM_OUT.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .addTooltip("Item Output Inventory")
+ .setPos(20 * (page - 1), -20))
+ .addPage(itemOutputTab.addChild(getLogo().setPos(147, 86)));
+ }
+
+ if (host.hasFluidInput()) {
+ MultiChildWidget fluidInputTab = new MultiChildWidget();
+ fluidInputTab.setSize(host.getWidth(), host.getHeight());
+ createFluidInputTab(fluidInputTab, builder, uiContext);
+ tabs.addTabButton(
+ new TabButton(page++)
+ .setBackground(
+ false,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f),
+ GT_UITextures.PICTURE_FLUID_IN.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .setBackground(
+ true,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f),
+ GT_UITextures.PICTURE_FLUID_IN.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .addTooltip("Fluid Input Tanks")
+ .setPos(20 * (page - 1), -20))
+ .addPage(fluidInputTab.addChild(getLogo().setPos(147, 86)));
+ }
+
+ if (host.hasFluidOutput()) {
+ MultiChildWidget fluidOutputTab = new MultiChildWidget();
+ fluidOutputTab.setSize(host.getWidth(), host.getHeight());
+ createFluidOutputTab(fluidOutputTab, builder, uiContext);
+ tabs.addTabButton(
+ new TabButton(page++)
+ .setBackground(
+ false,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f),
+ GT_UITextures.PICTURE_FLUID_OUT.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .setBackground(
+ true,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f),
+ GT_UITextures.PICTURE_FLUID_OUT.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .addTooltip("Fluid Output Tanks")
+ .setPos(20 * (page - 1), -20))
+ .addPage(fluidOutputTab.addChild(getLogo().setPos(147, 86)));
+ }
+ MultiChildWidget powerInfoTab = new MultiChildWidget();
+ powerInfoTab.setSize(host.getWidth(), host.getHeight());
+ createPowerTab(powerInfoTab, builder, uiContext);
+ tabs.addTabButton(
+ new TabButton(page++)
+ .setBackground(
+ false,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f),
+ GT_UITextures.PICTURE_FLUID_OUT.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .setBackground(
+ true,
+ ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f),
+ GT_UITextures.PICTURE_FLUID_OUT.withFixedSize(16, 16)
+ .withOffset(2, 4))
+ .addTooltip("Power Information")
+ .setPos(20 * (page - 1), -20))
+ .addPage(powerInfoTab.addChild(getLogo().setPos(147, 86)));
+ builder.widget(tabs);
+ }
+
+ protected void createMainTab(@Nonnull MultiChildWidget tab, @Nonnull Builder builder,
+ @Nonnull UIBuildContext uiBuildContext) {
+ MultiChildWidget buttons = new MultiChildWidget();
+ buttons.setSize(16, 167)
+ .setPos(7, 86);
+ buttons.addChild(createPowerSwitchButton(builder))
+ .addChild(createVoidExcessButton(builder))
+ .addChild(createInputSeparationButton(builder))
+ .addChild(createBatchModeButton(builder))
+ .addChild(createLockToSingleRecipeButton(builder));
+ tab.addChild(
+ new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK)
+ .setPos(7, 4)
+ .setSize(160, 75))
+ .addChild(buttons);
+ }
+
+ protected void createItemInputTab(@Nonnull MultiChildWidget tab, @Nonnull Builder builder,
+ @Nonnull UIBuildContext uiBuildContext) {
+ tab.addChild(
+ host.getItemLogic(InventoryType.Input, null)
+ .getGuiPart()
+ .setSize(18 * 4 + 9, 5 * 18)
+ .setPos(host.getWidth() / 2 - 2 * 18, 10));
+ }
+
+ protected void createItemOutputTab(@Nonnull MultiChildWidget tab, @Nonnull Builder builder,
+ @Nonnull UIBuildContext uiBuildContext) {
+ tab.addChild(
+ host.getItemLogic(InventoryType.Output, null)
+ .getGuiPart()
+ .setSize(18 * 4 + 9, 5 * 18)
+ .setPos(host.getWidth() / 2 - 2 * 18, 10));
+ }
+
+ protected void createFluidInputTab(@Nonnull MultiChildWidget tab, @Nonnull Builder builder,
+ @Nonnull UIBuildContext uiBuildContext) {
+ tab.addChild(
+ host.getFluidLogic(InventoryType.Input, null)
+ .getGuiPart()
+ .setSize(18 * 4 + 9, 5 * 18)
+ .setPos(host.getWidth() / 2 - 2 * 18, 10));
+ }
+
+ protected void createFluidOutputTab(@Nonnull MultiChildWidget tab, @Nonnull Builder builder,
+ @Nonnull UIBuildContext uiBuildContext) {
+ tab.addChild(
+ host.getFluidLogic(InventoryType.Output, null)
+ .getGuiPart()
+ .setSize(18 * 4 + 9, 5 * 18)
+ .setPos(host.getWidth() / 2 - 2 * 18, 10));
+ }
+
+ protected void createPowerTab(@Nonnull MultiChildWidget tab, @Nonnull Builder builder,
+ @Nonnull UIBuildContext uiBuildContext) {
+ PowerLogic power = host.getPowerLogic();
+ tab.addChild(
+ new TextWidget()
+ .setStringSupplier(
+ () -> numberFormat.format(power.getStoredEnergy()) + "/"
+ + numberFormat.format(power.getCapacity())
+ + " EU")
+ .setPos(10, 30))
+ .addChild(
+ new TextWidget()
+ .setStringSupplier(
+ () -> numberFormat.format(power.getVoltage()) + " EU/t"
+ + "("
+ + numberFormat.format(power.getMaxAmperage())
+ + " A)")
+ .setPos(10, 60));
+ }
+
+ /**
+ * Should return the logo you want to use that is pasted on each tab. Default is the GT logo.
+ */
+ @Nonnull
+ protected Widget getLogo() {
+ DrawableWidget logo = new DrawableWidget();
+ logo.setDrawable(GUITextureSet.DEFAULT.getGregTechLogo())
+ .setSize(LOGO_SIZE, LOGO_SIZE);
+ return logo;
+ }
+
+ protected Pos2d getPowerSwitchButtonPos() {
+ return POWER_SWITCH_BUTTON_DEFAULT_POS;
+ }
+
+ protected ButtonWidget createPowerSwitchButton(IWidgetBuilder<?> builder) {
+ ButtonWidget button = new ButtonWidget();
+ button.setOnClick((clickData, widget) -> {
+ if (host.isAllowedToWork()) {
+ host.disableWorking();
+ } else {
+ host.enableWorking();
+ }
+ })
+ .setPlayClickSoundResource(
+ () -> host.isAllowedToWork() ? SoundResource.GUI_BUTTON_UP.resourceLocation
+ : SoundResource.GUI_BUTTON_DOWN.resourceLocation)
+ .setBackground(() -> {
+ if (host.isAllowedToWork()) {
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD_PRESSED,
+ GT_UITextures.OVERLAY_BUTTON_POWER_SWITCH_ON };
+ } else {
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD,
+ GT_UITextures.OVERLAY_BUTTON_POWER_SWITCH_OFF };
+ }
+ })
+ .attachSyncer(new FakeSyncWidget.BooleanSyncer(host::isAllowedToWork, host::setAllowedToWork), builder)
+ .addTooltip(StatCollector.translateToLocal("GT5U.gui.button.power_switch"))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(getPowerSwitchButtonPos())
+ .setSize(16, 16);
+ return button;
+ }
+
+ @Nonnull
+ protected Pos2d getVoidingModeButtonPos() {
+ return VOIDING_MODE_BUTTON_DEFAULT_POS;
+ }
+
+ @Nonnull
+ protected ButtonWidget createVoidExcessButton(IWidgetBuilder<?> builder) {
+ ButtonWidget button = new ButtonWidget();
+ button.setOnClick((clickData, widget) -> {
+ if (host.supportsVoidProtection()) {
+ Set<VoidingMode> allowed = host.getAllowedVoidingModes();
+ switch (clickData.mouseButton) {
+ case 0 -> host.setVoidingMode(
+ host.getVoidingMode()
+ .nextInCollection(allowed));
+ case 1 -> host.setVoidingMode(
+ host.getVoidingMode()
+ .previousInCollection(allowed));
+ }
+ widget.notifyTooltipChange();
+ }
+ })
+ .setPlayClickSound(host.supportsVoidProtection())
+ .setBackground(() -> {
+ List<UITexture> ret = new ArrayList<>();
+ ret.add(host.getVoidingMode().buttonTexture);
+ ret.add(host.getVoidingMode().buttonOverlay);
+ if (!host.supportsVoidProtection()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_FORBIDDEN);
+ }
+ return ret.toArray(new IDrawable[0]);
+ })
+ .attachSyncer(
+ new FakeSyncWidget.IntegerSyncer(
+ () -> host.getVoidingMode()
+ .ordinal(),
+ val -> host.setVoidingMode(VoidingMode.fromOrdinal(val))),
+ builder)
+ .dynamicTooltip(
+ () -> Arrays.asList(
+ StatCollector.translateToLocal("GT5U.gui.button.voiding_mode"),
+ StatCollector.translateToLocal(
+ host.getVoidingMode()
+ .getTransKey())))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(getVoidingModeButtonPos())
+ .setSize(16, 16);
+ if (!host.supportsVoidProtection()) {
+ button.addTooltip(StatCollector.translateToLocal(BUTTON_FORBIDDEN_TOOLTIP));
+ }
+ return button;
+ }
+
+ @Nonnull
+ protected Pos2d getInputSeparationButtonPos() {
+ return INPUT_SEPARATION_BUTTON_DEFAULT_POS;
+ }
+
+ protected ButtonWidget createInputSeparationButton(IWidgetBuilder<?> builder) {
+ ButtonWidget button = new ButtonWidget();
+ button.setOnClick((clickData, widget) -> {
+ if (host.supportsInputSeparation()) {
+ host.setInputSeparation(!host.isInputSeparated());
+ }
+ })
+ .setPlayClickSound(host.supportsInputSeparation())
+ .setBackground(() -> {
+ List<UITexture> ret = new ArrayList<>();
+ if (host.isInputSeparated()) {
+ ret.add(GT_UITextures.BUTTON_STANDARD_PRESSED);
+ if (host.supportsInputSeparation()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_INPUT_SEPARATION_ON);
+ } else {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_INPUT_SEPARATION_ON_DISABLED);
+ }
+ } else {
+ ret.add(GT_UITextures.BUTTON_STANDARD);
+ if (host.supportsInputSeparation()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_INPUT_SEPARATION_OFF);
+ } else {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_INPUT_SEPARATION_OFF_DISABLED);
+ }
+ }
+ if (!host.supportsInputSeparation()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_FORBIDDEN);
+ }
+ return ret.toArray(new IDrawable[0]);
+ })
+ .attachSyncer(new FakeSyncWidget.BooleanSyncer(host::isInputSeparated, host::setInputSeparation), builder)
+ .addTooltip(StatCollector.translateToLocal("GT5U.gui.button.input_separation"))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(getInputSeparationButtonPos())
+ .setSize(16, 16);
+ if (!host.supportsInputSeparation()) {
+ button.addTooltip(StatCollector.translateToLocal(BUTTON_FORBIDDEN_TOOLTIP));
+ }
+ return button;
+ }
+
+ @Nonnull
+ protected Pos2d getBatchModeButtonPos() {
+ return BATCH_MODE_BUTTON_DEFAULT_POS;
+ }
+
+ protected ButtonWidget createBatchModeButton(IWidgetBuilder<?> builder) {
+ ButtonWidget button = new ButtonWidget();
+ button.setOnClick((clickData, widget) -> {
+ if (host.supportsBatchMode()) {
+ host.setBatchMode(!host.isBatchModeEnabled());
+ }
+ })
+ .setPlayClickSound(host.supportsBatchMode())
+ .setBackground(() -> {
+ List<UITexture> ret = new ArrayList<>();
+ if (host.isBatchModeEnabled()) {
+ ret.add(GT_UITextures.BUTTON_STANDARD_PRESSED);
+ if (host.supportsBatchMode()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_BATCH_MODE_ON);
+ } else {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_BATCH_MODE_ON_DISABLED);
+ }
+ } else {
+ ret.add(GT_UITextures.BUTTON_STANDARD);
+ if (host.supportsBatchMode()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_BATCH_MODE_OFF);
+ } else {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_BATCH_MODE_OFF_DISABLED);
+ }
+ }
+ if (!host.supportsBatchMode()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_FORBIDDEN);
+ }
+ return ret.toArray(new IDrawable[0]);
+ })
+ .attachSyncer(new FakeSyncWidget.BooleanSyncer(host::isBatchModeEnabled, host::setBatchMode), builder)
+ .addTooltip(StatCollector.translateToLocal("GT5U.gui.button.batch_mode"))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(getBatchModeButtonPos())
+ .setSize(16, 16);
+ if (!host.supportsBatchMode()) {
+ button.addTooltip(StatCollector.translateToLocal(BUTTON_FORBIDDEN_TOOLTIP));
+ }
+ return button;
+ }
+
+ @Nonnull
+ protected Pos2d getRecipeLockingButtonPos() {
+ return RECIPE_LOCKING_BUTTON_DEFAULT_POS;
+ }
+
+ protected ButtonWidget createLockToSingleRecipeButton(IWidgetBuilder<?> builder) {
+ ButtonWidget button = new ButtonWidget();
+ button.setOnClick((clickData, widget) -> {
+ if (host.supportsSingleRecipeLocking()) {
+ host.setRecipeLocking(!host.isRecipeLockingEnabled());
+ }
+ })
+ .setPlayClickSound(host.supportsSingleRecipeLocking())
+ .setBackground(() -> {
+ List<UITexture> ret = new ArrayList<>();
+ if (host.isRecipeLockingEnabled()) {
+ ret.add(GT_UITextures.BUTTON_STANDARD_PRESSED);
+ if (host.supportsSingleRecipeLocking()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_RECIPE_LOCKED);
+ } else {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_RECIPE_LOCKED_DISABLED);
+ }
+ } else {
+ ret.add(GT_UITextures.BUTTON_STANDARD);
+ if (host.supportsSingleRecipeLocking()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_RECIPE_UNLOCKED);
+ } else {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_RECIPE_UNLOCKED_DISABLED);
+ }
+ }
+ if (!host.supportsSingleRecipeLocking()) {
+ ret.add(GT_UITextures.OVERLAY_BUTTON_FORBIDDEN);
+ }
+ return ret.toArray(new IDrawable[0]);
+ })
+ .attachSyncer(
+ new FakeSyncWidget.BooleanSyncer(host::isRecipeLockingEnabled, host::setRecipeLocking),
+ builder)
+ .addTooltip(StatCollector.translateToLocal("GT5U.gui.button.lock_recipe"))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(getRecipeLockingButtonPos())
+ .setSize(16, 16);
+ if (!host.supportsSingleRecipeLocking()) {
+ button.addTooltip(StatCollector.translateToLocal(BUTTON_FORBIDDEN_TOOLTIP));
+ }
+ return button;
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/PartGUIProvider.java b/src/main/java/gregtech/common/gui/PartGUIProvider.java
new file mode 100644
index 0000000000..7600444f71
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/PartGUIProvider.java
@@ -0,0 +1,33 @@
+package gregtech.common.gui;
+
+import javax.annotation.Nonnull;
+
+import com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+
+import gregtech.api.gui.GUIHost;
+import gregtech.api.gui.GUIProvider;
+import gregtech.api.logic.interfaces.FluidInventoryLogicHost;
+import gregtech.api.logic.interfaces.ItemInventoryLogicHost;
+import gregtech.api.logic.interfaces.PowerLogicHost;
+
+public class PartGUIProvider<T extends GUIHost & ItemInventoryLogicHost & PowerLogicHost & FluidInventoryLogicHost>
+ extends GUIProvider<T> {
+
+ public PartGUIProvider(@Nonnull T host) {
+ super(host);
+ }
+
+ @Override
+ protected void attachSynchHandlers(@Nonnull Builder builder, @Nonnull UIBuildContext uiContext) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Unimplemented method 'attachSynchHandlers'");
+ }
+
+ @Override
+ protected void addWidgets(@Nonnull Builder builder, @Nonnull UIBuildContext uiContext) {
+ // TODO Auto-generated method stub
+ throw new UnsupportedOperationException("Unimplemented method 'addWidgets'");
+ }
+
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/UIHelper.java b/src/main/java/gregtech/common/gui/modularui/UIHelper.java
new file mode 100644
index 0000000000..64d686d350
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/UIHelper.java
@@ -0,0 +1,200 @@
+package gregtech.common.gui.modularui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+
+import gregtech.api.enums.SteamVariant;
+import gregtech.api.gui.modularui.SteamTexture;
+import gregtech.api.recipe.BasicUIProperties;
+import gregtech.api.util.MethodsReturnNonnullByDefault;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public class UIHelper {
+
+ /**
+ * Iterates over candidates for slot placement.
+ */
+ @SuppressWarnings("SimplifyStreamApiCallChains")
+ public static void forEachSlots(ForEachSlot forEachItemInputSlot, ForEachSlot forEachItemOutputSlot,
+ ForEachSlot forEachSpecialSlot, ForEachSlot forEachFluidInputSlot, ForEachSlot forEachFluidOutputSlot,
+ IDrawable itemSlotBackground, IDrawable fluidSlotBackground, BasicUIProperties uiProperties, int itemInputCount,
+ int itemOutputCount, int fluidInputCount, int fluidOutputCount, SteamVariant steamVariant, Pos2d offset) {
+ List<Pos2d> itemInputPositions = uiProperties.itemInputPositionsGetter.apply(itemInputCount)
+ .stream()
+ .map(p -> p.add(offset))
+ .collect(Collectors.toList());
+ for (int i = 0; i < itemInputPositions.size(); i++) {
+ forEachItemInputSlot.accept(
+ i,
+ getBackgroundsForSlot(itemSlotBackground, uiProperties, false, false, i, false, steamVariant),
+ itemInputPositions.get(i));
+ }
+
+ List<Pos2d> itemOutputPositions = uiProperties.itemOutputPositionsGetter.apply(itemOutputCount)
+ .stream()
+ .map(p -> p.add(offset))
+ .collect(Collectors.toList());
+ for (int i = 0; i < itemOutputPositions.size(); i++) {
+ forEachItemOutputSlot.accept(
+ i,
+ getBackgroundsForSlot(itemSlotBackground, uiProperties, false, true, i, false, steamVariant),
+ itemOutputPositions.get(i));
+ }
+
+ forEachSpecialSlot.accept(
+ 0,
+ getBackgroundsForSlot(itemSlotBackground, uiProperties, false, false, 0, true, steamVariant),
+ uiProperties.specialItemPositionGetter.get()
+ .add(offset));
+
+ List<Pos2d> fluidInputPositions = uiProperties.fluidInputPositionsGetter.apply(fluidInputCount)
+ .stream()
+ .map(p -> p.add(offset))
+ .collect(Collectors.toList());
+ for (int i = 0; i < fluidInputPositions.size(); i++) {
+ forEachFluidInputSlot.accept(
+ i,
+ getBackgroundsForSlot(fluidSlotBackground, uiProperties, true, false, i, false, steamVariant),
+ fluidInputPositions.get(i));
+ }
+
+ List<Pos2d> fluidOutputPositions = uiProperties.fluidOutputPositionsGetter.apply(fluidOutputCount)
+ .stream()
+ .map(p -> p.add(offset))
+ .collect(Collectors.toList());
+ for (int i = 0; i < fluidOutputPositions.size(); i++) {
+ forEachFluidOutputSlot.accept(
+ i,
+ getBackgroundsForSlot(fluidSlotBackground, uiProperties, true, true, i, false, steamVariant),
+ fluidOutputPositions.get(i));
+ }
+ }
+
+ /**
+ * @return Display positions for GUI, including border (18x18 size)
+ */
+ public static List<Pos2d> getItemInputPositions(int itemInputCount) {
+ return switch (itemInputCount) {
+ case 0 -> Collections.emptyList();
+ case 1 -> getGridPositions(itemInputCount, 52, 24, 1, 1);
+ case 2 -> getGridPositions(itemInputCount, 34, 24, 2, 1);
+ case 3 -> getGridPositions(itemInputCount, 16, 24, 3, 1);
+ case 4 -> getGridPositions(itemInputCount, 34, 15, 2, 2);
+ case 5, 6 -> getGridPositions(itemInputCount, 16, 15, 3, 2);
+ default -> getGridPositions(itemInputCount, 16, 6, 3);
+ };
+ }
+
+ /**
+ * @return Display positions for GUI, including border (18x18 size)
+ */
+ public static List<Pos2d> getItemOutputPositions(int itemOutputCount) {
+ return switch (itemOutputCount) {
+ case 0 -> Collections.emptyList();
+ case 1 -> getGridPositions(itemOutputCount, 106, 24, 1, 1);
+ case 2 -> getGridPositions(itemOutputCount, 106, 24, 2, 1);
+ case 3 -> getGridPositions(itemOutputCount, 106, 24, 3, 1);
+ case 4 -> getGridPositions(itemOutputCount, 106, 15, 2, 2);
+ case 5, 6 -> getGridPositions(itemOutputCount, 106, 15, 3, 2);
+ default -> getGridPositions(itemOutputCount, 106, 6, 3);
+ };
+ }
+
+ /**
+ * @return Display position for GUI, including border (18x18 size)
+ */
+ public static Pos2d getSpecialItemPosition() {
+ return new Pos2d(124, 62);
+ }
+
+ /**
+ * @return Display positions for GUI, including border (18x18 size)
+ */
+ public static List<Pos2d> getFluidInputPositions(int fluidInputCount) {
+ List<Pos2d> results = new ArrayList<>();
+ int x = 52;
+ for (int i = 0; i < fluidInputCount; i++) {
+ results.add(new Pos2d(x, 62));
+ x -= 18;
+ }
+ return results;
+ }
+
+ /**
+ * @return Display positions for GUI, including border (18x18 size)
+ */
+ public static List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
+ List<Pos2d> results = new ArrayList<>();
+ int x = 106;
+ for (int i = 0; i < fluidOutputCount; i++) {
+ results.add(new Pos2d(x, 62));
+ x += 18;
+ }
+ return results;
+ }
+
+ public static List<Pos2d> getGridPositions(int totalCount, int xOrigin, int yOrigin, int xDirMaxCount,
+ int yDirMaxCount) {
+ // 18 pixels to get to a new grid for placing an item tile since they are 16x16 and have 1 pixel buffers
+ // around them.
+ int distance = 18;
+
+ List<Pos2d> results = new ArrayList<>();
+ int count = 0;
+ loop: for (int j = 0; j < yDirMaxCount; j++) {
+ for (int i = 0; i < xDirMaxCount; i++) {
+ if (count >= totalCount) break loop;
+ results.add(new Pos2d(xOrigin + i * distance, yOrigin + j * distance));
+ count++;
+ }
+ }
+ return results;
+ }
+
+ public static List<Pos2d> getGridPositions(int totalCount, int xOrigin, int yOrigin, int xDirMaxCount) {
+ return getGridPositions(totalCount, xOrigin, yOrigin, xDirMaxCount, 100);
+ }
+
+ private static IDrawable[] getBackgroundsForSlot(IDrawable base, BasicUIProperties uiProperties, boolean isFluid,
+ boolean isOutput, int index, boolean isSpecial, SteamVariant steamVariant) {
+ IDrawable overlay = getOverlay(uiProperties, isFluid, isOutput, index, isSpecial, steamVariant);
+ if (overlay != null) {
+ return new IDrawable[] { base, overlay };
+ } else {
+ return new IDrawable[] { base };
+ }
+ }
+
+ @Nullable
+ private static IDrawable getOverlay(BasicUIProperties uiProperties, boolean isFluid, boolean isOutput, int index,
+ boolean isSpecial, SteamVariant steamVariant) {
+ if (isSpecial && !uiProperties.useSpecialSlot) {
+ return null;
+ }
+ if (steamVariant != SteamVariant.NONE) {
+ SteamTexture steamTexture = uiProperties.getOverlayForSlotSteam(index, isFluid, isOutput, isSpecial);
+ if (steamTexture != null) {
+ return steamTexture.get(steamVariant);
+ } else {
+ return null;
+ }
+ } else {
+ return uiProperties.getOverlayForSlot(index, isFluid, isOutput, isSpecial);
+ }
+ }
+
+ @FunctionalInterface
+ public interface ForEachSlot {
+
+ void accept(int index, IDrawable[] backgrounds, Pos2d pos);
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/uifactory/SelectItemUIFactory.java b/src/main/java/gregtech/common/gui/modularui/uifactory/SelectItemUIFactory.java
new file mode 100644
index 0000000000..452ca36c0c
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/uifactory/SelectItemUIFactory.java
@@ -0,0 +1,229 @@
+package gregtech.common.gui.modularui.uifactory;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
+
+import com.gtnewhorizons.modularui.api.GlStateManager;
+import com.gtnewhorizons.modularui.api.ModularUITextures;
+import com.gtnewhorizons.modularui.api.drawable.GuiHelper;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.drawable.ItemDrawable;
+import com.gtnewhorizons.modularui.api.drawable.Text;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.DrawableWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
+
+import gregtech.api.enums.Dyes;
+import gregtech.api.gui.GT_GUIColorOverride;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.util.GT_Util;
+import gregtech.api.util.GT_Utility;
+
+/**
+ * Creates UI for selecting item from given list. This is client-only UI to allow using client-preferred settings.
+ */
+public class SelectItemUIFactory {
+
+ private final String header;
+ private final ItemStack headerItem;
+ public static final int UNSELECTED = -1;
+ private static final int cols = 9;
+ private final Consumer<ItemStack> selectedCallback;
+ // passed in stack
+ private final List<ItemStack> stacks;
+ private final boolean noDeselect;
+ private int selected;
+ private boolean anotherWindow = false;
+ private AtomicBoolean dialogOpened;
+ private int guiTint = GT_Util.getRGBInt(Dyes.MACHINE_METAL.getRGBA());
+ private Supplier<ItemStack> currentGetter;
+
+ private final GT_GUIColorOverride colorOverride = GT_GUIColorOverride.get("SelectItemUIFactory");
+
+ private int getTextColorOrDefault(String textType, int defaultColor) {
+ return colorOverride.getTextColorOrDefault(textType, defaultColor);
+ }
+
+ private final Supplier<Integer> COLOR_TITLE = () -> getTextColorOrDefault("title", 0x222222);
+ private final Supplier<Integer> COLOR_TEXT_GRAY = () -> getTextColorOrDefault("text_gray", 0x555555);
+
+ public SelectItemUIFactory(String header, ItemStack headerItem, Consumer<ItemStack> selectedCallback,
+ List<ItemStack> stacks) {
+ this(header, headerItem, selectedCallback, stacks, UNSELECTED);
+ }
+
+ public SelectItemUIFactory(String header, ItemStack headerItem, Consumer<ItemStack> selectedCallback,
+ List<ItemStack> stacks, int selected) {
+ this(header, headerItem, selectedCallback, stacks, selected, false);
+ }
+
+ /**
+ * Constructor for a dialog to select an item from given list. Given callback may be called zero or more times
+ * depending on user action.
+ *
+ * @param header Header text
+ * @param headerItem ItemStack to use as Dialog icon
+ * @param selectedCallback callback upon selected
+ * @param stacks list to choose from
+ * @param selected preselected item. Use {@link #UNSELECTED} for unselected. Invalid selected will be
+ * clamped to 0 or highest index
+ * @param noDeselect true if player cannot deselect, false otherwise. If this is set to true, selectedCallback
+ * is guaranteed to be called with a nonnull stack
+ */
+ public SelectItemUIFactory(String header, ItemStack headerItem, Consumer<ItemStack> selectedCallback,
+ List<ItemStack> stacks, int selected, boolean noDeselect) {
+ this.header = header;
+ this.headerItem = headerItem;
+ this.selectedCallback = selectedCallback;
+ this.stacks = stacks;
+ this.noDeselect = noDeselect;
+ this.selected = noDeselect ? Math.max(0, selected) : selected;
+ }
+
+ /**
+ * @param anotherWindow If UI is shown on top of another window
+ * @param dialogOpened Flag to store whether this UI is opened and hence it should block duplicated creation of
+ * this UI
+ */
+ public SelectItemUIFactory setAnotherWindow(boolean anotherWindow, AtomicBoolean dialogOpened) {
+ this.anotherWindow = anotherWindow;
+ this.dialogOpened = dialogOpened;
+ return this;
+ }
+
+ public SelectItemUIFactory setGuiTint(int guiTint) {
+ this.guiTint = guiTint;
+ return this;
+ }
+
+ /**
+ * @param currentGetter Getter for "current" item displayed that may change from external reasons
+ */
+ public SelectItemUIFactory setCurrentGetter(Supplier<ItemStack> currentGetter) {
+ this.currentGetter = currentGetter;
+ return this;
+ }
+
+ public ModularWindow createWindow(UIBuildContext buildContext) {
+ ModularWindow.Builder builder = ModularWindow
+ .builder(getGUIWidth(), 53 + 18 * ((stacks.size() - 1) / cols + 1));
+ builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
+ builder.setGuiTint(guiTint);
+
+ if (headerItem != null) {
+ builder.widget(
+ new ItemDrawable(headerItem).asWidget()
+ .setPos(5, 5)
+ .setSize(16, 16));
+ }
+ builder.widget(
+ new TextWidget(header).setDefaultColor(COLOR_TITLE.get())
+ .setPos(25, 9));
+
+ int currentSlotX = 9
+ + getFontRenderer().getStringWidth(StatCollector.translateToLocal("GT5U.gui.select.current"));
+ int currentSlotY = 24;
+ builder.widget(new DrawableWidget() {
+
+ @Override
+ public void onScreenUpdate() {
+ super.onScreenUpdate();
+ if (currentGetter != null) {
+ ItemStack current = currentGetter.get();
+ selected = GT_Utility.findMatchingStackInList(stacks, current);
+ }
+ }
+ }.setDrawable(GT_UITextures.SLOT_DARK_GRAY)
+ .setPos(currentSlotX, currentSlotY)
+ .setSize(18, 18))
+ .widget(
+ new ItemDrawable(() -> getCandidate(getSelected())).asWidgetWithTooltip()
+ .setPos(currentSlotX + 1, currentSlotY + 1));
+ builder.widget(
+ new TextWidget(StatCollector.translateToLocal("GT5U.gui.select.current"))
+ .setDefaultColor(COLOR_TEXT_GRAY.get())
+ .setPos(8, 25 + (18 - getFontRenderer().FONT_HEIGHT) / 2));
+
+ for (int i = 0; i < stacks.size(); i++) {
+ final int index = i;
+ builder.widget(new ButtonWidget() {
+
+ @Override
+ public void draw(float partialTicks) {
+ GlStateManager.pushMatrix();
+ // so that item z levels are properly ordered
+ GlStateManager.translate(0, 0, 150 * getWindowLayer());
+ new ItemDrawable(stacks.get(index)).draw(1, 1, 16, 16, partialTicks);
+ GlStateManager.popMatrix();
+ }
+ }.setOnClick((clickData, widget) -> {
+ if (clickData.mouseButton == 0) {
+ setSelected(index);
+ } else {
+ setSelected(UNSELECTED);
+ }
+ selectedCallback.accept(getCandidate(getSelected()));
+ })
+ .setSynced(false, false)
+ .dynamicTooltip(() -> GuiHelper.getItemTooltip(stacks.get(index)))
+ .setUpdateTooltipEveryTick(true)
+ .setBackground(
+ () -> new IDrawable[] {
+ index == selected ? GT_UITextures.SLOT_DARK_GRAY : ModularUITextures.ITEM_SLOT, })
+ .setPos(7 + 18 * (index % cols), 43 + 18 * (index / cols))
+ .setSize(18, 18));
+ }
+
+ if (anotherWindow) {
+ dialogOpened.set(true);
+ builder.widget(new ButtonWidget() {
+
+ @Override
+ public void onDestroy() {
+ dialogOpened.set(false);
+ }
+ }.setOnClick(
+ (clickData, widget) -> widget.getWindow()
+ .tryClose())
+ .setSynced(false, false)
+ .setBackground(ModularUITextures.VANILLA_BACKGROUND, new Text("x"))
+ .setPos(getGUIWidth() - 15, 3)
+ .setSize(12, 12));
+ }
+
+ return builder.build();
+ }
+
+ public int getSelected() {
+ return selected;
+ }
+
+ public void setSelected(int selected) {
+ if (selected == this.selected) return;
+ int newSelected = GT_Utility.clamp(selected, UNSELECTED, stacks.size() - 1);
+ if (noDeselect && newSelected == UNSELECTED) return;
+
+ this.selected = newSelected;
+ }
+
+ private ItemStack getCandidate(int listIndex) {
+ return listIndex < 0 || listIndex >= stacks.size() ? null : stacks.get(listIndex);
+ }
+
+ private FontRenderer getFontRenderer() {
+ return Minecraft.getMinecraft().fontRenderer;
+ }
+
+ private int getGUIWidth() {
+ return 176;
+ }
+}
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..368f219ef4
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/AESlotWidget.java
@@ -0,0 +1,64 @@
+package gregtech.common.gui.modularui.widget;
+
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.client.renderer.texture.TextureManager;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+
+import com.gtnewhorizons.modularui.common.internal.wrapper.BaseSlot;
+import com.gtnewhorizons.modularui.common.internal.wrapper.ModularGui;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+
+import appeng.api.storage.IItemDisplayRegistry.ItemRenderHook;
+import appeng.client.render.AppEngRenderItem;
+import appeng.core.AELog;
+import appeng.util.Platform;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+public class AESlotWidget extends SlotWidget {
+
+ private static class HookHolder {
+
+ static ItemRenderHook SKIP_ITEM_STACK_SIZE_HOOK = new ItemRenderHook() {
+
+ @Override
+ public boolean renderOverlay(FontRenderer fr, TextureManager tm, ItemStack is, int x, int y) {
+ return true;
+ }
+
+ @Override
+ public boolean showStackSize(ItemStack is) {
+ return false;
+ }
+ };
+ }
+
+ public AESlotWidget(BaseSlot slot) {
+ super(slot);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ protected void drawSlot(Slot slotIn) {
+ final AppEngRenderItem aeRenderItem = new AppEngRenderItem();
+ AppEngRenderItem.POST_HOOKS.add(HookHolder.SKIP_ITEM_STACK_SIZE_HOOK);
+ final RenderItem pIR = this.setItemRender(aeRenderItem);
+ try {
+ aeRenderItem.setAeStack(Platform.getAEStackInSlot(slotIn));
+ super.drawSlot(slotIn, true);
+ } catch (final Exception err) {
+ AELog.warn("[AppEng] AE prevented crash while drawing slot: " + err);
+ }
+ AppEngRenderItem.POST_HOOKS.remove(HookHolder.SKIP_ITEM_STACK_SIZE_HOOK);
+ 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/CheckRecipeResultSyncer.java b/src/main/java/gregtech/common/gui/modularui/widget/CheckRecipeResultSyncer.java
new file mode 100644
index 0000000000..9028c66be8
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CheckRecipeResultSyncer.java
@@ -0,0 +1,26 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+
+public class CheckRecipeResultSyncer extends FakeSyncWidget<CheckRecipeResult> {
+
+ public CheckRecipeResultSyncer(Supplier<CheckRecipeResult> getter, Consumer<CheckRecipeResult> setter) {
+ super(getter, setter, (buffer, result) -> {
+ NetworkUtils.writeStringSafe(buffer, result.getID());
+ result.encode(buffer);
+ }, buffer -> {
+ String id = NetworkUtils.readStringSafe(buffer);
+ CheckRecipeResult result = CheckRecipeResultRegistry.getSampleFromRegistry(id)
+ .newInstance();
+ result.decode(buffer);
+ return result;
+ });
+ }
+}
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..4d1c6649ae
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverCycleButtonWidget.java
@@ -0,0 +1,91 @@
+package gregtech.common.gui.modularui.widget;
+
+import org.lwjgl.opengl.GL11;
+
+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;
+
+/**
+ * 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..4543980ce1
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataControllerWidget.java
@@ -0,0 +1,133 @@
+package gregtech.common.gui.modularui.widget;
+
+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;
+
+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;
+
+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..ec7d31ca7e
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_CycleButtonWidget.java
@@ -0,0 +1,39 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+
+/**
+ * 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_NumericWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_NumericWidget.java
new file mode 100644
index 0000000000..f641ed090b
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_NumericWidget.java
@@ -0,0 +1,60 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+import com.gtnewhorizons.modularui.common.widget.textfield.NumericWidget;
+
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.modularui.IDataFollowerWidget;
+import gregtech.api.util.ISerializableObject;
+
+public class CoverDataFollower_NumericWidget<T extends ISerializableObject> extends NumericWidget
+ implements IDataFollowerWidget<T, Double> {
+
+ private Function<T, Double> dataToStateGetter;
+
+ public CoverDataFollower_NumericWidget() {
+ super();
+ setGetter(() -> 0); // 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_NumericWidget<T> setDataToStateGetter(Function<T, Double> dataToStateGetter) {
+ this.dataToStateGetter = dataToStateGetter;
+ return this;
+ }
+
+ @Override
+ public CoverDataFollower_NumericWidget<T> setStateSetter(Consumer<Double> setter) {
+ super.setSetter(setter::accept);
+ return this;
+ }
+
+ @Override
+ public void updateState(T data) {
+ setValue(dataToStateGetter.apply(data));
+ }
+
+}
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..0b827bfbc6
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_SlotWidget.java
@@ -0,0 +1,101 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import net.minecraft.item.ItemStack;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+import com.gtnewhorizons.modularui.api.widget.Interactable;
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+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;
+
+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 -> {
+ clickData.writeToPacket(buffer);
+ NetworkUtils.writeItemStack(buffer, draggedStack);
+ });
+ 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..1907aaf22f
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_TextFieldWidget.java
@@ -0,0 +1,134 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import net.minecraft.client.gui.GuiScreen;
+
+import com.gtnewhorizon.gtnhlib.util.parsing.MathExpressionParser;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Color;
+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;
+
+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));
+ }
+
+ /**
+ * @deprecated Use {@link CoverDataFollower_NumericWidget}
+ */
+ @Deprecated
+ 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;
+ }
+
+ /**
+ * @deprecated Use {@link CoverDataFollower_NumericWidget}
+ */
+ @Deprecated
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollNumbers() {
+ return setOnScrollNumbers(1, 50, 1000);
+ }
+
+ /**
+ * @deprecated Use {@link CoverDataFollower_NumericWidget}
+ */
+ @Deprecated
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollText(int baseStep, int ctrlStep, int shiftStep) {
+ setOnScroll((text, direction) -> {
+ int val = (int) MathExpressionParser.parse(text);
+ int step = (GuiScreen.isShiftKeyDown() ? shiftStep : GuiScreen.isCtrlKeyDown() ? ctrlStep : baseStep)
+ * direction;
+ try {
+ val = Math.addExact(val, step);
+ } catch (ArithmeticException ignored) {
+ val = Integer.MAX_VALUE;
+ }
+ return this.getDecimalFormatter()
+ .format(val);
+ });
+ return this;
+ }
+
+ /**
+ * @deprecated Use {@link CoverDataFollower_NumericWidget}
+ */
+ @Deprecated
+ public CoverDataFollower_TextFieldWidget<T> setOnScrollText() {
+ return setOnScrollText(1, 5, 50);
+ }
+
+ /**
+ * @deprecated Use {@link CoverDataFollower_NumericWidget}
+ */
+ @Deprecated
+ 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..6f798a93d5
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/CoverDataFollower_ToggleButtonWidget.java
@@ -0,0 +1,86 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+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;
+
+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..3717b128bb
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/DataControllerWidget.java
@@ -0,0 +1,166 @@
+package gregtech.common.gui.modularui.widget;
+
+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;
+
+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;
+
+/**
+ * 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() {
+ // 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/FluidLockWidget.java b/src/main/java/gregtech/common/gui/modularui/widget/FluidLockWidget.java
new file mode 100644
index 0000000000..5627a4fb8b
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/FluidLockWidget.java
@@ -0,0 +1,17 @@
+package gregtech.common.gui.modularui.widget;
+
+import com.gtnewhorizons.modularui.common.widget.FluidNameHolderWidget;
+
+import gregtech.api.interfaces.metatileentity.IFluidLockable;
+
+public class FluidLockWidget extends FluidNameHolderWidget {
+
+ public FluidLockWidget(IFluidLockable fluidLockable) {
+ super(fluidLockable::getLockedFluidName, name -> {
+ if (fluidLockable.acceptsFluidLock(name)) {
+ fluidLockable.setLockedFluidName(name);
+ fluidLockable.lockFluid(name != null);
+ }
+ });
+ }
+}
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..5200c1a416
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/ItemWatcherSlotWidget.java
@@ -0,0 +1,50 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Supplier;
+
+import net.minecraft.item.ItemStack;
+
+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;
+
+/**
+ * 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);
+ }
+}
diff --git a/src/main/java/gregtech/common/gui/modularui/widget/ShutDownReasonSyncer.java b/src/main/java/gregtech/common/gui/modularui/widget/ShutDownReasonSyncer.java
new file mode 100644
index 0000000000..21c19e524c
--- /dev/null
+++ b/src/main/java/gregtech/common/gui/modularui/widget/ShutDownReasonSyncer.java
@@ -0,0 +1,26 @@
+package gregtech.common.gui.modularui.widget;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+
+import gregtech.api.util.shutdown.ShutDownReason;
+import gregtech.api.util.shutdown.ShutDownReasonRegistry;
+
+public class ShutDownReasonSyncer extends FakeSyncWidget<ShutDownReason> {
+
+ public ShutDownReasonSyncer(Supplier<ShutDownReason> getter, Consumer<ShutDownReason> setter) {
+ super(getter, setter, (buffer, result) -> {
+ NetworkUtils.writeStringSafe(buffer, result.getID());
+ result.encode(buffer);
+ }, buffer -> {
+ String id = NetworkUtils.readStringSafe(buffer);
+ ShutDownReason result = ShutDownReasonRegistry.getSampleFromRegistry(id)
+ .newInstance();
+ result.decode(buffer);
+ return result;
+ });
+ }
+}