diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java b/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java index 32e457e4b..b51c9cb93 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java @@ -14,6 +14,7 @@ import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.ChatFormatting; +import net.minecraft.Util; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; @@ -22,6 +23,7 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.Level; import org.cyclops.cyclopscore.client.gui.component.WidgetScrollBar; import org.cyclops.cyclopscore.client.gui.component.button.ButtonText; import org.cyclops.cyclopscore.client.gui.component.input.WidgetArrowedListField; @@ -53,6 +55,8 @@ import javax.annotation.Nullable; import java.nio.file.InvalidPathException; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -88,6 +92,9 @@ public class ContainerScreenTerminalScripting extends ContainerScreenExtended toolbarEditButtons = Lists.newArrayList(); private ButtonIcon buttonUndo; private ButtonIcon buttonRedo; @@ -179,10 +186,24 @@ public int getTotalRows() { }); addRenderableWidget(textArea); - buttonCreateFile = new ButtonText(this.leftPos + 19, this.topPos + 222, 56, 10, Component.translatable("gui.integratedscripting.create_file"), Component.literal("+"), + // The scripts folder can only be opened if this client is running the server, such as in singleplayer + boolean canOpenFolder = Minecraft.getInstance().hasSingleplayerServer(); + buttonCreateFile = new ButtonText(this.leftPos + PATHS_X, this.topPos + 222, canOpenFolder ? PATHS_WIDTH - 12 : PATHS_WIDTH, 10, Component.translatable("gui.integratedscripting.create_file"), Component.literal("+"), (button) -> getMenu().createNewFile(), true); + buttonOpenFolder = null; + buttonOpenFolderDisk = Integer.MIN_VALUE; if (!focusMode) { addRenderableWidget(buttonCreateFile); + if (canOpenFolder) { + buttonOpenFolder = new ButtonIcon(this.leftPos + PATHS_X + PATHS_WIDTH - 10, this.topPos + 222, 10, 10, + Component.translatable("gui.integratedscripting.open_folder"), (button) -> { + this.openDiskFolder(); + // Run after the click is handled, as that focuses the button + Minecraft.getInstance().tell(() -> this.setFocused(null)); + }, ScriptImages.ACTION_OPEN_FOLDER); + addRenderableWidget(buttonOpenFolder); + this.updateOpenFolderButton(); + } } if (focusMode) { @@ -239,6 +260,37 @@ private ButtonIcon addToolbarButton(int x, int y, IImage image, KeyMapping keyMa return button; } + protected Path getDiskFolder(int disk) { + return IntegratedScripting._instance.scriptingData.getDiskPath(disk).toAbsolutePath().normalize(); + } + + private void updateOpenFolderButton() { + int disk = getMenu().getActiveDisk(); + if (this.buttonOpenFolder != null && disk != this.buttonOpenFolderDisk) { + this.buttonOpenFolderDisk = disk; + this.buttonOpenFolder.active = disk >= 0; + Component tooltip = Component.translatable("gui.integratedscripting.open_folder"); + if (disk >= 0) { + tooltip = tooltip.copy().append("\n").append(Component.literal(this.getDiskFolder(disk).toString()).withStyle(ChatFormatting.GRAY)); + } + this.buttonOpenFolder.setTooltip(Tooltip.create(tooltip)); + } + } + + private void openDiskFolder() { + int disk = getMenu().getActiveDisk(); + if (disk >= 0) { + Path path = this.getDiskFolder(disk); + try { + // The folder of a disk without scripts may not exist yet + Files.createDirectories(path); + } catch (IOException e) { + IntegratedScripting.clog(Level.ERROR, "Could not create the scripts folder " + path + ": " + e.getMessage()); + } + Util.getPlatform().openPath(path); + } + } + private void updateToolbar() { boolean scriptOpen = getMenu().getActiveScript() != null; for (ButtonIcon button : this.toolbarEditButtons) { @@ -252,6 +304,7 @@ private void updateToolbar() { protected void containerTick() { super.containerTick(); textArea.tick(); + this.updateOpenFolderButton(); this.updateToolbar(); // The matches change when the script is edited while searching this.updateSearchStatus(); diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java b/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java index 0796b72ef..4a980017b 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java @@ -21,6 +21,7 @@ public class ScriptImages { public static final Image ACTION_FIND = new Image(ICONS, 16, 16, 8, 8); public static final Image ACTION_GOTO_LINE = new Image(ICONS, 24, 16, 8, 8); public static final Image ACTION_COMMENT = new Image(ICONS, 32, 16, 8, 8); + public static final Image ACTION_OPEN_FOLDER = new Image(ICONS, 40, 16, 8, 8); public static final Image ACTION_FOCUS_ENTER = new Image(ICONS, 48, 16, 8, 8); public static final Image ACTION_FOCUS_EXIT = new Image(ICONS, 56, 16, 8, 8); diff --git a/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingData.java b/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingData.java index eea57b951..b36962848 100644 --- a/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingData.java +++ b/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingData.java @@ -101,7 +101,7 @@ protected Path getDisksPath() { return rootPath.resolve("scripting-disks"); } - protected Path getDiskPath(int id) { + public Path getDiskPath(int id) { return getDisksPath().resolve(String.valueOf(id)); } diff --git a/src/main/resources/assets/integratedscripting/lang/en_us.json b/src/main/resources/assets/integratedscripting/lang/en_us.json index 63dd0056e..83af42152 100644 --- a/src/main/resources/assets/integratedscripting/lang/en_us.json +++ b/src/main/resources/assets/integratedscripting/lang/en_us.json @@ -26,6 +26,7 @@ "gui.integratedscripting.disk": "Disk", "gui.integratedscripting.script": "Script", "gui.integratedscripting.create_file": "Create File", + "gui.integratedscripting.open_folder": "Open scripts folder", "gui.integratedscripting.find": "Find", "gui.integratedscripting.focus_mode.enter": "Enlarge editor", "gui.integratedscripting.focus_mode.exit": "Back to normal size (Escape)", @@ -198,9 +199,10 @@ "info_book.integratedscripting.advanced.external_editing": "External Editing", "info_book.integratedscripting.advanced.external_editing.text1": "If you have access to the file system on which your singleplayer or multiplayer game is running, you can access script files through any external editor. This allows you to create scripts using your favorite IDE (such as Vi, but definitely not Emacs).", - "info_book.integratedscripting.advanced.external_editing.text2": "Script files are accessible via the &oworld/integratedscripting/&r directory (see &osaves/&r for singleplayer worlds). In here, you will find directories for all &lScripting Disks&r by their id. Inside of each &lScripting Disk&r directory, you will find all script files stored in that disk.", + "info_book.integratedscripting.advanced.external_editing.text2": "Script files are accessible via the &oworld/integratedscripting/scripting-disks/&r directory (see &osaves/&r for singleplayer worlds). In here, you will find directories for all &lScripting Disks&r by their id. Inside of each &lScripting Disk&r directory, you will find all script files stored in that disk.", "info_book.integratedscripting.advanced.external_editing.text3": "You can safely modify these script files through an external editor, even if the game is running. After externally saving the file, they will be updated automatically in the game, and dependent variables will be updated as well.", "info_book.integratedscripting.advanced.external_editing.text4": "If your external editor supports this (modern IDEs do), modifying a script file through the &lScripting Terminal&r will also cause the files to be updated in your external editor.", + "info_book.integratedscripting.advanced.external_editing.text5": "In singleplayer, the folder button below the script list of the &lScripting Terminal&r opens the directory of the selected disk.", "info_book.integratedscripting.advanced.logging": "Logging", "info_book.integratedscripting.advanced.logging.text1": "For complex scripts, the need may arise to debug them by temporarily writing to external log files.", diff --git a/src/main/resources/assets/integratedscripting/textures/gui/icons.png b/src/main/resources/assets/integratedscripting/textures/gui/icons.png index 26a3a8cae..930ed7502 100644 Binary files a/src/main/resources/assets/integratedscripting/textures/gui/icons.png and b/src/main/resources/assets/integratedscripting/textures/gui/icons.png differ diff --git a/src/main/resources/data/integratedscripting/info/scripting_info.xml b/src/main/resources/data/integratedscripting/info/scripting_info.xml index 5327e9aa0..e1847f566 100644 --- a/src/main/resources/data/integratedscripting/info/scripting_info.xml +++ b/src/main/resources/data/integratedscripting/info/scripting_info.xml @@ -179,6 +179,7 @@ function getRunningAvg(value) { info_book.integratedscripting.advanced.external_editing.text2 info_book.integratedscripting.advanced.external_editing.text3 info_book.integratedscripting.advanced.external_editing.text4 + info_book.integratedscripting.advanced.external_editing.text5