diff options
Diffstat (limited to 'src/main/java/gregtech/common/gui')
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; + }); + } +} |