diff --git a/.idea/artifacts/TAL_jar.xml b/.idea/artifacts/TAL_jar.xml new file mode 100644 index 0000000..3e03979 --- /dev/null +++ b/.idea/artifacts/TAL_jar.xml @@ -0,0 +1,10 @@ + + + $PROJECT_DIR$/out/artifacts/TAL_jar + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/libRefTools.xml b/.idea/libraries/libRefTools.xml index c38d72c..2ce2aba 100644 --- a/.idea/libraries/libRefTools.xml +++ b/.idea/libraries/libRefTools.xml @@ -4,8 +4,6 @@ - - - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 2404a4c..59225cf 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,4 +9,5 @@ + \ No newline at end of file diff --git a/TAL.iml b/TAL.iml index f0db93c..f6ccf03 100644 --- a/TAL.iml +++ b/TAL.iml @@ -7,7 +7,6 @@ - @@ -17,5 +16,6 @@ + \ No newline at end of file diff --git a/libs/libRefTools.jar b/libs/libRefTools.jar index 9fb9834..02b5c30 100644 Binary files a/libs/libRefTools.jar and b/libs/libRefTools.jar differ diff --git a/src/Launcher/Main.java b/src/Launcher/Main.java index 86ff329..de7f54f 100644 --- a/src/Launcher/Main.java +++ b/src/Launcher/Main.java @@ -10,48 +10,71 @@ favour and pour yourself some nice Jack Daniels. You deserve it if you're going package Launcher; +import Launcher.net.Updater; import com.tofvesson.reflection.SafeReflection; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.application.Application; -import javafx.fxml.FXMLLoader; +import javafx.scene.Node; import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.Tab; -import javafx.scene.control.TextField; +import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.stage.StageStyle; -import java.net.URL; +import java.io.File; import com.tofvesson.async.*; +import javafx.util.Duration; public class Main extends Application { - public static final URL mainLauncher = Main.class.getResource("../assets/layout/main.fxml"); // Launcher body + // Semantic versioning system data + public static final String semVerDevState = "PreDev"; // Development stage + public static final int semVerMajor = 0; // Major version + public static final int semVerMinor = 2; // Minor version + public static final int semVerPatch = 2; // Patch version private double xOffset = 0, yOffset = 0; // Offsets for dragging - private Button exit, min, Home_btn, Modpack_btn, Settings_btn, Instance_btn; // Define buttons + private static String[] args; + private Button exit, min, Home_btn, Modpack_btn, Settings_btn, Instance_btn; // Define buttons private ImageView icon; private TextField Search_modpacks; private Image appIcon; private Rectangle dragBar; // Draggable top bar private Pane root, tab; - private Tabs activeTab = Tabs.Home; + private Node activeTab, settings_activeTab; Async stringUpdater; @Override public void start(Stage primaryStage) throws Exception{ - primaryStage.initStyle(StageStyle.UNDECORATED); // Remove ugly trash + primaryStage.initStyle(StageStyle.UNDECORATED); - root = FXMLLoader.load(mainLauncher); + if(args.length<2 || !args[1].equals("false")){ + Stage d = new Stage(); + Timeline t = new Timeline(); + t.getKeyFrames().add(new KeyFrame(Duration.millis(1), event ->{ d.close(); primaryStage.show(); })); + d.initStyle(StageStyle.UNDECORATED); + Pane n = (Pane) Tabs.load("dialog_update"); + d.setScene(new Scene(n)); + d.show(); + Thread t1 = new Thread(()->{ + try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } + Updater.getInstance(t); + }); + t1.setDaemon(true); + t1.start(); + } else primaryStage.show(); // Remove ugly trash + + root = (Pane) Tabs.load("main"); // Load via layout loader + ((Label)root.lookup("#version")).setText(((Label) root.lookup("#version")) // Dynamically set version label + .getText().replace("$v", semVerDevState+"-"+semVerMajor+"."+semVerMinor+"."+semVerPatch)); // Use variables to define version primaryStage.setTitle("Team-Avion Launcher [WIP]"); primaryStage.setScene(new Scene(root, 900, 500)); - primaryStage.show(); primaryStage.getIcons().clear(); - primaryStage.getIcons().add(appIcon = new Image(getClass().getResourceAsStream("../assets/icons/app.png"))); - + primaryStage.getIcons().add(appIcon = new Image(getClass().getResourceAsStream("/assets/icons/app.png"))); // Field initialization exit = (Button) root.lookup("#exit"); @@ -70,45 +93,66 @@ public class Main extends Application { Search_modpacks = (TextField) root.lookup("#search-modpacks"); - // Infrastructural navigation exit.setOnMouseClicked(event -> primaryStage.close()); // Closes the program if exit button is clicked min.setOnMouseClicked(event -> primaryStage.setIconified(true)); // Minimizes the program if minimize button is clicked Home_btn.setOnMouseClicked(event ->{ - if(activeTab!=Tabs.Home){ - (activeTab=Tabs.Home).switchTab(tab); + if(!activeTab.equals(Home_btn)){ + updateTabSelection(Home_btn, TabType.MAIN); + Tabs.switchTab("home", tab); } }); // Sets the active tab to the home tab unless it's already active Modpack_btn.setOnMouseClicked(event ->{ - if(activeTab!=Tabs.Modpacks){ + if(!activeTab.equals(Modpack_btn)){ + updateTabSelection(Modpack_btn, TabType.MAIN); + Tabs.switchTab("modpacks", tab); if(stringUpdater!=null && stringUpdater.isAlive()) stringUpdater.cancel(); - (activeTab=Tabs.Modpacks).switchTab(tab); // Sets the active tab to the modpacks tab unless it's already active - - //TODO: Create a dynamic updating string from the input ( Text Field ) *-* Done *-* - - - stringUpdater = new Async(SafeReflection.getFirstMethod(Main.class, "detectStringUpdate"), Tabs.Modpacks.loaded.lookup("#search-modpacks")); - + stringUpdater = new Async(SafeReflection.getFirstMethod(Main.class, "detectStringUpdate"), Tabs.load("modpacks").lookup("#search-modpacks")); } }); Instance_btn.setOnMouseClicked(event -> { - if(activeTab!=Tabs.Instance){ - (activeTab = Tabs.Instance).switchTab(tab); + if(!activeTab.equals(Instance_btn)){ + updateTabSelection(Instance_btn, TabType.MAIN); + Tabs.switchTab("instance", tab); + Tabs.load("instance").lookup("#Launch-VM").setOnMouseClicked(event1 -> { + }); } }); Settings_btn.setOnMouseClicked(event ->{ - if(activeTab!=Tabs.Settings){ - (activeTab=Tabs.Settings).switchTab(tab); // Sets the active tab to the settings tab unless it's already active + if(!activeTab.equals(Settings_btn)){ + updateTabSelection(Settings_btn, TabType.MAIN); + Node n = Tabs.switchTab("settings", tab); // Sets the active tab to the settings tab unless it's already active + if(settings_activeTab==null) settings_activeTab = n.lookup("#Settings-Gen-btn"); // First time stuff + + n.lookup("#Settings-Gen-btn").setOnMouseClicked(event1 -> { + // Generic Settings Sub-tab + if(!settings_activeTab.getId().equals(n.lookup("#Settings-Gen-btn").getId())){ // Use id to identify layouts + updateTabSelection(n.lookup("#Settings-Gen-btn"), TabType.SETTINGS); + Node genericLayout = Tabs.switchTab("settings_generic", (Pane) n.lookup("#Settings-Pane")); + + + } + }); + + n.lookup("#Settings-Mine-btn").setOnMouseClicked(event1 -> { + // Minecraft Settings Sub-tab + if(!settings_activeTab.getId().equals(n.lookup("#Settings-Mine-btn").getId())){ // Use id to identify layouts + updateTabSelection(n.lookup("#Settings-Mine-btn"), TabType.SETTINGS); + Node minecraftLayout = Tabs.switchTab("settings_minecraft", (Pane) n.lookup("#Settings-Pane")); + + } + }); + + Tabs.switchTab(settings_activeTab.getId().equals("Settings-Gen-btn") ? "settings_generic" : "settings_minecraft", (Pane) n.lookup("#Settings-Pane")); } }); - // Drag dragBar.setOnMousePressed(event -> { xOffset = event.getSceneX(); @@ -119,20 +163,42 @@ public class Main extends Application { primaryStage.setY(event.getScreenY() - yOffset); }); - // Set up default layout - Tabs.Home.switchTab(tab); + activeTab = Home_btn; // Update selected tab + Tabs.switchTab("home", tab); icon.setImage(appIcon); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception{ + Main.args = args; + if (args.length > 0) { + File f = new File(args[0]); + if (f.isFile()) f.delete(); // Delete previous jar + } launch(args); } + /** + * Search for packs with an 80% match compared to detected string. + * @param toRead TextField to read from. + */ public static void detectStringUpdate(TextField toRead){ String s = ""; while(true) if(!s.equals(toRead.getText())) System.out.println(s = toRead.getText()); } + void updateTabSelection(Node newTab, TabType t){ + Node n = t==TabType.MAIN?activeTab:settings_activeTab; + n.getStyleClass().remove("selected"); + n.getStyleClass().add("tab"); + if(t==TabType.MAIN) activeTab = newTab; + else settings_activeTab = newTab; + newTab.getStyleClass().remove("tab"); + newTab.getStyleClass().add("selected"); + } + + enum TabType{ + SETTINGS, MAIN + } } diff --git a/src/Launcher/Tabs.java b/src/Launcher/Tabs.java index d82baba..45b90ee 100644 --- a/src/Launcher/Tabs.java +++ b/src/Launcher/Tabs.java @@ -1,37 +1,73 @@ package Launcher; +import com.tofvesson.collections.ShiftingList; import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; +import javafx.scene.Node; import javafx.scene.layout.Pane; +import javafx.util.Pair; import java.io.IOException; import java.net.URL; -public enum Tabs { +@SuppressWarnings("unused") +public class Tabs { - Modpacks(Tabs.class.getResource("../assets/layout/modpacks.fxml")), - Home(Tabs.class.getResource("../assets/layout/home.fxml")), - Settings(Tabs.class.getResource("../assets/layout/settings.fxml")), - Instance(Tabs.class.getResource("../assets/layout/instance.fxml")); + private static final ShiftingList> loaded = new ShiftingList<>(35); // Memory-efficient list of loaded files /** - * Url referencing xml. + * Loads layout from file in layout assets folder. If layout is already loaded, + * Tabs won't bother with reloading and will instead return the cached version. + * @param fileName Name of fxml file to load. (Must be located in /assets/layout/ folder or name must define name subfolder in layouts folder) + * @return Generified object referring to loaded resource. */ - public final URL url; - - /** - * Loaded layout - */ - public final Parent loaded; - - Tabs(URL url){ - this.url = url; - Parent p = null; - try { p = FXMLLoader.load(url); } catch (IOException e) { e.printStackTrace(); } - loaded = p; + public static Node load(String fileName){ + if(!fileName.endsWith(".fxml")) fileName+=".fxml"; + URL file = Main.class.getResource("/assets/layout/"+fileName); + try { + final boolean[] b = {false}; + loaded.stream().filter(p -> p.getKey().equals(file)).forEach(p->b[0]=true); + if(!b[0]) loaded.add(new Pair<>(file, FXMLLoader.load(file))); // Load file if it isn't already loaded + final Node[] p1 = new Node[]{new Pane()}; + loaded.stream().filter(p->p.getKey().equals(file)).forEach(p->p1[0]=p.getValue()); + return p1[0]; + } catch (IOException e) { + e.printStackTrace(); + return new Pane(); // Returns empty layout if all else fails + } } - public void switchTab(Pane holder){ - holder.getChildren().clear(); - holder.getChildren().add(loaded); + /** + * Switches the currently loaded tab in holder pane. Removes current children from holder and adds new tab instead. + * If holder already contains layout, method simply returns the loaded resource. + * @param newTabName Name of file containing the new tab data. + * @param holder Pane where tab should be loaded to. + * @return Generified object referring to loaded resource. + */ + public static Node switchTab(String newTabName, Pane holder){ + Node n = load(newTabName); + if(!holder.getChildren().contains(n)) { + holder.getChildren().clear(); + holder.getChildren().add(n); + } + return n; + } + + /** + * Forces unloading of resource to free up resources and/or clear data. + * @param fileName Name of resource to unload. + */ + public static void unloadTab(String fileName){ + if(!fileName.endsWith(".fxml")) fileName+=".fxml"; + URL file = Main.class.getResource("/assets/layout/"+fileName); + loaded.stream().filter(p->p.getKey().equals(file)).forEach(loaded::remove); + } + + /** + * Forces unloading and the subsequent loading of a layout resource. + * @param fileName Name of resource to reload. + * @return Newly loaded layout. + */ + public static Node reloadTab(String fileName){ + unloadTab(fileName); + return load(fileName); } } diff --git a/src/Launcher/minecraft/Launcher.java b/src/Launcher/minecraft/Launcher.java new file mode 100644 index 0000000..9e31fcb --- /dev/null +++ b/src/Launcher/minecraft/Launcher.java @@ -0,0 +1,5 @@ +package Launcher.minecraft; + +public class Launcher { + +} diff --git a/src/Launcher/net/Updater.java b/src/Launcher/net/Updater.java index 0b8d339..07a0b37 100644 --- a/src/Launcher/net/Updater.java +++ b/src/Launcher/net/Updater.java @@ -1,35 +1,111 @@ package Launcher.net; -import java.io.IOException; +import Launcher.Main; +import com.tofvesson.async.Async; +import com.tofvesson.reflection.SafeReflection; +import javafx.animation.Timeline; +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.net.MalformedURLException; import java.net.URL; -import java.net.URLConnection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import static Launcher.Main.semVerMajor; +import static Launcher.Main.semVerMinor; +import static Launcher.Main.semVerPatch; /** * Simple thing for updating launcher */ public class Updater { - private static Updater instance; - private URLConnection conn; + private static volatile Updater instance; + private static Async setup; + public static final Pattern version = Pattern.compile("(?s).*?(\\d)\\.(\\d)\\.(\\d).*?"); // Pattern to match when finding refs + private HttpsURLConnection conn; + public static final URL updateURL; - private Updater(String URL) throws IOException { - conn = new URL(URL).openConnection(); + static { + URL u = null; + try { u = new URL("https://github.com/GabrielTofvesson/TeamAvionLauncher/releases"); } catch (MalformedURLException e) { e.printStackTrace(); } + updateURL = u; } - public void downloadStuff(){ - //TODO: Download lots of stuff - } + private Updater(Timeline t){ + try { + conn = (HttpsURLConnection) updateURL.openConnection(); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setRequestMethod("GET"); + conn.setRequestProperty("User-Agent", "Mozilla/5.0"); + if(conn.getResponseCode()!=200){ + t.play(); + return; // Can't get update site + } + conn.connect(); - public void downloadMoreStuff(){ - //TODO: Download more stuff - } - public void downloadEvenMoreStuff(){ - //TODO: Download even more stuff - } + BufferedReader in = new BufferedReader(new InputStreamReader( + conn.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) response.append(inputLine); + in.close(); - public static Updater getInstance(String url) throws IOException { - if(instance==null) instance = new Updater(url); - return instance; + Matcher m = version.matcher(response.toString()); + String downloadLink = ""; + int semMajor = semVerMajor, semMinor = semVerMinor, semPatch = semVerPatch; + while(m.find()){ + int semMaj = Integer.parseInt(m.group(1)), + semMin = Integer.parseInt(m.group(2)), + semPat = Integer.parseInt(m.group(3)); + if(semMaj < semMajor || (semMaj==semMajor && semMin(SafeReflection.getFirstConstructor(Updater.class), t); } + + public static Updater getInstance(Timeline t) { + if(setup==null) checkUpdate(t); + return instance==null?instance=setup.await():instance; // Await async creation } } diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..33021cc --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Launcher.Main + diff --git a/src/assets/layout/dialog_update.fxml b/src/assets/layout/dialog_update.fxml new file mode 100644 index 0000000..79fe1e9 --- /dev/null +++ b/src/assets/layout/dialog_update.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/src/assets/layout/home.fxml b/src/assets/layout/home.fxml index b896070..2dbf270 100644 --- a/src/assets/layout/home.fxml +++ b/src/assets/layout/home.fxml @@ -5,15 +5,13 @@ - - + - - - diff --git a/src/assets/layout/instance.fxml b/src/assets/layout/instance.fxml index b37f150..34ccae5 100644 --- a/src/assets/layout/instance.fxml +++ b/src/assets/layout/instance.fxml @@ -1,18 +1,24 @@ + - - + - -