diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 74594a8ed4..71aad5f534 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -61,6 +61,7 @@ import net.runelite.client.plugins.PluginManager; import net.runelite.client.rs.ClientUpdateCheckMode; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.DrawManager; +import net.runelite.client.ui.RuneLiteSplashScreen; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.client.ui.overlay.WidgetOverlay; @@ -79,8 +80,10 @@ public class RuneLite public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles"); public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins"); public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots"); + private static final RuneLiteSplashScreen splashScreen = new RuneLiteSplashScreen(); - @Getter + + @Getter private static Injector injector; @Inject @@ -160,6 +163,7 @@ public class RuneLite final OptionParser parser = new OptionParser(); parser.accepts("developer-mode", "Enable developer tools"); parser.accepts("debug", "Show extra debugging output"); + parser.accepts("no-splash", "Do not show the splash screen"); final ArgumentAcceptingOptionSpec updateMode = parser .accepts("rs", "Select client type") @@ -213,6 +217,14 @@ public class RuneLite } }); + if (!options.has("no-splash")) + { + splashScreen.open(4); + } + + // The submessage is shown in case the connection is slow + splashScreen.setMessage("Loading client", "And checking for updates..."); + final long start = System.currentTimeMillis(); injector = Guice.createInjector(new RuneLiteModule( @@ -239,6 +251,7 @@ public class RuneLite } // Load user configuration + splashScreen.setMessage("Loading configuration"); configManager.load(); // Load the session, including saved configuration @@ -252,6 +265,7 @@ public class RuneLite // Load the plugins, but does not start them yet. // This will initialize configuration + splashScreen.setMessage("Loading plugins and patches", "Starting session..."); pluginManager.loadCorePlugins(); // Plugins have provided their config, so set default config @@ -261,9 +275,15 @@ public class RuneLite // Start client session clientSessionManager.start(); + // Load the session, including saved configuration + splashScreen.setMessage("Loading interface"); + // Initialize UI clientUI.open(this); + // Close the splash screen + splashScreen.close(); + // Initialize Discord service discordService.init(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java index 42686dae70..3be9dc053b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java @@ -27,6 +27,7 @@ package net.runelite.client.plugins.timetracking.clocks; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.google.inject.Singleton; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -34,11 +35,13 @@ import javax.inject.Inject; import javax.swing.SwingUtilities; import joptsimple.internal.Strings; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; import net.runelite.client.plugins.timetracking.TimeTrackingConfig; @Singleton +@Slf4j public class ClockManager { @Inject @@ -57,7 +60,19 @@ public class ClockManager private final List stopwatches = new ArrayList<>(); @Getter - private ClockTabPanel clockTabPanel = new ClockTabPanel(this); + private ClockTabPanel clockTabPanel; + + ClockManager() + { + try + { + SwingUtilities.invokeAndWait(() -> clockTabPanel = new ClockTabPanel(this)); + } + catch (InterruptedException | InvocationTargetException e) + { + log.error("Error constructing ClockManager", e); + } + } void addTimer() { diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 96ba6c31e6..4722b0027e 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -83,7 +83,6 @@ import net.runelite.client.input.KeyManager; import net.runelite.client.input.MouseAdapter; import net.runelite.client.input.MouseListener; import net.runelite.client.input.MouseManager; -import net.runelite.client.ui.skin.SubstanceRuneLiteLookAndFeel; import net.runelite.client.util.HotkeyListener; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.OSType; @@ -300,14 +299,7 @@ public class ClientUI { SwingUtilities.invokeAndWait(() -> { - // Set some sensible swing defaults - SwingUtil.setupDefaults(); - - // Use substance look and feel - SwingUtil.setTheme(new SubstanceRuneLiteLookAndFeel()); - - // Use custom UI font - SwingUtil.setFont(FontManager.getRunescapeFont()); + SwingUtil.setupRuneLiteLookAndFeel(); // Create main window frame = new ContainableFrame(); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/RuneLiteSplashScreen.java b/runelite-client/src/main/java/net/runelite/client/ui/RuneLiteSplashScreen.java new file mode 100644 index 0000000000..925238d58e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/RuneLiteSplashScreen.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2016-2017, Jeremy Plsek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.image.BufferedImage; +import java.io.IOException; +import javax.imageio.ImageIO; +import javax.inject.Singleton; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.SwingUtilities; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.util.SwingUtil; +import org.pushingpixels.substance.internal.SubstanceSynapse; + +/** + * This is a custom Splash Screen and does not use Java's SplashScreen class. This has helper methods to update the + * status while loading RuneLite. All public methods run non-blocking passed to the swing thread. + */ +@Slf4j +@Singleton +public class RuneLiteSplashScreen +{ + private RuneLiteProperties runeLiteProperties = new RuneLiteProperties(); + + private JFrame frame; + private JLabel messageLabel; + private JLabel subMessageLabel; + private JProgressBar progressBar; + + private int currentStep; + + /** + * This is not done in the constructor in order to avoid processing in case the user chooses to not load + * the splash screen. + * @param estimatedSteps steps until completion, used for the progress bar + */ + private void initLayout(final int estimatedSteps) + { + SwingUtil.setupRuneLiteLookAndFeel(); + + // init fields with updated swing look and feel + frame = new JFrame("RuneLitePlus Loading"); + messageLabel = new JLabel("Loading..."); + subMessageLabel = new JLabel(); + progressBar = new JProgressBar(0, estimatedSteps); + + // frame setup + frame.setSize(220, 290); + frame.setLocationRelativeTo(null); + frame.setUndecorated(true); + + // main panel setup + final JPanel panel = new JPanel(); + // To reduce substance's colorization (tinting) + panel.putClientProperty(SubstanceSynapse.COLORIZATION_FACTOR, 1.0); + panel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + final GridBagLayout layout = new GridBagLayout(); + layout.columnWeights = new double[]{1}; + layout.rowWeights = new double[]{1, 0, 0, 1, 0, 1}; + panel.setLayout(layout); + + // logo + synchronized (ImageIO.class) + { + try + { + final BufferedImage logo = ImageIO.read(RuneLiteSplashScreen.class.getResourceAsStream("/runeliteplus.png")); + frame.setIconImage(logo); + + final BufferedImage logoTransparent = ImageIO.read(RuneLiteSplashScreen.class.getResourceAsStream("/runeliteplus_transparent.png")); + final GridBagConstraints logoConstraints = new GridBagConstraints(); + logoConstraints.anchor = GridBagConstraints.SOUTH; + panel.add(new JLabel(new ImageIcon(logoTransparent.getScaledInstance(96, 96, Image.SCALE_SMOOTH))), logoConstraints); + } + catch (IOException e) + { + log.warn("Error loading logo", e); + } + } + + // runelite title + final JLabel title = new JLabel("RuneLitePlus"); + final GridBagConstraints titleConstraints = new GridBagConstraints(); + titleConstraints.gridy = 1; + panel.add(title, titleConstraints); + + // version + final JLabel version = new JLabel("Version " + runeLiteProperties.getVersion()); + version.setFont(FontManager.getRunescapeSmallFont()); + version.setForeground(version.getForeground().darker()); + final GridBagConstraints versionConstraints = new GridBagConstraints(); + versionConstraints.gridy = 2; + panel.add(version, versionConstraints); + + // progressbar + final GridBagConstraints progressConstraints = new GridBagConstraints(); + progressConstraints.insets = new Insets(0, 30, 5, 30); + progressConstraints.fill = GridBagConstraints.HORIZONTAL; + progressConstraints.anchor = GridBagConstraints.SOUTH; + progressConstraints.gridy = 3; + panel.add(progressBar, progressConstraints); + + // main message + messageLabel.setFont(FontManager.getRunescapeSmallFont()); + final GridBagConstraints messageConstraints = new GridBagConstraints(); + messageConstraints.gridy = 4; + panel.add(messageLabel, messageConstraints); + + // alternate message + subMessageLabel.setForeground(subMessageLabel.getForeground().darker()); + subMessageLabel.setFont(FontManager.getRunescapeSmallFont()); + final GridBagConstraints altConstrains = new GridBagConstraints(); + altConstrains.anchor = GridBagConstraints.NORTH; + altConstrains.gridy = 5; + panel.add(subMessageLabel, altConstrains); + + frame.setContentPane(panel); + } + + private boolean notActive() + { + return frame == null || !frame.isDisplayable(); + } + + /** + * Close/dispose of the splash screen + */ + public void close() + { + SwingUtilities.invokeLater(() -> + { + if (notActive()) + { + return; + } + + frame.dispose(); + }); + } + + /** + * Set the splash screen to be visible. + * @param estimatedSteps steps until completion, used for the progress bar + */ + public void open(final int estimatedSteps) + { + SwingUtilities.invokeLater(() -> + { + initLayout(estimatedSteps); + frame.setVisible(true); + }); + } + + /** + * Set a loading message. The subMessage will also be removed. + * @param message The main message. It will automatically append an ellipsis. + */ + public void setMessage(final String message) + { + setMessage(message, " "); + } + + /** + * Set a loading message. + * @param message The main message. It will automatically append an ellipsis. + * @param subMessage A separate alternate title. + */ + public void setMessage(final String message, final String subMessage) + { + SwingUtilities.invokeLater(() -> + { + if (notActive()) + { + return; + } + + messageLabel.setText(message + "..."); + subMessageLabel.setText(subMessage); + progressBar.setValue(++currentStep); + }); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java index 39fc738f1f..e8aaef79a6 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -26,6 +26,7 @@ package net.runelite.client.util; import java.awt.AWTException; import java.awt.Color; +import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Image; @@ -52,11 +53,15 @@ import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE; +import javax.swing.border.EmptyBorder; import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.basic.BasicProgressBarUI; import lombok.extern.slf4j.Slf4j; import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.components.CustomScrollBarUI; +import net.runelite.client.ui.skin.SubstanceRuneLiteLookAndFeel; import org.pushingpixels.substance.internal.SubstanceSynapse; /** @@ -65,6 +70,8 @@ import org.pushingpixels.substance.internal.SubstanceSynapse; @Slf4j public class SwingUtil { + private static boolean lookAndFeelIsSet = false; + /** * Sets some sensible defaults for swing. * IMPORTANT! Needs to be called before main frame creation @@ -87,6 +94,14 @@ public class SwingUtil UIManager.put("FormattedTextField.selectionForeground", Color.WHITE); UIManager.put("TextArea.selectionBackground", ColorScheme.BRAND_ORANGE_TRANSPARENT); UIManager.put("TextArea.selectionForeground", Color.WHITE); + UIManager.put("ProgressBar.background", ColorScheme.BRAND_ORANGE_TRANSPARENT.darker()); + UIManager.put("ProgressBar.foreground", ColorScheme.BRAND_ORANGE); + UIManager.put("ProgressBar.selectionBackground", ColorScheme.BRAND_ORANGE); + UIManager.put("ProgressBar.selectionForeground", Color.BLACK); + UIManager.put("ProgressBar.border", new EmptyBorder(0, 0, 0, 0)); + UIManager.put("ProgressBar.verticalSize", new Dimension(12, 10)); + UIManager.put("ProgressBar.horizontalSize", new Dimension(10, 12)); + UIManager.put("ProgressBarUI", BasicProgressBarUI.class.getName()); // Do not render shadows under popups/tooltips. // Fixes black boxes under popups that are above the game applet. @@ -277,4 +292,24 @@ public class SwingUtil navigationButton.setOnSelect(button::doClick); return button; } + + /** + * Sets up the RuneLite look and feel. Checks to see if the look and feel + * was already set up before running in case the splash screen has already + * set up the theme. + * This must be run inside the Swing Event Dispatch thread. + */ + public static void setupRuneLiteLookAndFeel() + { + if (!lookAndFeelIsSet) + { + lookAndFeelIsSet = true; + // Set some sensible swing defaults + SwingUtil.setupDefaults(); + // Use substance look and feel + SwingUtil.setTheme(new SubstanceRuneLiteLookAndFeel()); + // Use custom UI font + SwingUtil.setFont(FontManager.getRunescapeFont()); + } + } } diff --git a/runelite-client/src/main/resources/runelite_transparent.png b/runelite-client/src/main/resources/runelite_transparent.png new file mode 100644 index 0000000000..c60bc7ee66 Binary files /dev/null and b/runelite-client/src/main/resources/runelite_transparent.png differ diff --git a/runelite-client/src/main/resources/runeliteplus.png b/runelite-client/src/main/resources/runeliteplus.png new file mode 100644 index 0000000000..e4057a8084 Binary files /dev/null and b/runelite-client/src/main/resources/runeliteplus.png differ diff --git a/runelite-client/src/main/resources/runeliteplus_transparent.png b/runelite-client/src/main/resources/runeliteplus_transparent.png new file mode 100644 index 0000000000..60565f1b40 Binary files /dev/null and b/runelite-client/src/main/resources/runeliteplus_transparent.png differ