runelite-client: Add loading splash screen
This commit is contained in:
@@ -60,6 +60,7 @@ import net.runelite.client.rs.ClientLoader;
|
||||
import net.runelite.client.rs.ClientUpdateCheckMode;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.DrawManager;
|
||||
import net.runelite.client.ui.SplashScreen;
|
||||
import net.runelite.client.ui.overlay.OverlayManager;
|
||||
import net.runelite.client.ui.overlay.OverlayRenderer;
|
||||
import net.runelite.client.ui.overlay.WidgetOverlay;
|
||||
@@ -197,40 +198,50 @@ public class RuneLite
|
||||
}
|
||||
});
|
||||
|
||||
final ClientLoader clientLoader = new ClientLoader(options.valueOf(updateMode));
|
||||
SplashScreen.init();
|
||||
SplashScreen.stage(0, "Retrieving client", "");
|
||||
|
||||
new Thread(() ->
|
||||
try
|
||||
{
|
||||
clientLoader.get();
|
||||
ClassPreloader.preload();
|
||||
}, "Preloader").start();
|
||||
final ClientLoader clientLoader = new ClientLoader(options.valueOf(updateMode));
|
||||
|
||||
final boolean developerMode = options.has("developer-mode") && RuneLiteProperties.getLauncherVersion() == null;
|
||||
|
||||
if (developerMode)
|
||||
{
|
||||
boolean assertions = false;
|
||||
assert assertions = true;
|
||||
if (!assertions)
|
||||
new Thread(() ->
|
||||
{
|
||||
throw new RuntimeException("Developers should enable assertions; Add `-ea` to your JVM arguments`");
|
||||
clientLoader.get();
|
||||
ClassPreloader.preload();
|
||||
}, "Preloader").start();
|
||||
|
||||
final boolean developerMode = options.has("developer-mode") && RuneLiteProperties.getLauncherVersion() == null;
|
||||
|
||||
if (developerMode)
|
||||
{
|
||||
boolean assertions = false;
|
||||
assert assertions = true;
|
||||
if (!assertions)
|
||||
{
|
||||
throw new RuntimeException("Developers should enable assertions; Add `-ea` to your JVM arguments`");
|
||||
}
|
||||
}
|
||||
|
||||
PROFILES_DIR.mkdirs();
|
||||
|
||||
final long start = System.currentTimeMillis();
|
||||
|
||||
injector = Guice.createInjector(new RuneLiteModule(
|
||||
clientLoader,
|
||||
developerMode));
|
||||
|
||||
injector.getInstance(RuneLite.class).start();
|
||||
|
||||
final long end = System.currentTimeMillis();
|
||||
final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
|
||||
final long uptime = rb.getUptime();
|
||||
log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SplashScreen.stop();
|
||||
}
|
||||
|
||||
PROFILES_DIR.mkdirs();
|
||||
|
||||
final long start = System.currentTimeMillis();
|
||||
|
||||
injector = Guice.createInjector(new RuneLiteModule(
|
||||
clientLoader,
|
||||
developerMode));
|
||||
|
||||
injector.getInstance(RuneLite.class).start();
|
||||
|
||||
final long end = System.currentTimeMillis();
|
||||
final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
|
||||
final long uptime = rb.getUptime();
|
||||
log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime);
|
||||
}
|
||||
|
||||
public void start() throws Exception
|
||||
@@ -244,6 +255,8 @@ public class RuneLite
|
||||
injector.injectMembers(client);
|
||||
}
|
||||
|
||||
SplashScreen.stage(.57, null, "Loading configuration");
|
||||
|
||||
// Load user configuration
|
||||
configManager.load();
|
||||
|
||||
@@ -257,6 +270,8 @@ public class RuneLite
|
||||
// This will initialize configuration
|
||||
pluginManager.loadCorePlugins();
|
||||
|
||||
SplashScreen.stage(.70, null, "Finalizing configuration");
|
||||
|
||||
// Plugins have provided their config, so set default config
|
||||
// to main settings
|
||||
pluginManager.loadDefaultPluginConfiguration();
|
||||
@@ -264,8 +279,10 @@ public class RuneLite
|
||||
// Start client session
|
||||
clientSessionManager.start();
|
||||
|
||||
SplashScreen.stage(.75, null, "Starting core interface");
|
||||
|
||||
// Initialize UI
|
||||
clientUI.open(this);
|
||||
clientUI.init(this);
|
||||
|
||||
// Initialize Discord service
|
||||
discordService.init();
|
||||
@@ -301,6 +318,10 @@ public class RuneLite
|
||||
|
||||
// Start plugins
|
||||
pluginManager.startCorePlugins();
|
||||
|
||||
SplashScreen.stop();
|
||||
|
||||
clientUI.show();
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
|
||||
@@ -70,6 +70,7 @@ import net.runelite.client.events.PluginChanged;
|
||||
import net.runelite.client.task.Schedule;
|
||||
import net.runelite.client.task.ScheduledMethod;
|
||||
import net.runelite.client.task.Scheduler;
|
||||
import net.runelite.client.ui.SplashScreen;
|
||||
import net.runelite.client.util.GameEventManager;
|
||||
|
||||
@Singleton
|
||||
@@ -200,6 +201,7 @@ public class PluginManager
|
||||
public void startCorePlugins()
|
||||
{
|
||||
List<Plugin> scannedPlugins = new ArrayList<>(plugins);
|
||||
int loaded = 0;
|
||||
for (Plugin plugin : scannedPlugins)
|
||||
{
|
||||
try
|
||||
@@ -211,11 +213,15 @@ public class PluginManager
|
||||
log.warn("Unable to start plugin {}. {}", plugin.getClass().getSimpleName(), ex);
|
||||
plugins.remove(plugin);
|
||||
}
|
||||
|
||||
loaded++;
|
||||
SplashScreen.stage(.80, 1, null, "Starting plugins", loaded, scannedPlugins.size(), false);
|
||||
}
|
||||
}
|
||||
|
||||
List<Plugin> scanAndInstantiate(ClassLoader classLoader, String packageName) throws IOException
|
||||
{
|
||||
SplashScreen.stage(.59, null, "Loading Plugins");
|
||||
MutableGraph<Class<? extends Plugin>> graph = GraphBuilder
|
||||
.directed()
|
||||
.build();
|
||||
@@ -280,20 +286,22 @@ public class PluginManager
|
||||
List<Class<? extends Plugin>> sortedPlugins = topologicalSort(graph);
|
||||
sortedPlugins = Lists.reverse(sortedPlugins);
|
||||
|
||||
int loaded = 0;
|
||||
for (Class<? extends Plugin> pluginClazz : sortedPlugins)
|
||||
{
|
||||
Plugin plugin;
|
||||
try
|
||||
{
|
||||
plugin = instantiate(scannedPlugins, (Class<Plugin>) pluginClazz);
|
||||
scannedPlugins.add(plugin);
|
||||
}
|
||||
catch (PluginInstantiationException ex)
|
||||
{
|
||||
log.warn("Error instantiating plugin!", ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
scannedPlugins.add(plugin);
|
||||
loaded++;
|
||||
SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, sortedPlugins.size(), false);
|
||||
}
|
||||
|
||||
return scannedPlugins;
|
||||
|
||||
@@ -34,6 +34,7 @@ import io.sigpipe.jbsdiff.InvalidHeaderException;
|
||||
import io.sigpipe.jbsdiff.Patch;
|
||||
import java.applet.Applet;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -53,6 +54,7 @@ import net.runelite.api.Client;
|
||||
import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO;
|
||||
import static net.runelite.client.rs.ClientUpdateCheckMode.NONE;
|
||||
import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA;
|
||||
import net.runelite.client.ui.SplashScreen;
|
||||
import net.runelite.http.api.RuneLiteAPI;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
@@ -88,6 +90,7 @@ public class ClientLoader implements Supplier<Applet>
|
||||
|
||||
try
|
||||
{
|
||||
SplashScreen.stage(0, null, "Fetching applet viewer config");
|
||||
RSConfig config = ClientConfigLoader.fetch();
|
||||
|
||||
Map<String, byte[]> zipFile = new HashMap<>();
|
||||
@@ -102,7 +105,26 @@ public class ClientLoader implements Supplier<Applet>
|
||||
|
||||
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
||||
{
|
||||
JarInputStream jis = new JarInputStream(response.body().byteStream());
|
||||
int length = (int) response.body().contentLength();
|
||||
if (length < 0)
|
||||
{
|
||||
length = 3 * 1024 * 1024;
|
||||
}
|
||||
final int flength = length;
|
||||
InputStream istream = new FilterInputStream(response.body().byteStream())
|
||||
{
|
||||
private int read = 0;
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException
|
||||
{
|
||||
int thisRead = super.read(b, off, len);
|
||||
this.read += thisRead;
|
||||
SplashScreen.stage(.05, .35, null, "Downloading Old School RuneScape", this.read, flength, true);
|
||||
return thisRead;
|
||||
}
|
||||
};
|
||||
JarInputStream jis = new JarInputStream(istream);
|
||||
|
||||
byte[] tmp = new byte[4096];
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024);
|
||||
@@ -146,6 +168,7 @@ public class ClientLoader implements Supplier<Applet>
|
||||
|
||||
if (updateCheckMode == AUTO)
|
||||
{
|
||||
SplashScreen.stage(.35, null, "Patching");
|
||||
Map<String, String> hashes;
|
||||
try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json"))
|
||||
{
|
||||
@@ -197,11 +220,14 @@ public class ClientLoader implements Supplier<Applet>
|
||||
file.setValue(patchOs.toByteArray());
|
||||
|
||||
++patchCount;
|
||||
SplashScreen.stage(.38, .45, null, "Patching", patchCount, zipFile.size(), false);
|
||||
}
|
||||
|
||||
log.debug("Patched {} classes", patchCount);
|
||||
}
|
||||
|
||||
SplashScreen.stage(.465, "Starting", "Starting Old School RuneScape");
|
||||
|
||||
String initialClass = config.getInitialClass();
|
||||
|
||||
ClassLoader rsClassLoader = new ClassLoader(ClientLoader.class.getClassLoader())
|
||||
@@ -230,6 +256,8 @@ public class ClientLoader implements Supplier<Applet>
|
||||
log.info("client-patch {}", ((Client) rs).getBuildID());
|
||||
}
|
||||
|
||||
SplashScreen.stage(.5, null, "Starting core classes");
|
||||
|
||||
return rs;
|
||||
}
|
||||
catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException
|
||||
|
||||
@@ -261,7 +261,7 @@ public class ClientUI
|
||||
return;
|
||||
}
|
||||
|
||||
final Client client = (Client)this.client;
|
||||
final Client client = (Client) this.client;
|
||||
final ClientThread clientThread = clientThreadProvider.get();
|
||||
|
||||
// Keep scheduling event until we get our name
|
||||
@@ -293,11 +293,10 @@ public class ClientUI
|
||||
|
||||
/**
|
||||
* Initialize UI.
|
||||
*
|
||||
* @param runelite runelite instance that will be shut down on exit
|
||||
* @throws Exception exception that can occur during creation of the UI
|
||||
*/
|
||||
public void open(final RuneLite runelite) throws Exception
|
||||
public void init(final RuneLite runelite) throws Exception
|
||||
{
|
||||
SwingUtilities.invokeAndWait(() ->
|
||||
{
|
||||
@@ -453,7 +452,13 @@ public class ClientUI
|
||||
|
||||
titleToolbar.addComponent(sidebarNavigationButton, sidebarNavigationJButton);
|
||||
toggleSidebar();
|
||||
});
|
||||
}
|
||||
|
||||
public void show()
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
// Layout frame
|
||||
frame.pack();
|
||||
frame.revalidateMinimumSize();
|
||||
@@ -603,10 +608,10 @@ public class ClientUI
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes cursor for client window. Requires ${@link ClientUI#open(RuneLite)} to be called first.
|
||||
* Changes cursor for client window. Requires ${@link ClientUI#init(RuneLite)} to be called first.
|
||||
* FIXME: This is working properly only on Windows, Linux and Mac are displaying cursor incorrectly
|
||||
* @param image cursor image
|
||||
* @param name cursor name
|
||||
* @param name cursor name
|
||||
*/
|
||||
public void setCursor(final BufferedImage image, final String name)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Abex
|
||||
* 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.Color;
|
||||
import java.awt.Container;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.plaf.basic.BasicProgressBarUI;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SplashScreen extends JFrame implements ActionListener
|
||||
{
|
||||
private static final int WIDTH = 200;
|
||||
private static final int PAD = 10;
|
||||
|
||||
private static SplashScreen INSTANCE;
|
||||
|
||||
private final JLabel action = new JLabel("Loading");
|
||||
private final JProgressBar progress = new JProgressBar();
|
||||
private final JLabel subAction = new JLabel();
|
||||
private final Timer timer;
|
||||
|
||||
private volatile double overallProgress = 0;
|
||||
private volatile String actionText = "Loading";
|
||||
private volatile String subActionText = "";
|
||||
private volatile String progressText = null;
|
||||
|
||||
private SplashScreen() throws IOException
|
||||
{
|
||||
BufferedImage logo = ImageIO.read(SplashScreen.class.getResourceAsStream("runelite_transparent.png"));
|
||||
|
||||
setTitle("RuneLite Launcher");
|
||||
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setUndecorated(true);
|
||||
setIconImage(logo);
|
||||
setLayout(null);
|
||||
Container pane = getContentPane();
|
||||
pane.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
|
||||
Font font = new Font(Font.DIALOG, Font.PLAIN, 12);
|
||||
|
||||
JLabel logoLabel = new JLabel(new ImageIcon(logo));
|
||||
pane.add(logoLabel);
|
||||
logoLabel.setBounds(0, 0, WIDTH, WIDTH);
|
||||
|
||||
int y = WIDTH;
|
||||
|
||||
pane.add(action);
|
||||
action.setForeground(Color.WHITE);
|
||||
action.setBounds(0, y, WIDTH, 16);
|
||||
action.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
action.setFont(font);
|
||||
y += action.getHeight() + PAD;
|
||||
|
||||
pane.add(progress);
|
||||
progress.setForeground(ColorScheme.BRAND_ORANGE);
|
||||
progress.setBackground(ColorScheme.BRAND_ORANGE.darker().darker());
|
||||
progress.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
progress.setBounds(0, y, WIDTH, 14);
|
||||
progress.setFont(font);
|
||||
progress.setUI(new BasicProgressBarUI()
|
||||
{
|
||||
@Override
|
||||
protected Color getSelectionBackground()
|
||||
{
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Color getSelectionForeground()
|
||||
{
|
||||
return Color.BLACK;
|
||||
}
|
||||
});
|
||||
y += 12 + PAD;
|
||||
|
||||
pane.add(subAction);
|
||||
subAction.setForeground(Color.LIGHT_GRAY);
|
||||
subAction.setBounds(0, y, WIDTH, 16);
|
||||
subAction.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
subAction.setFont(font);
|
||||
y += subAction.getHeight() + PAD;
|
||||
|
||||
setSize(WIDTH, y);
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
timer = new Timer(100, this);
|
||||
timer.setRepeats(true);
|
||||
timer.start();
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
action.setText(actionText);
|
||||
subAction.setText(subActionText);
|
||||
progress.setMaximum(1000);
|
||||
progress.setValue((int) (overallProgress * 1000));
|
||||
|
||||
String progressText = this.progressText;
|
||||
if (progressText == null)
|
||||
{
|
||||
progress.setStringPainted(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.setStringPainted(true);
|
||||
progress.setString(progressText);
|
||||
}
|
||||
}
|
||||
|
||||
public static void init()
|
||||
{
|
||||
try
|
||||
{
|
||||
SwingUtilities.invokeAndWait(() ->
|
||||
{
|
||||
if (INSTANCE != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
|
||||
INSTANCE = new SplashScreen();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("Unable to start splash screen", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (InterruptedException | InvocationTargetException bs)
|
||||
{
|
||||
throw new RuntimeException(bs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop()
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
if (INSTANCE == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
INSTANCE.timer.stop();
|
||||
INSTANCE.dispose();
|
||||
INSTANCE = null;
|
||||
});
|
||||
}
|
||||
|
||||
public static void stage(double overallProgress, @Nullable String actionText, String subActionText)
|
||||
{
|
||||
stage(overallProgress, actionText, subActionText, null);
|
||||
}
|
||||
|
||||
public static void stage(double startProgress, double endProgress,
|
||||
@Nullable String actionText, String subActionText,
|
||||
int done, int total, boolean mib)
|
||||
{
|
||||
String progress;
|
||||
if (mib)
|
||||
{
|
||||
final double MiB = 1024 * 1042;
|
||||
progress = String.format("%.1f / %.1f MiB", done / MiB, total / MiB);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress = done + " / " + total;
|
||||
}
|
||||
stage(startProgress + ((endProgress - startProgress) * done / total), actionText, subActionText, progress);
|
||||
}
|
||||
|
||||
public static void stage(double overallProgress, @Nullable String actionText, String subActionText, @Nullable String progressText)
|
||||
{
|
||||
if (INSTANCE != null)
|
||||
{
|
||||
INSTANCE.overallProgress = overallProgress;
|
||||
if (actionText != null)
|
||||
{
|
||||
INSTANCE.actionText = actionText;
|
||||
}
|
||||
INSTANCE.subActionText = subActionText;
|
||||
INSTANCE.progressText = progressText;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Reference in New Issue
Block a user