From 0e28e33bf980554f5e449a09a61e1c53698f3baf Mon Sep 17 00:00:00 2001 From: Semmieboy YT Date: Thu, 2 Jun 2022 20:02:05 +0200 Subject: [PATCH] Added a drag and drop feature for songs --- src/main/java/semmiedev/disc_jockey/Main.java | 3 +- .../semmiedev/disc_jockey/SongLoader.java | 175 ++++++++++-------- .../disc_jockey/gui/SongListWidget.java | 1 + .../gui/screen/DiscJockeyScreen.java | 45 ++++- .../assets/disc_jockey/lang/en_us.json | 2 + 5 files changed, 142 insertions(+), 84 deletions(-) diff --git a/src/main/java/semmiedev/disc_jockey/Main.java b/src/main/java/semmiedev/disc_jockey/Main.java index 7a41f0e..37502bc 100644 --- a/src/main/java/semmiedev/disc_jockey/Main.java +++ b/src/main/java/semmiedev/disc_jockey/Main.java @@ -16,6 +16,7 @@ import net.minecraft.client.util.InputUtil; import net.minecraft.client.world.ClientWorld; import net.minecraft.text.MutableText; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.lwjgl.glfw.GLFW; @@ -62,7 +63,7 @@ public class Main implements ClientModInitializer { if (openScreenKeyBind.wasPressed()) { if (SongLoader.loadingSongs) { - client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".still_loading")); + client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".still_loading").formatted(Formatting.RED)); } else { client.setScreen(new DiscJockeyScreen()); } diff --git a/src/main/java/semmiedev/disc_jockey/SongLoader.java b/src/main/java/semmiedev/disc_jockey/SongLoader.java index 866bc4b..845b102 100644 --- a/src/main/java/semmiedev/disc_jockey/SongLoader.java +++ b/src/main/java/semmiedev/disc_jockey/SongLoader.java @@ -6,9 +6,11 @@ import net.minecraft.text.Text; import semmiedev.disc_jockey.gui.SongListWidget; import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; public class SongLoader { public static final ArrayList SONGS = new ArrayList<>(); @@ -23,93 +25,104 @@ public class SongLoader { SONG_SUGGESTIONS.clear(); SONG_SUGGESTIONS.add("Songs are loading, please wait"); for (File file : Main.songsFolder.listFiles()) { - if (file.isFile()) { - try { - BinaryReader reader = new BinaryReader(Files.newInputStream(file.toPath())); - Song song = new Song(); - - song.fileName = file.getName().replaceAll("[\\n\\r]", ""); - - song.length = reader.readShort(); - - boolean newFormat = song.length == 0; - if (newFormat) { - song.formatVersion = reader.readByte(); - song.vanillaInstrumentCount = reader.readByte(); - song.length = reader.readShort(); - } - - song.height = reader.readShort(); - song.name = reader.readString().replaceAll("[\\n\\r]", ""); - song.author = reader.readString().replaceAll("[\\n\\r]", ""); - song.originalAuthor = reader.readString().replaceAll("[\\n\\r]", ""); - song.description = reader.readString().replaceAll("[\\n\\r]", ""); - song.tempo = reader.readShort(); - song.autoSaving = reader.readByte(); - song.autoSavingDuration = reader.readByte(); - song.timeSignature = reader.readByte(); - song.minutesSpent = reader.readInt(); - song.leftClicks = reader.readInt(); - song.rightClicks = reader.readInt(); - song.blocksAdded = reader.readInt(); - song.blocksRemoved = reader.readInt(); - song.importFileName = reader.readString().replaceAll("[\\n\\r]", ""); - - if (newFormat) { - song.loop = reader.readByte(); - song.maxLoopCount = reader.readByte(); - song.loopStartTick = reader.readShort(); - } - - song.displayName = song.name.replaceAll("\\s", "").isEmpty() ? song.fileName : song.name+" ("+song.fileName+")"; - song.entry = new SongListWidget.SongEntry(song, SONGS.size()); - song.entry.favorite = Main.config.favorites.contains(song.fileName); - song.searchableFileName = song.fileName.toLowerCase().replaceAll("\\s", ""); - song.searchableName = song.name.toLowerCase().replaceAll("\\s", ""); - - short tick = -1; - short jumps; - while ((jumps = reader.readShort()) != 0) { - tick += jumps; - short layer = -1; - while ((jumps = reader.readShort()) != 0) { - layer += jumps; - - byte instrumentId = reader.readByte(); - byte noteId = (byte)(reader.readByte() - 33); - - if (newFormat) { - // Data that is not needed as it only works with commands - reader.readByte(); // Velocity - reader.readByte(); // Panning - reader.readShort(); // Pitch - } - - if (noteId < 0) { - noteId = 0; - } else if (noteId > 24) { - noteId = 24; - } - - Note note = new Note(Note.INSTRUMENTS[instrumentId], noteId); - if (!song.uniqueNotes.contains(note)) song.uniqueNotes.add(note); - - song.notes = Arrays.copyOf(song.notes, song.notes.length + 1); - song.notes[song.notes.length - 1] = tick | layer << Note.LAYER_SHIFT | (long)instrumentId << Note.INSTRUMENT_SHIFT | (long)noteId << Note.NOTE_SHIFT; - } - } - - SONGS.add(song); - } catch (Throwable exception) { - Main.LOGGER.error("Unable to read song "+file.getName(), exception); - } + Song song = null; + try { + song = loadSong(file); + } catch (IOException exception) { + Main.LOGGER.error("Unable to read song "+file.getName(), exception); } + if (song != null) SONGS.add(song); } for (Song song : SONGS) SONG_SUGGESTIONS.add(song.displayName); Main.config.favorites.removeIf(favorite -> SongLoader.SONGS.stream().map(song -> song.fileName).noneMatch(favorite::equals)); - SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.PACK_LOAD_FAILURE, Main.NAME, Text.translatable(Main.MOD_ID+".loading_done")); + if (MinecraftClient.getInstance().textRenderer != null) SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.PACK_LOAD_FAILURE, Main.NAME, Text.translatable(Main.MOD_ID+".loading_done")); loadingSongs = false; }).start(); } + + public static Song loadSong(File file) throws IOException { + if (file.isFile()) { + BinaryReader reader = new BinaryReader(Files.newInputStream(file.toPath())); + Song song = new Song(); + + song.fileName = file.getName().replaceAll("[\\n\\r]", ""); + + song.length = reader.readShort(); + + boolean newFormat = song.length == 0; + if (newFormat) { + song.formatVersion = reader.readByte(); + song.vanillaInstrumentCount = reader.readByte(); + song.length = reader.readShort(); + } + + song.height = reader.readShort(); + song.name = reader.readString().replaceAll("[\\n\\r]", ""); + song.author = reader.readString().replaceAll("[\\n\\r]", ""); + song.originalAuthor = reader.readString().replaceAll("[\\n\\r]", ""); + song.description = reader.readString().replaceAll("[\\n\\r]", ""); + song.tempo = reader.readShort(); + song.autoSaving = reader.readByte(); + song.autoSavingDuration = reader.readByte(); + song.timeSignature = reader.readByte(); + song.minutesSpent = reader.readInt(); + song.leftClicks = reader.readInt(); + song.rightClicks = reader.readInt(); + song.blocksAdded = reader.readInt(); + song.blocksRemoved = reader.readInt(); + song.importFileName = reader.readString().replaceAll("[\\n\\r]", ""); + + if (newFormat) { + song.loop = reader.readByte(); + song.maxLoopCount = reader.readByte(); + song.loopStartTick = reader.readShort(); + } + + song.displayName = song.name.replaceAll("\\s", "").isEmpty() ? song.fileName : song.name+" ("+song.fileName+")"; + song.entry = new SongListWidget.SongEntry(song, SONGS.size()); + song.entry.favorite = Main.config.favorites.contains(song.fileName); + song.searchableFileName = song.fileName.toLowerCase().replaceAll("\\s", ""); + song.searchableName = song.name.toLowerCase().replaceAll("\\s", ""); + + short tick = -1; + short jumps; + while ((jumps = reader.readShort()) != 0) { + tick += jumps; + short layer = -1; + while ((jumps = reader.readShort()) != 0) { + layer += jumps; + + byte instrumentId = reader.readByte(); + byte noteId = (byte)(reader.readByte() - 33); + + if (newFormat) { + // Data that is not needed as it only works with commands + reader.readByte(); // Velocity + reader.readByte(); // Panning + reader.readShort(); // Pitch + } + + if (noteId < 0) { + noteId = 0; + } else if (noteId > 24) { + noteId = 24; + } + + Note note = new Note(Note.INSTRUMENTS[instrumentId], noteId); + if (!song.uniqueNotes.contains(note)) song.uniqueNotes.add(note); + + song.notes = Arrays.copyOf(song.notes, song.notes.length + 1); + song.notes[song.notes.length - 1] = tick | layer << Note.LAYER_SHIFT | (long)instrumentId << Note.INSTRUMENT_SHIFT | (long)noteId << Note.NOTE_SHIFT; + } + } + + return song; + } + return null; + } + + public static void sort() { + SONGS.sort(Comparator.comparing(song -> song.displayName)); + } } diff --git a/src/main/java/semmiedev/disc_jockey/gui/SongListWidget.java b/src/main/java/semmiedev/disc_jockey/gui/SongListWidget.java index 26eb1ec..453ad90 100644 --- a/src/main/java/semmiedev/disc_jockey/gui/SongListWidget.java +++ b/src/main/java/semmiedev/disc_jockey/gui/SongListWidget.java @@ -38,6 +38,7 @@ public class SongListWidget extends EntryListWidget { super.setSelected(entry); } + // TODO: 6/2/2022 Add a delete icon public static class SongEntry extends Entry { private static final Identifier ICONS = new Identifier(Main.MOD_ID, "textures/gui/icons.png"); diff --git a/src/main/java/semmiedev/disc_jockey/gui/screen/DiscJockeyScreen.java b/src/main/java/semmiedev/disc_jockey/gui/screen/DiscJockeyScreen.java index f9980c8..47b082a 100644 --- a/src/main/java/semmiedev/disc_jockey/gui/screen/DiscJockeyScreen.java +++ b/src/main/java/semmiedev/disc_jockey/gui/screen/DiscJockeyScreen.java @@ -1,5 +1,6 @@ package semmiedev.disc_jockey.gui.screen; +import net.minecraft.client.gui.screen.ConfirmScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.TextFieldWidget; @@ -7,6 +8,7 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; import net.minecraft.text.MutableText; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import semmiedev.disc_jockey.Main; import semmiedev.disc_jockey.Note; import semmiedev.disc_jockey.Song; @@ -14,16 +16,22 @@ import semmiedev.disc_jockey.SongLoader; import semmiedev.disc_jockey.gui.SongListWidget; import semmiedev.disc_jockey.gui.hud.BlocksOverlay; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; -// TODO: 6/1/2022 Add a drag and drop action for songs public class DiscJockeyScreen extends Screen { private static final MutableText SELECT_SONG = Text.translatable(Main.MOD_ID+".screen.select_song"), PLAY = Text.translatable(Main.MOD_ID+".screen.play"), PLAY_STOP = Text.translatable(Main.MOD_ID+".screen.play.stop"), PREVIEW = Text.translatable(Main.MOD_ID+".screen.preview"), - PREVIEW_STOP = Text.translatable(Main.MOD_ID+".screen.preview.stop") + PREVIEW_STOP = Text.translatable(Main.MOD_ID+".screen.preview.stop"), + DROP_HINT = Text.translatable(Main.MOD_ID+".screen.drop_hint").formatted(Formatting.GRAY) ; private SongListWidget songListWidget; @@ -70,6 +78,7 @@ public class DiscJockeyScreen extends Screen { addDrawableChild(previewButton); addDrawableChild(new ButtonWidget(width / 2 + 60, height - 61, 100, 20, Text.translatable(Main.MOD_ID+".screen.blocks"), button -> { + // TODO: 6/2/2022 Add an auto build mode if (BlocksOverlay.itemStacks == null) { SongListWidget.SongEntry entry = songListWidget.getSelectedOrNull(); if (entry != null) { @@ -115,12 +124,15 @@ public class DiscJockeyScreen extends Screen { shouldFilter = true; }); addDrawableChild(searchBar); + + // TODO: 6/2/2022 Add a reload button } @Override public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { super.render(matrices, mouseX, mouseY, delta); + drawCenteredText(matrices, textRenderer, DROP_HINT, width / 2, 5, 0xFFFFFF); drawCenteredText(matrices, textRenderer, SELECT_SONG, width / 2, 20, 0xFFFFFF); } @@ -147,6 +159,35 @@ public class DiscJockeyScreen extends Screen { } } + @Override + public void filesDragged(List paths) { + String string = paths.stream().map(Path::getFileName).map(Path::toString).collect(Collectors.joining(", ")); + if (string.length() > 300) string = string.substring(0, 300)+"..."; + + client.setScreen(new ConfirmScreen(confirmed -> { + if (confirmed) { + paths.forEach(path -> { + try { + File file = path.toFile(); + + if (SongLoader.SONGS.stream().anyMatch(input -> input.fileName.equalsIgnoreCase(file.getName()))) return; + + Song song = SongLoader.loadSong(file); + if (song != null) { + Files.copy(path, Main.songsFolder.toPath().resolve(file.getName())); + SongLoader.SONGS.add(song); + } + } catch (IOException exception) { + Main.LOGGER.warn("Failed to copy song file from {} to {}", path, Main.songsFolder.toPath(), exception); + } + }); + + SongLoader.sort(); + } + client.setScreen(this); + }, Text.translatable("disc_jockey.drop_confirm"), Text.literal(string))); + } + @Override public boolean shouldPause() { return false; diff --git a/src/main/resources/assets/disc_jockey/lang/en_us.json b/src/main/resources/assets/disc_jockey/lang/en_us.json index ea99f09..6522a99 100644 --- a/src/main/resources/assets/disc_jockey/lang/en_us.json +++ b/src/main/resources/assets/disc_jockey/lang/en_us.json @@ -7,6 +7,8 @@ "disc_jockey.screen.blocks.title": "Blocks", "disc_jockey.screen.blocks": "Blocks", "disc_jockey.screen.search": "Search For Songs", + "disc_jockey.screen.drop_hint": "Drag and drop song files into this window to add them", + "disc_jockey.screen.drop_confirm": "Do you want to add the following songs to Disc Jockey?", "disc_jockey.player.invalid_note_blocks": "The Note Blocks near you are not in the correct configuration. Missing:", "disc_jockey.player.invalid_game_mode": "You can't play in %s", "disc_jockey.player.to_far": "You went to far away",