From 8db4f83fa38cb03e30c5833a31d27c76d3ccf525 Mon Sep 17 00:00:00 2001 From: Owain van Brakel Date: Sun, 11 Aug 2019 05:13:14 +0200 Subject: [PATCH] client: update splashscreen --- .../java/net/runelite/client/RuneLite.java | 46 ++- .../client/plugins/PluginManager.java | 16 +- .../client/plugins/info/InfoPanel.java | 13 +- .../java/net/runelite/client/ui/ClientUI.java | 2 +- .../client/ui/RuneLiteSplashScreen.java | 322 ++++++------------ .../client/ui/components/InfoPanel.java | 187 ++++++++++ .../client/ui/components/MessagePanel.java | 155 +++++++++ .../net/runelite/client/util/LinkBrowser.java | 50 +++ .../net/runelite/client/util/SwingUtil.java | 140 ++++++++ 9 files changed, 669 insertions(+), 262 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/components/InfoPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/components/MessagePanel.java 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 94298a2a62..eca00e25f7 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -80,13 +80,13 @@ import org.slf4j.LoggerFactory; @Slf4j public class RuneLite { - public static final String PLUS_VERSION = "2.1.1.0"; public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".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"); public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs"); - private static final RuneLiteSplashScreen splashScreen = new RuneLiteSplashScreen(); + private static final File LOG_FILE = new File(LOGS_DIR, "client.log"); + private static final RuneLiteProperties PROPERTIES = new RuneLiteProperties(); public static boolean allowPrivateServer = false; @Getter @@ -260,6 +260,13 @@ public class RuneLite } } + if (!options.has("no-splash")) + { + RuneLiteSplashScreen.init(); + } + + RuneLiteSplashScreen.stage(0, "Initializing client"); + PROFILES_DIR.mkdirs(); if (options.has("debug")) @@ -277,14 +284,8 @@ public class RuneLite } }); - if (!options.has("no-splash")) - { - splashScreen.open(4); - } - // The submessage is shown in case the connection is slow - splashScreen.setMessage("Starting RuneLite Injector"); - splashScreen.setSubMessage(" "); + RuneLiteSplashScreen.stage(.2, "Starting RuneLitePlus injector"); final long start = System.currentTimeMillis(); @@ -293,7 +294,6 @@ public class RuneLite true)); injector.getInstance(RuneLite.class).start(); - splashScreen.setProgress(1, 5); final long end = System.currentTimeMillis(); final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean(); final long uptime = rb.getUptime(); @@ -312,14 +312,13 @@ public class RuneLite } // Load user configuration - splashScreen.setMessage("Loading configuration"); + + RuneLiteSplashScreen.stage(.57, "Loading user config"); configManager.load(); // Load the session, including saved configuration sessionManager.loadSession(); - splashScreen.setProgress(2, 5); - - splashScreen.setMessage("Loading plugins"); + RuneLiteSplashScreen.stage(.58, "Loading session data"); // Begin watching for new plugins pluginManager.watch(); @@ -330,20 +329,15 @@ public class RuneLite // Load the plugins, but does not start them yet. // This will initialize configuration pluginManager.loadCorePlugins(); + RuneLiteSplashScreen.stage(.70, "Finalizing configuration"); // Plugins have provided their config, so set default config // to main settings pluginManager.loadDefaultPluginConfiguration(); - splashScreen.setProgress(3, 5); - splashScreen.setMessage("Starting Session"); // Start client session + RuneLiteSplashScreen.stage(.80, "Starting core interface"); clientSessionManager.start(); - splashScreen.setProgress(4, 5); - - // Load the session, including saved configuration - splashScreen.setMessage("Loading interface"); - splashScreen.setProgress(5, 5); // Initialize UI clientUI.init(this); @@ -371,11 +365,6 @@ public class RuneLite overlayManager.add(arrowMinimapOverlay.get()); } - // Close the splash screen - splashScreen.close(); - - clientUI.show(); - // Start plugins pluginManager.startCorePlugins(); @@ -386,6 +375,11 @@ public class RuneLite { scheduler.registerObject(modelOutlineRenderer.get()); } + + // Close the splash screen + RuneLiteSplashScreen.close(); + + clientUI.show(); } public void shutdown() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index e970dd53d7..11f99f372c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -54,6 +54,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; @@ -74,6 +75,7 @@ import net.runelite.client.events.SessionOpen; import net.runelite.client.task.Schedule; import net.runelite.client.task.ScheduledMethod; import net.runelite.client.task.Scheduler; +import net.runelite.client.ui.RuneLiteSplashScreen; import net.runelite.client.util.GameEventManager; @Singleton @@ -216,6 +218,8 @@ public class PluginManager public void startCorePlugins() { List scannedPlugins = new ArrayList<>(plugins); + int loaded = 0; + for (Plugin plugin : scannedPlugins) { try @@ -227,12 +231,17 @@ public class PluginManager log.warn("Unable to start plugin {}. {}", plugin.getClass().getSimpleName(), ex); plugins.remove(plugin); } + + loaded++; + + RuneLiteSplashScreen.stage(.80, 1, "Starting plugins", loaded, scannedPlugins.size()); } } @SuppressWarnings("unchecked") List scanAndInstantiate(ClassLoader classLoader, String packageName) throws IOException { + RuneLiteSplashScreen.stage(.59, "Loading plugins"); MutableGraph> graph = GraphBuilder .directed() .build(); @@ -295,6 +304,7 @@ public class PluginManager List>> sortedPlugins = topologicalGroupSort(graph); sortedPlugins = Lists.reverse(sortedPlugins); + AtomicInteger loaded = new AtomicInteger(); final long start = System.currentTimeMillis(); @@ -312,13 +322,17 @@ public class PluginManager try { plugin = instantiate(scannedPlugins, (Class) pluginClazz); + scannedPlugins.add(plugin); } catch (PluginInstantiationException e) { log.warn("Error instantiating plugin!", e); return; } - scannedPlugins.add(plugin); + + loaded.getAndIncrement(); + + RuneLiteSplashScreen.stage(.60, .70, "Loading plugins", loaded.get(), scannedPlugins.size()); }))); curGroup.forEach(future -> { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java index 3a200eb840..6d599a7a39 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -29,13 +29,11 @@ import com.google.inject.Inject; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; -import java.awt.Desktop; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; -import java.io.IOException; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; import javax.inject.Singleton; @@ -184,16 +182,7 @@ public class InfoPanel extends PluginPanel */ private JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, File dir) { - return buildLinkPanel(icon, topText, bottomText, () -> - { - try - { - Desktop.getDesktop().open(dir); - } - catch (IOException ex) - { - } - }); + return buildLinkPanel(icon, topText, bottomText, () -> LinkBrowser.openLocalFile(dir)); } /** 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 75e0ae4424..550a9c52f6 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 @@ -531,7 +531,7 @@ public class ClientUI if (client != null && !(client instanceof Client)) { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame, - "RuneLite has not yet been updated to work with the latest\n" + "RuneLitePlus has not yet been updated to work with the latest\n" + "game update, it will work with reduced functionality until then.", "RuneLite is outdated", INFORMATION_MESSAGE)); } 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 index 67f52ded62..38efb3b6dd 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/RuneLiteSplashScreen.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/RuneLiteSplashScreen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017, Jeremy Plsek + * Copyright (c) 2019, TheStonedTurtle * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,247 +24,125 @@ */ package net.runelite.client.ui; -import java.awt.Color; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; -import javax.imageio.ImageIO; -import javax.inject.Singleton; -import javax.swing.ImageIcon; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.lang.reflect.InvocationTargetException; import javax.swing.JFrame; -import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingUtilities; -import javax.swing.plaf.basic.BasicProgressBarUI; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.client.util.SwingUtil; -import org.pushingpixels.substance.internal.SubstanceSynapse; +import net.runelite.client.ui.components.InfoPanel; +import net.runelite.client.ui.components.MessagePanel; +import net.runelite.client.util.ImageUtil; -/** - * 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 +public class RuneLiteSplashScreen extends JFrame { - private static final String RUNELITE_VERSION = "runelite.version"; - private static final String RUNELITE_PLUS_VERSION = "runelite.plus.version"; - private static final String RUNELITE_PLUS_DATE = "runelite.plus.builddate"; + private static RuneLiteSplashScreen INSTANCE; + public static final Dimension FRAME_SIZE = new Dimension(600, 350); - private JFrame frame; - private final JPanel panel = new JPanel(); - private JLabel messageLabel; - private JLabel subMessageLabel; - private final JProgressBar progressBar = new JProgressBar(); + @Getter + private final MessagePanel messagePanel = new MessagePanel(); - private final Properties properties = new Properties(); - - public RuneLiteSplashScreen() + private RuneLiteSplashScreen() { - try (InputStream in = getClass().getResourceAsStream("/runelite.plus.properties")) + this.setTitle("RuneLitePlus"); + this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + this.setSize(FRAME_SIZE); + this.setLayout(new BorderLayout()); + this.setUndecorated(true); + this.setIconImage(ImageUtil.getResourceStreamFromClass(RuneLiteSplashScreen.class, "/runeliteplus.png")); + + final JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setPreferredSize(RuneLiteSplashScreen.FRAME_SIZE); + + panel.add(new InfoPanel(), BorderLayout.EAST); + panel.add(messagePanel, BorderLayout.WEST); + + this.setContentPane(panel); + pack(); + + this.setLocationRelativeTo(null); + this.setVisible(true); + } + + private void setBarText(final String text) + { + final JProgressBar bar = messagePanel.getBar(); + bar.setString(text); + bar.setStringPainted(text != null); + bar.revalidate(); + bar.repaint(); + } + + private void setMessage(final String msg, final double value) + { + messagePanel.getBarLabel().setText(msg); + messagePanel.getBar().setMaximum(1000); + messagePanel.getBar().setValue((int) (value * 1000)); + setBarText(null); + + this.getContentPane().revalidate(); + this.getContentPane().repaint(); + } + + public static void init() + { + try { - properties.load(in); + SwingUtilities.invokeAndWait(() -> + { + if (INSTANCE != null) + { + return; + } + + try + { + INSTANCE = new RuneLiteSplashScreen(); + } + catch (Exception e) + { + log.warn("Unable to start splash screen", e); + } + }); } - catch (IOException ex) + catch (InterruptedException | InvocationTargetException bs) { - log.warn("unable to load propertries", ex); + throw new RuntimeException(bs); } } - /** - * 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) + public static void close() { - SwingUtil.setupRuneLiteLookAndFeel(); - - // init fields with updated swing look and feel - frame = new JFrame("RuneLitePlus Loading"); - messageLabel = new JLabel("Loading..."); - subMessageLabel = new JLabel(); - progressBar.setUI(new BasicProgressBarUI()); - progressBar.setMinimum(0); - progressBar.setMaximum(estimatedSteps); - - // frame setup - frame.setSize(220, 290); - frame.setLocationRelativeTo(null); - frame.setUndecorated(true); - - // main panel setup - // 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, 0, 1}; - panel.setLayout(layout); - - // logo - synchronized (ImageIO.class) + SwingUtilities.invokeLater(() -> { - try + if (INSTANCE == null) { - final BufferedImage logo = ImageIO.read(RuneLiteSplashScreen.class.getResourceAsStream("/runeliteplus.png")); - frame.setIconImage(logo); + return; + } - 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); - } + INSTANCE.setVisible(false); + INSTANCE.dispose(); + INSTANCE = null; + }); + } + + public static void stage(double startProgress, double endProgress, + String progressText, int done, int total) + { + String progress = done + " / " + total; + stage(startProgress + ((endProgress - startProgress) * done / total), progressText + " " + progress); + } + + public static void stage(double overallProgress, String progressText) + { + if (INSTANCE != null) + { + INSTANCE.setMessage(progressText, overallProgress); } - - // 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("RuneLite Version : " + properties.getProperty(RUNELITE_VERSION)); - version.setForeground(Color.GREEN); - version.setFont(FontManager.getRunescapeSmallFont()); - version.setForeground(version.getForeground().darker()); - final GridBagConstraints versionConstraints = new GridBagConstraints(); - versionConstraints.gridy = 2; - panel.add(version, versionConstraints); - - // version - final JLabel litVersion = new JLabel("Plus Version : " + properties.getProperty(RUNELITE_PLUS_VERSION)); - litVersion.setForeground(Color.GREEN); - litVersion.setFont(FontManager.getRunescapeSmallFont()); - litVersion.setForeground(litVersion.getForeground().darker()); - final GridBagConstraints litVersionConstraints = new GridBagConstraints(); - litVersionConstraints.gridy = 3; - panel.add(litVersion, litVersionConstraints); - - // build date - final JLabel litBuildDate = new JLabel("Build date : " + properties.getProperty(RUNELITE_PLUS_DATE)); - litBuildDate.setForeground(Color.GREEN); - litBuildDate.setFont(FontManager.getRunescapeSmallFont()); - litBuildDate.setForeground(litBuildDate.getForeground().darker()); - final GridBagConstraints litBuildDateConstraints = new GridBagConstraints(); - litBuildDateConstraints.gridy = 4; - panel.add(litBuildDate, litBuildDateConstraints); - - - // progressbar - final GridBagConstraints progressConstraints = new GridBagConstraints(); - progressConstraints.fill = GridBagConstraints.HORIZONTAL; - progressConstraints.anchor = GridBagConstraints.SOUTH; - progressConstraints.gridy = 5; - panel.add(progressBar, progressConstraints); - - // main message - messageLabel.setFont(FontManager.getRunescapeSmallFont()); - final GridBagConstraints messageConstraints = new GridBagConstraints(); - messageConstraints.gridy = 6; - panel.add(messageLabel, messageConstraints); - - // alternate message - final GridBagConstraints subMessageConstraints = new GridBagConstraints(); - subMessageLabel.setForeground(subMessageLabel.getForeground().darker()); - subMessageLabel.setFont(FontManager.getRunescapeSmallFont()); - subMessageConstraints.gridy = 7; - panel.add(subMessageLabel, subMessageConstraints); - - 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); - }); - } - - public void setMessage(final String message) - { - SwingUtilities.invokeLater(() -> - { - if (notActive()) - { - return; - } - messageLabel.setText(message); - }); - } - - public void setSubMessage(final String subMessage) - { - SwingUtilities.invokeLater(() -> - { - if (notActive()) - { - return; - } - subMessageLabel.setText(subMessage); - }); - } - - public void setProgress(int currentProgress, int progressGoal) - { - SwingUtilities.invokeLater(() -> - { - if (notActive()) - { - return; - } - if (progressGoal != progressBar.getMaximum()) - { - panel.remove(progressBar); - panel.validate(); - final GridBagConstraints progressConstraints = new GridBagConstraints(); - progressConstraints.fill = GridBagConstraints.HORIZONTAL; - progressConstraints.anchor = GridBagConstraints.SOUTH; - progressConstraints.gridy = 5; - panel.add(progressBar, progressConstraints); - panel.validate(); - } - progressBar.setMaximum(progressGoal); - progressBar.setValue(currentProgress); - }); - } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/ui/components/InfoPanel.java new file mode 100644 index 0000000000..175d28d530 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/InfoPanel.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * 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.components; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.RuneLiteSplashScreen; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; + +@Slf4j +public class InfoPanel extends JPanel +{ + private static final String RUNELITE_VERSION = "runelite.version"; + private static final String RUNELITE_PLUS_VERSION = "runelite.plus.version"; + private static final String RUNELITE_PLUS_DATE = "runelite.plus.builddate"; + + private static final BufferedImage TRANSPARENT_LOGO = ImageUtil.getResourceStreamFromClass(InfoPanel.class, "/runeliteplus_transparent.png"); + static final Dimension PANEL_SIZE = new Dimension(200, RuneLiteSplashScreen.FRAME_SIZE.height); + private static final Dimension VERSION_SIZE = new Dimension(PANEL_SIZE.width, 25); + private static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite"); + private static final File LOGS_DIR = new File(RUNELITE_DIR, "logs"); + + private final Properties properties = new Properties(); + + public InfoPanel() + { + try (InputStream in = getClass().getResourceAsStream("/runelite.plus.properties")) + { + properties.load(in); + } + catch (IOException ex) + { + log.warn("unable to load propertries", ex); + } + + this.setLayout(new GridBagLayout()); + this.setPreferredSize(PANEL_SIZE); + this.setBackground(new Color(38, 38, 38)); + + final GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 0; + c.gridy = 0; + c.ipady = 5; + + // Logo + final ImageIcon transparentLogo = new ImageIcon(); + if (TRANSPARENT_LOGO != null) + { + transparentLogo.setImage(TRANSPARENT_LOGO.getScaledInstance(128, 128, Image.SCALE_SMOOTH)); + } + final JLabel logo = new JLabel(transparentLogo); + + c.anchor = GridBagConstraints.NORTH; + c.weighty = 1; + this.add(logo, c); + c.gridy++; + c.anchor = GridBagConstraints.SOUTH; + c.weighty = 0; + + // Version + this.add(createPanelTextButton("RuneLite Version: " + properties.getProperty(RUNELITE_VERSION)), c); + c.gridy++; + + // Plus version + this.add(createPanelTextButton("Plus Version: " + properties.getProperty(RUNELITE_PLUS_VERSION)), c); + c.gridy++; + + // Build date + this.add(createPanelTextButton("Build date: " + properties.getProperty(RUNELITE_PLUS_DATE)), c); + c.gridy++; + + final JLabel logsFolder = createPanelButton("Open logs folder", null, () -> LinkBrowser.openLocalFile(LOGS_DIR)); + this.add(logsFolder, c); + c.gridy++; + + final JLabel discord = createPanelButton("Get help on Discord", "Instant invite link to join the RuneLitePlus discord", () -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite())); + this.add(discord, c); + c.gridy++; + + final JLabel troubleshooting = createPanelButton("Troubleshooting steps", "Opens a link to the troubleshooting wiki", () -> LinkBrowser.browse(RuneLiteProperties.getTroubleshootingLink())); + this.add(troubleshooting, c); + c.gridy++; + + final JLabel exit = createPanelButton("Exit", "Closes the application immediately", () -> System.exit(0)); + this.add(exit, c); + c.gridy++; + } + + private static JLabel createPanelTextButton(final String title) + { + final JLabel textButton = new JLabel(title); + textButton.setFont(FontManager.getRunescapeSmallFont()); + textButton.setHorizontalAlignment(JLabel.CENTER); + textButton.setForeground(ColorScheme.BRAND_ORANGE); + textButton.setBackground(null); + textButton.setPreferredSize(VERSION_SIZE); + textButton.setMinimumSize(VERSION_SIZE); + textButton.setBorder(new MatteBorder(1, 0, 0, 0, Color.LIGHT_GRAY)); + + return textButton; + } + + private static JLabel createPanelButton(final String name, final String tooltip, final Runnable runnable) + { + final JLabel btn = new JLabel(name, JLabel.CENTER); + btn.setToolTipText(tooltip); + btn.setOpaque(true); + btn.setBackground(null); + btn.setForeground(Color.WHITE); + btn.setFont(FontManager.getRunescapeFont()); + btn.setBorder(new CompoundBorder( + new MatteBorder(1, 0, 0, 0, Color.LIGHT_GRAY), + new EmptyBorder(3, 0, 3, 0)) + ); + btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + btn.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + runnable.run(); + } + + @Override + public void mouseEntered(MouseEvent e) + { + btn.setBackground(new Color(60, 60, 60)); + btn.repaint(); + } + + @Override + public void mouseExited(MouseEvent e) + { + btn.setBackground(null); + btn.repaint(); + } + }); + + return btn; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/MessagePanel.java b/runelite-client/src/main/java/net/runelite/client/ui/components/MessagePanel.java new file mode 100644 index 0000000000..eea2e11f22 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/MessagePanel.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * 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.components; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JViewport; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import javax.swing.plaf.basic.BasicProgressBarUI; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.RuneLiteSplashScreen; + +@Getter +public class MessagePanel extends JPanel +{ + private static final Dimension PANEL_SIZE = new Dimension(RuneLiteSplashScreen.FRAME_SIZE.width - InfoPanel.PANEL_SIZE.width, RuneLiteSplashScreen.FRAME_SIZE.height); + private static final Dimension BAR_SIZE = new Dimension(PANEL_SIZE.width, 30); + private static final int MESSAGE_AREA_PADDING = 15; + + private final JLabel titleLabel = new JLabel("Welcome to RuneLitePlus"); + private final JLabel messageArea; + private final JLabel barLabel = new JLabel("Doing something important"); + private final JProgressBar bar = new JProgressBar(0, 100); + + @Getter(AccessLevel.NONE) + private final JScrollPane scrollPane; + + public MessagePanel() + { + this.setPreferredSize(PANEL_SIZE); + this.setLayout(new GridBagLayout()); + this.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + final GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTH; + c.weightx = 1; + c.gridx = 0; + c.gridy = 0; + c.ipady = 25; + + // main message + titleLabel.setFont(new Font(FontManager.getRunescapeFont().getName(), FontManager.getRunescapeFont().getStyle(), 32)); + titleLabel.setHorizontalAlignment(JLabel.CENTER); + titleLabel.setForeground(Color.WHITE); + this.add(titleLabel, c); + c.gridy++; + + // alternate message action + messageArea = new JLabel("
Fork of RuneLite that provides more functionality and less restrictions while staying open source.
") + { + @Override + public Dimension getPreferredSize() + { + final Dimension results = super.getPreferredSize(); + results.width = PANEL_SIZE.width - MESSAGE_AREA_PADDING; + return results; + } + }; + messageArea.setFont(new Font(FontManager.getRunescapeFont().getName(), FontManager.getRunescapeSmallFont().getStyle(), 16)); + messageArea.setForeground(Color.WHITE); + messageArea.setBorder(new EmptyBorder(0, MESSAGE_AREA_PADDING, 0, MESSAGE_AREA_PADDING)); + + scrollPane = new JScrollPane(messageArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setBorder(new EmptyBorder(0, 0, 0, 0)); + scrollPane.getVerticalScrollBar().setUI(new CustomScrollBarUI()); + final JViewport viewport = scrollPane.getViewport(); + viewport.setForeground(Color.WHITE); + viewport.setBackground(ColorScheme.DARKER_GRAY_COLOR); + viewport.setOpaque(true); + + c.weighty = 1; + c.fill = 1; + this.add(scrollPane, c); + c.gridy++; + + c.weighty = 0; + c.weightx = 1; + c.ipady = 5; + + barLabel.setFont(FontManager.getRunescapeFont()); + barLabel.setHorizontalAlignment(JLabel.CENTER); + barLabel.setForeground(Color.WHITE); + barLabel.setBorder(new EmptyBorder(5, 0, 5, 0)); + this.add(barLabel, c); + c.gridy++; + + bar.setBackground(ColorScheme.BRAND_ORANGE_TRANSPARENT.darker()); + bar.setForeground(ColorScheme.BRAND_ORANGE); + bar.setMinimumSize(BAR_SIZE); + bar.setMaximumSize(BAR_SIZE); + bar.setBorder(new MatteBorder(0, 0, 0, 0, Color.LIGHT_GRAY)); + bar.setUI(new BasicProgressBarUI() + { + protected Color getSelectionBackground() + { + return ColorScheme.DARKER_GRAY_COLOR; + } + + protected Color getSelectionForeground() + { + return ColorScheme.DARKER_GRAY_COLOR; + } + }); + bar.setFont(FontManager.getRunescapeFont()); + bar.setVisible(true); + this.add(bar, c); + c.gridy++; + } + + public void setMessageContent(String content) + { + if (!content.startsWith(""; + } + + messageArea.setText(content); + messageArea.revalidate(); + messageArea.repaint(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java index 094ae56102..4e27ba15d7 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java +++ b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java @@ -26,6 +26,7 @@ package net.runelite.client.util; import com.google.common.base.Strings; import java.awt.Desktop; +import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -128,6 +129,55 @@ public class LinkBrowser } } + /** + * Tries to open the specified {@code File} with the systems default text editor. If operation fails + * an error message is displayed with the option to copy the absolute file path to clipboard. + * @param file the File instance of the log file + * @return did the file open successfully? + */ + public static boolean openLocalFile(final File file) + { + if (file == null || !file.exists()) + { + return false; + } + + if (attemptOpenLocalFile(file)) + { + log.debug("Opened log file through Desktop#edit to {}", file); + return true; + } + + showMessageBox("Unable to open log file. Press 'OK' and the file path will be copied to your clipboard", file.getAbsolutePath()); + return false; + } + + private static boolean attemptOpenLocalFile(final File file) + { + if (!Desktop.isDesktopSupported()) + { + return false; + } + + final Desktop desktop = Desktop.getDesktop(); + + if (!desktop.isSupported(Desktop.Action.OPEN)) + { + return false; + } + + try + { + desktop.open(file); + return true; + } + catch (IOException ex) + { + log.warn("Failed to open Desktop#edit {}", file, ex); + return false; + } + } + /** * Open swing message box with specified message and copy data to clipboard * 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 ff62937bc8..1dc821434f 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 @@ -25,7 +25,9 @@ package net.runelite.client.util; import java.awt.AWTException; +import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; @@ -42,18 +44,26 @@ import java.util.concurrent.Callable; import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.swing.ButtonModel; import javax.swing.ImageIcon; import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.LookAndFeel; 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.Border; +import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; import javax.swing.plaf.FontUIResource; import javax.swing.plaf.basic.BasicProgressBarUI; import lombok.extern.slf4j.Slf4j; @@ -293,6 +303,136 @@ public class SwingUtil return button; } + /** + * Creates a custom {@link JButton} with a flat design for use inside {@link JOptionPane}. + * The button will display the passed {@code text} and set the value of the pane to {@code buttonOption} on click + * + * @param text text to be displayed inside the button + * @param buttonOption the code to be set via {@link JOptionPane#setValue(Object)} + * @return newly created {@link JButton} + */ + public static JButton createFlatButton(final String text, final int buttonOption) + { + final Border BUTTON_BORDER = new EmptyBorder(5, 17, 5, 17); + final Border BORDERED_BUTTON_BORDER = new CompoundBorder( + new MatteBorder(1, 1, 1, 1, Color.BLACK), + new EmptyBorder(4, 16, 4, 16) + ); + + final JButton button = new JButton(text); + button.setForeground(Color.WHITE); + button.setBackground(Color.BLACK); + button.setFont(FontManager.getRunescapeFont()); + button.setBorder(BUTTON_BORDER); + + button.setBorderPainted(false); + button.setFocusPainted(false); + button.setContentAreaFilled(false); + button.setOpaque(true); + + // Selecting the button option requires us to determine which parent element is the JOptionPane + button.addActionListener(e -> { + JComponent component = (JComponent) e.getSource(); + while (component != null) + { + if (component instanceof JOptionPane) + { + ((JOptionPane) component).setValue(buttonOption); + component = null; + } + else + { + component = component.getParent() == null ? null : (JComponent) component.getParent(); + } + } + }); + + // Use change listener instead of mouse listener for buttons + button.getModel().addChangeListener(e -> + { + final ButtonModel model = (ButtonModel) e.getSource(); + button.setBackground(model.isRollover() ? ColorScheme.DARKER_GRAY_HOVER_COLOR : Color.BLACK); + button.setBorderPainted(model.isPressed()); + button.setBorder(model.isPressed() ? BORDERED_BUTTON_BORDER : BUTTON_BORDER); + }); + + return button; + } + + /** + * Opens a {@link JDialog} with a stylized {@link JOptionPane} ignoring UIManager defaults. + * The buttons should be created via the {@link #createFlatButton(String, int)} function to look correctly + * + * @param component The frame the dialog should be attached to. nullable + * @param content The string content to be added to the content pane + * @param optionType The JOptionPane option type of dialog pane to create + * @param buttons Buttons to display, created via {@link #createFlatButton(String, int)} + * @return The Integer value representing the button selected + */ + public static int showRuneLiteOptionPane(final JComponent component, final String content, final int optionType, final JButton[] buttons) + { + final JLabel contentLabel = new JLabel(content); + contentLabel.setFont(FontManager.getRunescapeFont()); + contentLabel.setForeground(Color.WHITE); + contentLabel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + final JPanel p = new JPanel(new BorderLayout()); + p.setBackground(ColorScheme.DARKER_GRAY_COLOR); + p.setForeground(Color.WHITE); + p.add(contentLabel, BorderLayout.NORTH); + + final JOptionPane pane = new JOptionPane(p, + JOptionPane.ERROR_MESSAGE, + optionType, + null, + buttons, + buttons[1]); + pane.setBackground(ColorScheme.DARKER_GRAY_COLOR); + pane.setForeground(Color.WHITE); + stylizeJPanels(pane); + + final Frame frame = component == null ? JOptionPane.getRootFrame() : JOptionPane.getFrameForComponent(component); + final JDialog dialog = new JDialog(frame, "RuneLitePlus Error", true); + dialog.setContentPane(pane); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + dialog.setAlwaysOnTop(true); + dialog.setAutoRequestFocus(true); + dialog.setLocationRelativeTo(null); + dialog.setIconImage(ImageUtil.getResourceStreamFromClass(SwingUtil.class, "/runeliteplus_transparent.png")); + + // Listen for value changes and close dialog when necessary + pane.addPropertyChangeListener(e -> { + String prop = e.getPropertyName(); + + if (dialog.isVisible() + && (e.getSource() == pane) + && (prop.equals(JOptionPane.VALUE_PROPERTY))) + { + dialog.setVisible(false); + } + }); + + dialog.pack(); + // Try to center dialog based on its size + dialog.setLocation(dialog.getX() - dialog.getSize().width / 2, dialog.getY() - dialog.getSize().height / 2); + dialog.setVisible(true); + + return (Integer) pane.getValue(); + } + + private static void stylizeJPanels(final JComponent component) + { + for (final Component c : component.getComponents()) + { + if (c instanceof JPanel) + { + c.setBackground(ColorScheme.DARKER_GRAY_COLOR); + c.setForeground(Color.WHITE); + stylizeJPanels((JComponent) c); + } + } + } + /** * 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