Merge pull request #10446 from abextm/external-plugins
External Plugins
This commit is contained in:
@@ -57,6 +57,7 @@ import net.runelite.client.game.ItemManager;
|
|||||||
import net.runelite.client.game.LootManager;
|
import net.runelite.client.game.LootManager;
|
||||||
import net.runelite.client.game.chatbox.ChatboxPanelManager;
|
import net.runelite.client.game.chatbox.ChatboxPanelManager;
|
||||||
import net.runelite.client.menus.MenuManager;
|
import net.runelite.client.menus.MenuManager;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManager;
|
||||||
import net.runelite.client.plugins.PluginManager;
|
import net.runelite.client.plugins.PluginManager;
|
||||||
import net.runelite.client.rs.ClientLoader;
|
import net.runelite.client.rs.ClientLoader;
|
||||||
import net.runelite.client.rs.ClientUpdateCheckMode;
|
import net.runelite.client.rs.ClientUpdateCheckMode;
|
||||||
@@ -80,6 +81,7 @@ public class RuneLite
|
|||||||
{
|
{
|
||||||
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
|
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
|
||||||
public static final File CACHE_DIR = new File(RUNELITE_DIR, "cache");
|
public static final File CACHE_DIR = new File(RUNELITE_DIR, "cache");
|
||||||
|
public static final File PLUGINS_DIR = new File(RUNELITE_DIR, "plugins");
|
||||||
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
|
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
|
||||||
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
|
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
|
||||||
public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs");
|
public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs");
|
||||||
@@ -90,6 +92,9 @@ public class RuneLite
|
|||||||
@Inject
|
@Inject
|
||||||
private PluginManager pluginManager;
|
private PluginManager pluginManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ExternalPluginManager externalPluginManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private EventBus eventBus;
|
private EventBus eventBus;
|
||||||
|
|
||||||
@@ -288,12 +293,13 @@ public class RuneLite
|
|||||||
// Load the plugins, but does not start them yet.
|
// Load the plugins, but does not start them yet.
|
||||||
// This will initialize configuration
|
// This will initialize configuration
|
||||||
pluginManager.loadCorePlugins();
|
pluginManager.loadCorePlugins();
|
||||||
|
externalPluginManager.loadExternalPlugins();
|
||||||
|
|
||||||
SplashScreen.stage(.70, null, "Finalizing configuration");
|
SplashScreen.stage(.70, null, "Finalizing configuration");
|
||||||
|
|
||||||
// Plugins have provided their config, so set default config
|
// Plugins have provided their config, so set default config
|
||||||
// to main settings
|
// to main settings
|
||||||
pluginManager.loadDefaultPluginConfiguration();
|
pluginManager.loadDefaultPluginConfiguration(null);
|
||||||
|
|
||||||
// Start client session
|
// Start client session
|
||||||
clientSessionManager.start();
|
clientSessionManager.start();
|
||||||
@@ -309,6 +315,7 @@ public class RuneLite
|
|||||||
// Register event listeners
|
// Register event listeners
|
||||||
eventBus.register(clientUI);
|
eventBus.register(clientUI);
|
||||||
eventBus.register(pluginManager);
|
eventBus.register(pluginManager);
|
||||||
|
eventBus.register(externalPluginManager);
|
||||||
eventBus.register(overlayManager);
|
eventBus.register(overlayManager);
|
||||||
eventBus.register(drawManager);
|
eventBus.register(drawManager);
|
||||||
eventBus.register(infoBoxManager);
|
eventBus.register(infoBoxManager);
|
||||||
@@ -337,7 +344,7 @@ public class RuneLite
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start plugins
|
// Start plugins
|
||||||
pluginManager.startCorePlugins();
|
pluginManager.startPlugins();
|
||||||
|
|
||||||
SplashScreen.stop();
|
SplashScreen.stop();
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
public class RuneLiteProperties
|
public class RuneLiteProperties
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,8 @@ public class RuneLiteProperties
|
|||||||
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
|
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
|
||||||
private static final String JAV_CONFIG = "runelite.jav_config";
|
private static final String JAV_CONFIG = "runelite.jav_config";
|
||||||
private static final String JAV_CONFIG_BACKUP = "runelite.jav_config_backup";
|
private static final String JAV_CONFIG_BACKUP = "runelite.jav_config_backup";
|
||||||
|
private static final String PLUGINHUB_BASE = "runelite.pluginhub.url";
|
||||||
|
private static final String PLUGINHUB_VERSION = "runelite.pluginhub.version";
|
||||||
|
|
||||||
private static final Properties properties = new Properties();
|
private static final Properties properties = new Properties();
|
||||||
|
|
||||||
@@ -130,4 +133,10 @@ public class RuneLiteProperties
|
|||||||
{
|
{
|
||||||
return properties.getProperty(JAV_CONFIG_BACKUP);
|
return properties.getProperty(JAV_CONFIG_BACKUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HttpUrl getPluginHubBase()
|
||||||
|
{
|
||||||
|
String version = System.getProperty(PLUGINHUB_VERSION, properties.getProperty(PLUGINHUB_VERSION));
|
||||||
|
return HttpUrl.parse(properties.get(PLUGINHUB_BASE) + "/" + version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,11 @@ import net.runelite.api.Constants;
|
|||||||
import net.runelite.client.Notifier;
|
import net.runelite.client.Notifier;
|
||||||
import net.runelite.client.ui.ContainableFrame;
|
import net.runelite.client.ui.ContainableFrame;
|
||||||
|
|
||||||
@ConfigGroup("runelite")
|
@ConfigGroup(RuneLiteConfig.GROUP_NAME)
|
||||||
public interface RuneLiteConfig extends Config
|
public interface RuneLiteConfig extends Config
|
||||||
{
|
{
|
||||||
|
String GROUP_NAME = "runelite";
|
||||||
|
|
||||||
@ConfigItem(
|
@ConfigItem(
|
||||||
keyName = "gameSize",
|
keyName = "gameSize",
|
||||||
name = "Game size",
|
name = "Game size",
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.events;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Value;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posted when an external plugin has been added, removed, or updated
|
||||||
|
*/
|
||||||
|
@Value
|
||||||
|
public class ExternalPluginsChanged
|
||||||
|
{
|
||||||
|
private final List<ExternalPluginManifest> loadedManifest;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.externalplugins;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
class ExternalPluginClassLoader extends URLClassLoader
|
||||||
|
{
|
||||||
|
@Getter
|
||||||
|
private final ExternalPluginManifest manifest;
|
||||||
|
|
||||||
|
ExternalPluginClassLoader(ExternalPluginManifest manifest, URL[] urls)
|
||||||
|
{
|
||||||
|
super(urls, ExternalPluginClassLoader.class.getClassLoader());
|
||||||
|
this.manifest = manifest;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* 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.externalplugins;
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import net.runelite.client.RuneLiteProperties;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import net.runelite.client.util.VerificationException;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okio.BufferedSource;
|
||||||
|
|
||||||
|
public class ExternalPluginClient
|
||||||
|
{
|
||||||
|
private final OkHttpClient cachingClient;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ExternalPluginClient(OkHttpClient cachingClient)
|
||||||
|
{
|
||||||
|
this.cachingClient = cachingClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ExternalPluginManifest> downloadManifest() throws IOException, VerificationException
|
||||||
|
{
|
||||||
|
HttpUrl manifest = RuneLiteProperties.getPluginHubBase()
|
||||||
|
.newBuilder()
|
||||||
|
.addPathSegments("manifest.js")
|
||||||
|
.build();
|
||||||
|
try (Response res = cachingClient.newCall(new Request.Builder().url(manifest).build()).execute())
|
||||||
|
{
|
||||||
|
if (res.code() != 200)
|
||||||
|
{
|
||||||
|
throw new IOException("Non-OK response code: " + res.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedSource src = res.body().source();
|
||||||
|
|
||||||
|
byte[] signature = new byte[src.readInt()];
|
||||||
|
src.readFully(signature);
|
||||||
|
|
||||||
|
byte[] data = src.readByteArray();
|
||||||
|
Signature s = Signature.getInstance("SHA256withRSA");
|
||||||
|
s.initVerify(loadCertificate());
|
||||||
|
s.update(data);
|
||||||
|
|
||||||
|
if (!s.verify(signature))
|
||||||
|
{
|
||||||
|
throw new VerificationException("Unable to verify external plugin manifest");
|
||||||
|
}
|
||||||
|
|
||||||
|
return RuneLiteAPI.GSON.fromJson(new String(data, StandardCharsets.UTF_8),
|
||||||
|
new TypeToken<List<ExternalPluginManifest>>()
|
||||||
|
{
|
||||||
|
}.getType());
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage downloadIcon(ExternalPluginManifest plugin) throws IOException
|
||||||
|
{
|
||||||
|
if (!plugin.hasIcon())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpUrl url = RuneLiteProperties.getPluginHubBase()
|
||||||
|
.newBuilder()
|
||||||
|
.addPathSegment(plugin.getInternalName())
|
||||||
|
.addPathSegment(plugin.getCommit() + ".png")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response res = cachingClient.newCall(new Request.Builder().url(url).build()).execute())
|
||||||
|
{
|
||||||
|
byte[] bytes = res.body().bytes();
|
||||||
|
// We don't stream so the lock doesn't block the edt trying to load something at the same time
|
||||||
|
synchronized (ImageIO.class)
|
||||||
|
{
|
||||||
|
return ImageIO.read(new ByteArrayInputStream(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Certificate loadCertificate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||||
|
Certificate certificate = certFactory.generateCertificate(ExternalPluginClient.class.getResourceAsStream("externalplugins.crt"));
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
catch (CertificateException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
/*
|
||||||
|
* 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.externalplugins;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashMultimap;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.common.hash.HashingInputStream;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.RuneLite;
|
||||||
|
import net.runelite.client.RuneLiteProperties;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.config.RuneLiteConfig;
|
||||||
|
import net.runelite.client.eventbus.EventBus;
|
||||||
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
|
import net.runelite.client.events.ExternalPluginsChanged;
|
||||||
|
import net.runelite.client.events.SessionClose;
|
||||||
|
import net.runelite.client.events.SessionOpen;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginInstantiationException;
|
||||||
|
import net.runelite.client.plugins.PluginManager;
|
||||||
|
import net.runelite.client.ui.SplashScreen;
|
||||||
|
import net.runelite.client.util.CountingInputStream;
|
||||||
|
import net.runelite.client.util.Text;
|
||||||
|
import net.runelite.client.util.VerificationException;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Slf4j
|
||||||
|
public class ExternalPluginManager
|
||||||
|
{
|
||||||
|
private static final String PLUGIN_LIST_KEY = "externalPlugins";
|
||||||
|
private static Class<? extends Plugin>[] builtinExternals = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ConfigManager configManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ExternalPluginClient externalPluginClient;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PluginManager pluginManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ScheduledExecutorService executor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private EventBus eventBus;
|
||||||
|
|
||||||
|
public void loadExternalPlugins() throws PluginInstantiationException
|
||||||
|
{
|
||||||
|
refreshPlugins();
|
||||||
|
|
||||||
|
if (builtinExternals != null)
|
||||||
|
{
|
||||||
|
// builtin external's don't actually have a manifest or a separate classloader...
|
||||||
|
pluginManager.loadPlugins(Lists.newArrayList(builtinExternals), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onSessionOpen(SessionOpen event)
|
||||||
|
{
|
||||||
|
executor.submit(this::refreshPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onSessionClose(SessionClose event)
|
||||||
|
{
|
||||||
|
executor.submit(this::refreshPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshPlugins()
|
||||||
|
{
|
||||||
|
Multimap<ExternalPluginManifest, Plugin> loadedExternalPlugins = HashMultimap.create();
|
||||||
|
for (Plugin p : pluginManager.getPlugins())
|
||||||
|
{
|
||||||
|
ExternalPluginManifest m = getExternalPluginManifest(p.getClass());
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
loadedExternalPlugins.put(m, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> installedIDs = getInstalledExternalPlugins();
|
||||||
|
if (installedIDs.isEmpty() && loadedExternalPlugins.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean startup = SplashScreen.isOpen();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
double splashStart = startup ? .60 : 0;
|
||||||
|
double splashLength = startup ? .10 : 1;
|
||||||
|
if (!startup)
|
||||||
|
{
|
||||||
|
SplashScreen.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
SplashScreen.stage(splashStart, null, "Downloading external plugins");
|
||||||
|
Set<ExternalPluginManifest> externalPlugins = new HashSet<>();
|
||||||
|
|
||||||
|
RuneLite.PLUGINS_DIR.mkdirs();
|
||||||
|
|
||||||
|
List<ExternalPluginManifest> manifestList;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
manifestList = externalPluginClient.downloadManifest();
|
||||||
|
Map<String, ExternalPluginManifest> manifests = manifestList
|
||||||
|
.stream().collect(ImmutableMap.toImmutableMap(ExternalPluginManifest::getInternalName, Function.identity()));
|
||||||
|
|
||||||
|
Set<ExternalPluginManifest> needsDownload = new HashSet<>();
|
||||||
|
Set<File> keep = new HashSet<>();
|
||||||
|
|
||||||
|
for (String name : installedIDs)
|
||||||
|
{
|
||||||
|
ExternalPluginManifest manifest = manifests.get(name);
|
||||||
|
if (manifest != null)
|
||||||
|
{
|
||||||
|
externalPlugins.add(manifest);
|
||||||
|
|
||||||
|
if (!manifest.isValid())
|
||||||
|
{
|
||||||
|
needsDownload.add(manifest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keep.add(manifest.getJarFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old plugins
|
||||||
|
File[] files = RuneLite.PLUGINS_DIR.listFiles();
|
||||||
|
if (files != null)
|
||||||
|
{
|
||||||
|
for (File fi : files)
|
||||||
|
{
|
||||||
|
if (!keep.contains(fi))
|
||||||
|
{
|
||||||
|
fi.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int toDownload = needsDownload.stream().mapToInt(ExternalPluginManifest::getSize).sum();
|
||||||
|
int downloaded = 0;
|
||||||
|
|
||||||
|
for (ExternalPluginManifest manifest : needsDownload)
|
||||||
|
{
|
||||||
|
HttpUrl url = RuneLiteProperties.getPluginHubBase().newBuilder()
|
||||||
|
.addPathSegment(manifest.getInternalName())
|
||||||
|
.addPathSegment(manifest.getCommit() + ".jar")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response res = RuneLiteAPI.CLIENT.newCall(new Request.Builder().url(url).build()).execute())
|
||||||
|
{
|
||||||
|
int fdownloaded = downloaded;
|
||||||
|
downloaded += manifest.getSize();
|
||||||
|
HashingInputStream his = new HashingInputStream(Hashing.sha256(),
|
||||||
|
new CountingInputStream(res.body().byteStream(), i ->
|
||||||
|
SplashScreen.stage(splashStart + (splashLength * .2), splashStart + (splashLength * .8),
|
||||||
|
null, "Downloading " + manifest.getDisplayName(),
|
||||||
|
i + fdownloaded, toDownload, true)));
|
||||||
|
Files.asByteSink(manifest.getJarFile()).writeFrom(his);
|
||||||
|
if (!his.hash().toString().equals(manifest.getHash()))
|
||||||
|
{
|
||||||
|
throw new VerificationException("Plugin " + manifest.getInternalName() + " didn't match its hash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | VerificationException e)
|
||||||
|
{
|
||||||
|
externalPlugins.remove(manifest);
|
||||||
|
log.error("Unable to download external plugin \"{}\"", manifest.getInternalName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | VerificationException e)
|
||||||
|
{
|
||||||
|
log.error("Unable to download external plugins", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SplashScreen.stage(splashStart + (splashLength * .8), null, "Starting external plugins");
|
||||||
|
|
||||||
|
// TODO(abex): make sure the plugins get fully removed from the scheduler/eventbus/other managers (iterate and check classloader)
|
||||||
|
Set<ExternalPluginManifest> add = new HashSet<>();
|
||||||
|
for (ExternalPluginManifest ex : externalPlugins)
|
||||||
|
{
|
||||||
|
if (loadedExternalPlugins.removeAll(ex).size() <= 0)
|
||||||
|
{
|
||||||
|
add.add(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// list of loaded external plugins that aren't in the manifest
|
||||||
|
Collection<Plugin> remove = loadedExternalPlugins.values();
|
||||||
|
|
||||||
|
for (Plugin p : remove)
|
||||||
|
{
|
||||||
|
log.info("Stopping external plugin \"{}\"", p.getClass());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pluginManager.stopPlugin(p);
|
||||||
|
}
|
||||||
|
catch (PluginInstantiationException e)
|
||||||
|
{
|
||||||
|
log.warn("Unable to stop external plugin \"{}\"", p.getClass().getName(), e);
|
||||||
|
}
|
||||||
|
pluginManager.remove(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ExternalPluginManifest manifest : add)
|
||||||
|
{
|
||||||
|
// I think this can't happen, but just in case
|
||||||
|
if (!manifest.isValid())
|
||||||
|
{
|
||||||
|
log.warn("Invalid plugin for validated manifest: {}", manifest);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Loading external plugin \"{}\" version \"{}\" commit \"{}\"", manifest.getInternalName(), manifest.getVersion(), manifest.getCommit());
|
||||||
|
|
||||||
|
List<Plugin> newPlugins = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ClassLoader cl = new ExternalPluginClassLoader(manifest, new URL[]{manifest.getJarFile().toURI().toURL()});
|
||||||
|
List<Class<?>> clazzes = new ArrayList<>();
|
||||||
|
for (String className : manifest.getPlugins())
|
||||||
|
{
|
||||||
|
clazzes.add(cl.loadClass(className));
|
||||||
|
}
|
||||||
|
|
||||||
|
newPlugins = pluginManager.loadPlugins(clazzes, null);
|
||||||
|
if (!startup)
|
||||||
|
{
|
||||||
|
pluginManager.loadDefaultPluginConfiguration(newPlugins);
|
||||||
|
|
||||||
|
for (Plugin p : newPlugins)
|
||||||
|
{
|
||||||
|
pluginManager.startPlugin(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.warn("Unable to start or load external plugin \"{}\"", manifest.getInternalName(), e);
|
||||||
|
if (newPlugins != null)
|
||||||
|
{
|
||||||
|
for (Plugin p : newPlugins)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pluginManager.stopPlugin(p);
|
||||||
|
}
|
||||||
|
catch (Exception inner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
pluginManager.remove(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startup)
|
||||||
|
{
|
||||||
|
eventBus.post(new ExternalPluginsChanged(manifestList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!startup)
|
||||||
|
{
|
||||||
|
SplashScreen.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getInstalledExternalPlugins()
|
||||||
|
{
|
||||||
|
String externalPluginsStr = configManager.getConfiguration(RuneLiteConfig.GROUP_NAME, PLUGIN_LIST_KEY);
|
||||||
|
return Text.fromCSV(externalPluginsStr == null ? "" : externalPluginsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void install(String key)
|
||||||
|
{
|
||||||
|
Set<String> plugins = new HashSet<>(getInstalledExternalPlugins());
|
||||||
|
if (plugins.add(key))
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(RuneLiteConfig.GROUP_NAME, PLUGIN_LIST_KEY, Text.toCSV(plugins));
|
||||||
|
executor.submit(this::refreshPlugins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(String key)
|
||||||
|
{
|
||||||
|
Set<String> plugins = new HashSet<>(getInstalledExternalPlugins());
|
||||||
|
if (plugins.remove(key))
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(RuneLiteConfig.GROUP_NAME, PLUGIN_LIST_KEY, Text.toCSV(plugins));
|
||||||
|
executor.submit(this::refreshPlugins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update()
|
||||||
|
{
|
||||||
|
executor.submit(this::refreshPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExternalPluginManifest getExternalPluginManifest(Class<? extends Plugin> plugin)
|
||||||
|
{
|
||||||
|
ClassLoader cl = plugin.getClassLoader();
|
||||||
|
if (cl instanceof ExternalPluginClassLoader)
|
||||||
|
{
|
||||||
|
ExternalPluginClassLoader ecl = (ExternalPluginClassLoader) cl;
|
||||||
|
return ecl.getManifest();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadBuiltin(Class<? extends Plugin>... plugins)
|
||||||
|
{
|
||||||
|
builtinExternals = plugins;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.externalplugins;
|
||||||
|
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import net.runelite.client.RuneLite;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ExternalPluginManifest
|
||||||
|
{
|
||||||
|
private String internalName;
|
||||||
|
private String commit;
|
||||||
|
private String hash;
|
||||||
|
private int size;
|
||||||
|
private String[] plugins;
|
||||||
|
|
||||||
|
private String displayName;
|
||||||
|
private String version;
|
||||||
|
private String author;
|
||||||
|
private String description;
|
||||||
|
private String[] tags;
|
||||||
|
@EqualsAndHashCode.Exclude
|
||||||
|
private URL support;
|
||||||
|
private boolean hasIcon;
|
||||||
|
|
||||||
|
public boolean hasIcon()
|
||||||
|
{
|
||||||
|
return hasIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
File getJarFile()
|
||||||
|
{
|
||||||
|
return new File(RuneLite.PLUGINS_DIR, internalName + commit + ".jar");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isValid()
|
||||||
|
{
|
||||||
|
File file = getJarFile();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (file.exists())
|
||||||
|
{
|
||||||
|
String hash = Files.asByteSource(file).hash(Hashing.sha256()).toString();
|
||||||
|
if (this.hash.equals(hash))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
package net.runelite.client.plugins;
|
package net.runelite.client.plugins;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.graph.Graph;
|
import com.google.common.graph.Graph;
|
||||||
import com.google.common.graph.GraphBuilder;
|
import com.google.common.graph.GraphBuilder;
|
||||||
@@ -49,6 +48,7 @@ import java.util.Optional;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -61,7 +61,6 @@ import net.runelite.client.events.SessionClose;
|
|||||||
import net.runelite.client.events.SessionOpen;
|
import net.runelite.client.events.SessionOpen;
|
||||||
import net.runelite.client.RuneLite;
|
import net.runelite.client.RuneLite;
|
||||||
import net.runelite.client.config.Config;
|
import net.runelite.client.config.Config;
|
||||||
import net.runelite.client.config.ConfigGroup;
|
|
||||||
import net.runelite.client.config.ConfigManager;
|
import net.runelite.client.config.ConfigManager;
|
||||||
import net.runelite.client.config.RuneLiteConfig;
|
import net.runelite.client.config.RuneLiteConfig;
|
||||||
import net.runelite.client.eventbus.EventBus;
|
import net.runelite.client.eventbus.EventBus;
|
||||||
@@ -90,8 +89,6 @@ public class PluginManager
|
|||||||
private final Provider<GameEventManager> sceneTileManager;
|
private final Provider<GameEventManager> sceneTileManager;
|
||||||
private final List<Plugin> plugins = new CopyOnWriteArrayList<>();
|
private final List<Plugin> plugins = new CopyOnWriteArrayList<>();
|
||||||
private final List<Plugin> activePlugins = new CopyOnWriteArrayList<>();
|
private final List<Plugin> activePlugins = new CopyOnWriteArrayList<>();
|
||||||
private final String runeliteGroupName = RuneLiteConfig.class
|
|
||||||
.getAnnotation(ConfigGroup.class).value();
|
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
boolean isOutdated;
|
boolean isOutdated;
|
||||||
@@ -128,15 +125,22 @@ public class PluginManager
|
|||||||
|
|
||||||
private void refreshPlugins()
|
private void refreshPlugins()
|
||||||
{
|
{
|
||||||
loadDefaultPluginConfiguration();
|
loadDefaultPluginConfiguration(null);
|
||||||
getPlugins()
|
getPlugins()
|
||||||
.forEach(plugin -> executor.submit(() ->
|
.forEach(plugin -> executor.submit(() ->
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!startPlugin(plugin))
|
if (isPluginEnabled(plugin) != activePlugins.contains(plugin))
|
||||||
{
|
{
|
||||||
stopPlugin(plugin);
|
if (activePlugins.contains(plugin))
|
||||||
|
{
|
||||||
|
stopPlugin(plugin);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startPlugin(plugin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (PluginInstantiationException e)
|
catch (PluginInstantiationException e)
|
||||||
@@ -162,11 +166,15 @@ public class PluginManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Config> getPluginConfigProxies()
|
public List<Config> getPluginConfigProxies(Collection<Plugin> plugins)
|
||||||
{
|
{
|
||||||
List<Injector> injectors = new ArrayList<>();
|
List<Injector> injectors = new ArrayList<>();
|
||||||
injectors.add(RuneLite.getInjector());
|
if (plugins == null)
|
||||||
getPlugins().forEach(pl -> injectors.add(pl.getInjector()));
|
{
|
||||||
|
injectors.add(RuneLite.getInjector());
|
||||||
|
plugins = getPlugins();
|
||||||
|
}
|
||||||
|
plugins.forEach(pl -> injectors.add(pl.getInjector()));
|
||||||
|
|
||||||
List<Config> list = new ArrayList<>();
|
List<Config> list = new ArrayList<>();
|
||||||
for (Injector injector : injectors)
|
for (Injector injector : injectors)
|
||||||
@@ -185,20 +193,15 @@ public class PluginManager
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadDefaultPluginConfiguration()
|
public void loadDefaultPluginConfiguration(Collection<Plugin> plugins)
|
||||||
{
|
{
|
||||||
for (Object config : getPluginConfigProxies())
|
for (Object config : getPluginConfigProxies(plugins))
|
||||||
{
|
{
|
||||||
configManager.setDefaultConfiguration(config, false);
|
configManager.setDefaultConfiguration(config, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadCorePlugins() throws IOException
|
public void startPlugins()
|
||||||
{
|
|
||||||
plugins.addAll(scanAndInstantiate(getClass().getClassLoader(), PLUGIN_PACKAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startCorePlugins()
|
|
||||||
{
|
{
|
||||||
List<Plugin> scannedPlugins = new ArrayList<>(plugins);
|
List<Plugin> scannedPlugins = new ArrayList<>(plugins);
|
||||||
int loaded = 0;
|
int loaded = 0;
|
||||||
@@ -219,37 +222,41 @@ public class PluginManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Plugin> scanAndInstantiate(ClassLoader classLoader, String packageName) throws IOException
|
public void loadCorePlugins() throws IOException, PluginInstantiationException
|
||||||
{
|
{
|
||||||
SplashScreen.stage(.59, null, "Loading Plugins");
|
SplashScreen.stage(.59, null, "Loading Plugins");
|
||||||
|
ClassPath classPath = ClassPath.from(getClass().getClassLoader());
|
||||||
|
|
||||||
|
List<Class<?>> plugins = classPath.getTopLevelClassesRecursive(PLUGIN_PACKAGE).stream()
|
||||||
|
.map(ClassInfo::load)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
loadPlugins(plugins, (loaded, total) ->
|
||||||
|
SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, total, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Plugin> loadPlugins(List<Class<?>> plugins, BiConsumer<Integer, Integer> onPluginLoaded) throws PluginInstantiationException
|
||||||
|
{
|
||||||
MutableGraph<Class<? extends Plugin>> graph = GraphBuilder
|
MutableGraph<Class<? extends Plugin>> graph = GraphBuilder
|
||||||
.directed()
|
.directed()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
List<Plugin> scannedPlugins = new ArrayList<>();
|
for (Class<?> clazz : plugins)
|
||||||
ClassPath classPath = ClassPath.from(classLoader);
|
|
||||||
|
|
||||||
ImmutableSet<ClassInfo> classes = packageName == null ? classPath.getAllClasses()
|
|
||||||
: classPath.getTopLevelClassesRecursive(packageName);
|
|
||||||
for (ClassInfo classInfo : classes)
|
|
||||||
{
|
{
|
||||||
Class<?> clazz = classInfo.load();
|
|
||||||
PluginDescriptor pluginDescriptor = clazz.getAnnotation(PluginDescriptor.class);
|
PluginDescriptor pluginDescriptor = clazz.getAnnotation(PluginDescriptor.class);
|
||||||
|
|
||||||
if (pluginDescriptor == null)
|
if (pluginDescriptor == null)
|
||||||
{
|
{
|
||||||
if (clazz.getSuperclass() == Plugin.class)
|
if (clazz.getSuperclass() == Plugin.class)
|
||||||
{
|
{
|
||||||
log.warn("Class {} is a plugin, but has no plugin descriptor",
|
log.warn("Class {} is a plugin, but has no plugin descriptor", clazz);
|
||||||
clazz);
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clazz.getSuperclass() != Plugin.class)
|
if (clazz.getSuperclass() != Plugin.class)
|
||||||
{
|
{
|
||||||
log.warn("Class {} has plugin descriptor, but is not a plugin",
|
log.warn("Class {} has plugin descriptor, but is not a plugin", clazz);
|
||||||
clazz);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,20 +287,22 @@ public class PluginManager
|
|||||||
|
|
||||||
if (Graphs.hasCycle(graph))
|
if (Graphs.hasCycle(graph))
|
||||||
{
|
{
|
||||||
throw new RuntimeException("Plugin dependency graph contains a cycle!");
|
throw new PluginInstantiationException("Plugin dependency graph contains a cycle!");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Class<? extends Plugin>> sortedPlugins = topologicalSort(graph);
|
List<Class<? extends Plugin>> sortedPlugins = topologicalSort(graph);
|
||||||
sortedPlugins = Lists.reverse(sortedPlugins);
|
sortedPlugins = Lists.reverse(sortedPlugins);
|
||||||
|
|
||||||
int loaded = 0;
|
int loaded = 0;
|
||||||
|
List<Plugin> newPlugins = new ArrayList<>();
|
||||||
for (Class<? extends Plugin> pluginClazz : sortedPlugins)
|
for (Class<? extends Plugin> pluginClazz : sortedPlugins)
|
||||||
{
|
{
|
||||||
Plugin plugin;
|
Plugin plugin;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
plugin = instantiate(scannedPlugins, (Class<Plugin>) pluginClazz);
|
plugin = instantiate(this.plugins, (Class<Plugin>) pluginClazz);
|
||||||
scannedPlugins.add(plugin);
|
newPlugins.add(plugin);
|
||||||
|
this.plugins.add(plugin);
|
||||||
}
|
}
|
||||||
catch (PluginInstantiationException ex)
|
catch (PluginInstantiationException ex)
|
||||||
{
|
{
|
||||||
@@ -301,10 +310,13 @@ public class PluginManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
loaded++;
|
loaded++;
|
||||||
SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, sortedPlugins.size(), false);
|
if (onPluginLoaded != null)
|
||||||
|
{
|
||||||
|
onPluginLoaded.accept(loaded, sortedPlugins.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return scannedPlugins;
|
return newPlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean startPlugin(Plugin plugin) throws PluginInstantiationException
|
public synchronized boolean startPlugin(Plugin plugin) throws PluginInstantiationException
|
||||||
@@ -355,13 +367,11 @@ public class PluginManager
|
|||||||
|
|
||||||
public synchronized boolean stopPlugin(Plugin plugin) throws PluginInstantiationException
|
public synchronized boolean stopPlugin(Plugin plugin) throws PluginInstantiationException
|
||||||
{
|
{
|
||||||
if (!activePlugins.contains(plugin) || isPluginEnabled(plugin))
|
if (!activePlugins.remove(plugin))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
activePlugins.remove(plugin);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
unschedule(plugin);
|
unschedule(plugin);
|
||||||
@@ -395,13 +405,13 @@ public class PluginManager
|
|||||||
public void setPluginEnabled(Plugin plugin, boolean enabled)
|
public void setPluginEnabled(Plugin plugin, boolean enabled)
|
||||||
{
|
{
|
||||||
final String keyName = plugin.getClass().getSimpleName().toLowerCase();
|
final String keyName = plugin.getClass().getSimpleName().toLowerCase();
|
||||||
configManager.setConfiguration(runeliteGroupName, keyName, String.valueOf(enabled));
|
configManager.setConfiguration(RuneLiteConfig.GROUP_NAME, keyName, String.valueOf(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPluginEnabled(Plugin plugin)
|
public boolean isPluginEnabled(Plugin plugin)
|
||||||
{
|
{
|
||||||
final String keyName = plugin.getClass().getSimpleName().toLowerCase();
|
final String keyName = plugin.getClass().getSimpleName().toLowerCase();
|
||||||
final String value = configManager.getConfiguration(runeliteGroupName, keyName);
|
final String value = configManager.getConfiguration(RuneLiteConfig.GROUP_NAME, keyName);
|
||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
@@ -465,12 +475,12 @@ public class PluginManager
|
|||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(Plugin plugin)
|
public void add(Plugin plugin)
|
||||||
{
|
{
|
||||||
plugins.add(plugin);
|
plugins.add(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(Plugin plugin)
|
public void remove(Plugin plugin)
|
||||||
{
|
{
|
||||||
plugins.remove(plugin);
|
plugins.remove(plugin);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,12 +36,7 @@ import java.awt.event.ItemEvent;
|
|||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.ArrayList;
|
import javax.inject.Inject;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
@@ -49,6 +44,7 @@ import javax.swing.JCheckBox;
|
|||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
import javax.swing.JFormattedTextField;
|
import javax.swing.JFormattedTextField;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPasswordField;
|
import javax.swing.JPasswordField;
|
||||||
@@ -61,66 +57,59 @@ import javax.swing.SpinnerNumberModel;
|
|||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.event.DocumentEvent;
|
|
||||||
import javax.swing.event.DocumentListener;
|
|
||||||
import javax.swing.text.JTextComponent;
|
import javax.swing.text.JTextComponent;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.runelite.client.config.ChatColorConfig;
|
|
||||||
import net.runelite.client.config.Config;
|
|
||||||
import net.runelite.client.config.ConfigDescriptor;
|
import net.runelite.client.config.ConfigDescriptor;
|
||||||
import net.runelite.client.config.ConfigGroup;
|
|
||||||
import net.runelite.client.config.ConfigItem;
|
import net.runelite.client.config.ConfigItem;
|
||||||
import net.runelite.client.config.ConfigItemDescriptor;
|
import net.runelite.client.config.ConfigItemDescriptor;
|
||||||
import net.runelite.client.config.ConfigManager;
|
import net.runelite.client.config.ConfigManager;
|
||||||
import net.runelite.client.config.Keybind;
|
import net.runelite.client.config.Keybind;
|
||||||
import net.runelite.client.config.ModifierlessKeybind;
|
import net.runelite.client.config.ModifierlessKeybind;
|
||||||
import net.runelite.client.config.Range;
|
import net.runelite.client.config.Range;
|
||||||
import net.runelite.client.config.RuneLiteConfig;
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
import net.runelite.client.plugins.Plugin;
|
import net.runelite.client.events.ExternalPluginsChanged;
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
import net.runelite.client.events.PluginChanged;
|
||||||
import net.runelite.client.plugins.PluginInstantiationException;
|
import net.runelite.client.externalplugins.ExternalPluginManager;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManifest;
|
||||||
import net.runelite.client.plugins.PluginManager;
|
import net.runelite.client.plugins.PluginManager;
|
||||||
import net.runelite.client.ui.ColorScheme;
|
import net.runelite.client.ui.ColorScheme;
|
||||||
import net.runelite.client.ui.DynamicGridLayout;
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
import net.runelite.client.ui.PluginPanel;
|
import net.runelite.client.ui.PluginPanel;
|
||||||
import net.runelite.client.ui.components.ComboBoxListRenderer;
|
import net.runelite.client.ui.components.ComboBoxListRenderer;
|
||||||
import net.runelite.client.ui.components.IconButton;
|
|
||||||
import net.runelite.client.ui.components.IconTextField;
|
|
||||||
import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
|
import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
|
||||||
import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker;
|
import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker;
|
||||||
import net.runelite.client.util.ColorUtil;
|
import net.runelite.client.util.ColorUtil;
|
||||||
import net.runelite.client.util.ImageUtil;
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
import net.runelite.client.util.SwingUtil;
|
||||||
import net.runelite.client.util.Text;
|
import net.runelite.client.util.Text;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ConfigPanel extends PluginPanel
|
class ConfigPanel extends PluginPanel
|
||||||
{
|
{
|
||||||
private static final int SPINNER_FIELD_WIDTH = 6;
|
private static final int SPINNER_FIELD_WIDTH = 6;
|
||||||
private static final int SCROLLBAR_WIDTH = 17;
|
static final ImageIcon BACK_ICON;
|
||||||
private static final int OFFSET = 6;
|
static final ImageIcon BACK_ICON_HOVER;
|
||||||
private static final ImageIcon BACK_ICON;
|
|
||||||
private static final ImageIcon BACK_ICON_HOVER;
|
|
||||||
|
|
||||||
private static final String RUNELITE_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value();
|
private final FixedWidthPanel mainPanel;
|
||||||
private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins";
|
private final JLabel title;
|
||||||
private static final String RUNELITE_PLUGIN = "RuneLite";
|
private final PluginToggleButton pluginToggle;
|
||||||
private static final String CHAT_COLOR_PLUGIN = "Chat Color";
|
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
@Inject
|
||||||
private final ConfigManager configManager;
|
private PluginListPanel pluginList;
|
||||||
private final ScheduledExecutorService executorService;
|
|
||||||
private final RuneLiteConfig runeLiteConfig;
|
|
||||||
private final ChatColorConfig chatColorConfig;
|
|
||||||
private final ColorPickerManager colorPickerManager;
|
|
||||||
private final List<PluginListItem> pluginList = new ArrayList<>();
|
|
||||||
|
|
||||||
private final IconTextField searchBar = new IconTextField();
|
@Inject
|
||||||
private final JPanel topPanel;
|
private ConfigManager configManager;
|
||||||
private final JPanel mainPanel;
|
|
||||||
private final JScrollPane scrollPane;
|
|
||||||
|
|
||||||
private boolean showingPluginList = true;
|
@Inject
|
||||||
private int scrollBarPosition = 0;
|
private PluginManager pluginManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ExternalPluginManager externalPluginManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ColorPickerManager colorPickerManager;
|
||||||
|
|
||||||
|
private PluginConfigurationDescriptor pluginConfig = null;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
@@ -129,48 +118,16 @@ public class ConfigPanel extends PluginPanel
|
|||||||
BACK_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backIcon, -100));
|
BACK_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backIcon, -100));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigPanel(PluginManager pluginManager, ConfigManager configManager, ScheduledExecutorService executorService,
|
public ConfigPanel()
|
||||||
RuneLiteConfig runeLiteConfig, ChatColorConfig chatColorConfig, ColorPickerManager colorPickerManager)
|
|
||||||
{
|
{
|
||||||
super(false);
|
super(false);
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
this.configManager = configManager;
|
|
||||||
this.executorService = executorService;
|
|
||||||
this.runeLiteConfig = runeLiteConfig;
|
|
||||||
this.chatColorConfig = chatColorConfig;
|
|
||||||
this.colorPickerManager = colorPickerManager;
|
|
||||||
|
|
||||||
searchBar.setIcon(IconTextField.Icon.SEARCH);
|
|
||||||
searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30));
|
|
||||||
searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
|
||||||
searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
|
|
||||||
searchBar.getDocument().addDocumentListener(new DocumentListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void insertUpdate(DocumentEvent e)
|
|
||||||
{
|
|
||||||
onSearchBarChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeUpdate(DocumentEvent e)
|
|
||||||
{
|
|
||||||
onSearchBarChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changedUpdate(DocumentEvent e)
|
|
||||||
{
|
|
||||||
onSearchBarChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
topPanel = new JPanel();
|
JPanel topPanel = new JPanel();
|
||||||
topPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
topPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
topPanel.setLayout(new BorderLayout(0, OFFSET));
|
topPanel.setLayout(new BorderLayout(0, BORDER_OFFSET));
|
||||||
add(topPanel, BorderLayout.NORTH);
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
mainPanel = new FixedWidthPanel();
|
mainPanel = new FixedWidthPanel();
|
||||||
@@ -182,139 +139,75 @@ public class ConfigPanel extends PluginPanel
|
|||||||
northPanel.setLayout(new BorderLayout());
|
northPanel.setLayout(new BorderLayout());
|
||||||
northPanel.add(mainPanel, BorderLayout.NORTH);
|
northPanel.add(mainPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
scrollPane = new JScrollPane(northPanel);
|
JScrollPane scrollPane = new JScrollPane(northPanel);
|
||||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
add(scrollPane, BorderLayout.CENTER);
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
initializePluginList();
|
JButton topPanelBackButton = new JButton(BACK_ICON);
|
||||||
refreshPluginList();
|
topPanelBackButton.setRolloverIcon(BACK_ICON_HOVER);
|
||||||
}
|
SwingUtil.removeButtonDecorations(topPanelBackButton);
|
||||||
|
|
||||||
private void initializePluginList()
|
|
||||||
{
|
|
||||||
final List<String> pinnedPlugins = getPinnedPluginNames();
|
|
||||||
|
|
||||||
// populate pluginList with all non-hidden plugins
|
|
||||||
pluginManager.getPlugins().stream()
|
|
||||||
.filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden())
|
|
||||||
.forEach(plugin ->
|
|
||||||
{
|
|
||||||
final PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class);
|
|
||||||
final Config config = pluginManager.getPluginConfigProxy(plugin);
|
|
||||||
final ConfigDescriptor configDescriptor = config == null ? null : configManager.getConfigDescriptor(config);
|
|
||||||
|
|
||||||
final PluginListItem listItem = new PluginListItem(this, plugin, descriptor, config, configDescriptor);
|
|
||||||
listItem.setPinned(pinnedPlugins.contains(listItem.getName()));
|
|
||||||
pluginList.add(listItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
// add special entries for core client configurations
|
|
||||||
final PluginListItem runeLite = new PluginListItem(this, runeLiteConfig,
|
|
||||||
configManager.getConfigDescriptor(runeLiteConfig),
|
|
||||||
RUNELITE_PLUGIN, "RuneLite client settings", "client");
|
|
||||||
runeLite.setPinned(pinnedPlugins.contains(RUNELITE_PLUGIN));
|
|
||||||
pluginList.add(runeLite);
|
|
||||||
|
|
||||||
final PluginListItem chatColor = new PluginListItem(this, chatColorConfig,
|
|
||||||
configManager.getConfigDescriptor(chatColorConfig),
|
|
||||||
CHAT_COLOR_PLUGIN, "Recolor chat text", "colour", "messages");
|
|
||||||
chatColor.setPinned(pinnedPlugins.contains(CHAT_COLOR_PLUGIN));
|
|
||||||
pluginList.add(chatColor);
|
|
||||||
|
|
||||||
pluginList.sort(Comparator.comparing(PluginListItem::getName));
|
|
||||||
}
|
|
||||||
|
|
||||||
void refreshPluginList()
|
|
||||||
{
|
|
||||||
// update enabled / disabled status of all items
|
|
||||||
pluginList.forEach(listItem ->
|
|
||||||
{
|
|
||||||
final Plugin plugin = listItem.getPlugin();
|
|
||||||
if (plugin != null)
|
|
||||||
{
|
|
||||||
listItem.setPluginEnabled(pluginManager.isPluginEnabled(plugin));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (showingPluginList)
|
|
||||||
{
|
|
||||||
openConfigList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void openConfigList()
|
|
||||||
{
|
|
||||||
if (showingPluginList)
|
|
||||||
{
|
|
||||||
scrollBarPosition = scrollPane.getVerticalScrollBar().getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
showingPluginList = true;
|
|
||||||
|
|
||||||
topPanel.removeAll();
|
|
||||||
mainPanel.removeAll();
|
|
||||||
topPanel.add(searchBar, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
onSearchBarChanged();
|
|
||||||
searchBar.requestFocusInWindow();
|
|
||||||
validate();
|
|
||||||
scrollPane.getVerticalScrollBar().setValue(scrollBarPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSearchBarChanged()
|
|
||||||
{
|
|
||||||
final String text = searchBar.getText();
|
|
||||||
|
|
||||||
pluginList.forEach(mainPanel::remove);
|
|
||||||
|
|
||||||
showMatchingPlugins(true, text);
|
|
||||||
showMatchingPlugins(false, text);
|
|
||||||
|
|
||||||
revalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showMatchingPlugins(boolean pinned, String text)
|
|
||||||
{
|
|
||||||
if (text.isEmpty())
|
|
||||||
{
|
|
||||||
pluginList.stream().filter(item -> pinned == item.isPinned()).forEach(mainPanel::add);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String[] searchTerms = text.toLowerCase().split(" ");
|
|
||||||
pluginList.forEach(listItem ->
|
|
||||||
{
|
|
||||||
if (pinned == listItem.isPinned() && listItem.matchesSearchTerms(searchTerms))
|
|
||||||
{
|
|
||||||
mainPanel.add(listItem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void openGroupConfigPanel(PluginListItem listItem, Config config, ConfigDescriptor cd)
|
|
||||||
{
|
|
||||||
showingPluginList = false;
|
|
||||||
|
|
||||||
scrollBarPosition = scrollPane.getVerticalScrollBar().getValue();
|
|
||||||
topPanel.removeAll();
|
|
||||||
mainPanel.removeAll();
|
|
||||||
|
|
||||||
final IconButton topPanelBackButton = new IconButton(BACK_ICON, BACK_ICON_HOVER);
|
|
||||||
topPanelBackButton.setPreferredSize(new Dimension(22, 0));
|
topPanelBackButton.setPreferredSize(new Dimension(22, 0));
|
||||||
topPanelBackButton.setBorder(new EmptyBorder(0, 0, 0, 5));
|
topPanelBackButton.setBorder(new EmptyBorder(0, 0, 0, 5));
|
||||||
topPanelBackButton.addActionListener(e -> openConfigList());
|
topPanelBackButton.addActionListener(e -> pluginList.getMuxer().popState());
|
||||||
topPanelBackButton.setToolTipText("Back");
|
topPanelBackButton.setToolTipText("Back");
|
||||||
topPanel.add(topPanelBackButton, BorderLayout.WEST);
|
topPanel.add(topPanelBackButton, BorderLayout.WEST);
|
||||||
|
|
||||||
topPanel.add(listItem.getConfigToggleButton(), BorderLayout.EAST);
|
pluginToggle = new PluginToggleButton();
|
||||||
|
topPanel.add(pluginToggle, BorderLayout.EAST);
|
||||||
String name = listItem.getName();
|
title = new JLabel();
|
||||||
JLabel title = new JLabel(name);
|
|
||||||
title.setForeground(Color.WHITE);
|
title.setForeground(Color.WHITE);
|
||||||
title.setToolTipText("<html>" + name + ":<br>" + listItem.getDescription() + "</html>");
|
|
||||||
PluginListItem.addLabelPopupMenu(title, PluginListItem.wikiLinkMenuItem(listItem.getName()));
|
|
||||||
topPanel.add(title);
|
|
||||||
|
|
||||||
|
topPanel.add(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(PluginConfigurationDescriptor pluginConfig)
|
||||||
|
{
|
||||||
|
assert this.pluginConfig == null;
|
||||||
|
this.pluginConfig = pluginConfig;
|
||||||
|
|
||||||
|
String name = pluginConfig.getName();
|
||||||
|
title.setText(name);
|
||||||
|
title.setForeground(Color.WHITE);
|
||||||
|
title.setToolTipText("<html>" + name + ":<br>" + pluginConfig.getDescription() + "</html>");
|
||||||
|
|
||||||
|
ExternalPluginManifest mf = pluginConfig.getExternalPluginManifest();
|
||||||
|
JMenuItem uninstallItem = null;
|
||||||
|
if (mf != null)
|
||||||
|
{
|
||||||
|
uninstallItem = new JMenuItem("Uninstall");
|
||||||
|
uninstallItem.addActionListener(ev -> externalPluginManager.remove(mf.getInternalName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginListItem.addLabelPopupMenu(title, pluginConfig.createSupportMenuItem(), uninstallItem);
|
||||||
|
|
||||||
|
if (pluginConfig.getPlugin() != null)
|
||||||
|
{
|
||||||
|
pluginToggle.setSelected(pluginManager.isPluginEnabled(pluginConfig.getPlugin()));
|
||||||
|
pluginToggle.addItemListener(i ->
|
||||||
|
{
|
||||||
|
if (pluginToggle.isSelected())
|
||||||
|
{
|
||||||
|
pluginList.startPlugin(pluginConfig.getPlugin());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginList.stopPlugin(pluginConfig.getPlugin());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginToggle.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuild()
|
||||||
|
{
|
||||||
|
mainPanel.removeAll();
|
||||||
|
|
||||||
|
ConfigDescriptor cd = pluginConfig.getConfigDescriptor();
|
||||||
for (ConfigItemDescriptor cid : cd.getItems())
|
for (ConfigItemDescriptor cid : cd.getItems())
|
||||||
{
|
{
|
||||||
if (cid.getItem().hidden())
|
if (cid.getItem().hidden())
|
||||||
@@ -325,7 +218,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
JPanel item = new JPanel();
|
JPanel item = new JPanel();
|
||||||
item.setLayout(new BorderLayout());
|
item.setLayout(new BorderLayout());
|
||||||
item.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
|
item.setMinimumSize(new Dimension(PANEL_WIDTH, 0));
|
||||||
name = cid.getItem().name();
|
String name = cid.getItem().name();
|
||||||
JLabel configEntryName = new JLabel(name);
|
JLabel configEntryName = new JLabel(name);
|
||||||
configEntryName.setForeground(Color.WHITE);
|
configEntryName.setForeground(Color.WHITE);
|
||||||
configEntryName.setToolTipText("<html>" + name + ":<br>" + cid.getItem().description() + "</html>");
|
configEntryName.setToolTipText("<html>" + name + ":<br>" + cid.getItem().description() + "</html>");
|
||||||
@@ -336,7 +229,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
JCheckBox checkbox = new JCheckBox();
|
JCheckBox checkbox = new JCheckBox();
|
||||||
checkbox.setBackground(ColorScheme.LIGHT_GRAY_COLOR);
|
checkbox.setBackground(ColorScheme.LIGHT_GRAY_COLOR);
|
||||||
checkbox.setSelected(Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())));
|
checkbox.setSelected(Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName())));
|
||||||
checkbox.addActionListener(ae -> changeConfiguration(listItem, config, checkbox, cd, cid));
|
checkbox.addActionListener(ae -> changeConfiguration(checkbox, cd, cid));
|
||||||
|
|
||||||
item.add(checkbox, BorderLayout.EAST);
|
item.add(checkbox, BorderLayout.EAST);
|
||||||
}
|
}
|
||||||
@@ -361,7 +254,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
Component editor = spinner.getEditor();
|
Component editor = spinner.getEditor();
|
||||||
JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField();
|
JFormattedTextField spinnerTextField = ((JSpinner.DefaultEditor) editor).getTextField();
|
||||||
spinnerTextField.setColumns(SPINNER_FIELD_WIDTH);
|
spinnerTextField.setColumns(SPINNER_FIELD_WIDTH);
|
||||||
spinner.addChangeListener(ce -> changeConfiguration(listItem, config, spinner, cd, cid));
|
spinner.addChangeListener(ce -> changeConfiguration(spinner, cd, cid));
|
||||||
|
|
||||||
item.add(spinner, BorderLayout.EAST);
|
item.add(spinner, BorderLayout.EAST);
|
||||||
}
|
}
|
||||||
@@ -390,7 +283,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
@Override
|
@Override
|
||||||
public void focusLost(FocusEvent e)
|
public void focusLost(FocusEvent e)
|
||||||
{
|
{
|
||||||
changeConfiguration(listItem, config, textField, cd, cid);
|
changeConfiguration(textField, cd, cid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -433,7 +326,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
colorPickerBtn.setBackground(c);
|
colorPickerBtn.setBackground(c);
|
||||||
colorPickerBtn.setText(ColorUtil.toHexColor(c).toUpperCase());
|
colorPickerBtn.setText(ColorUtil.toHexColor(c).toUpperCase());
|
||||||
});
|
});
|
||||||
colorPicker.setOnClose(c -> changeConfiguration(listItem, config, colorPicker, cd, cid));
|
colorPicker.setOnClose(c -> changeConfiguration(colorPicker, cd, cid));
|
||||||
colorPicker.setVisible(true);
|
colorPicker.setVisible(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -499,7 +392,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
{
|
{
|
||||||
if (e.getStateChange() == ItemEvent.SELECTED)
|
if (e.getStateChange() == ItemEvent.SELECTED)
|
||||||
{
|
{
|
||||||
changeConfiguration(listItem, config, box, cd, cid);
|
changeConfiguration(box, cd, cid);
|
||||||
box.setToolTipText(Text.titleCase((Enum) box.getSelectedItem()));
|
box.setToolTipText(Text.titleCase((Enum) box.getSelectedItem()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -519,7 +412,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
@Override
|
@Override
|
||||||
public void focusLost(FocusEvent e)
|
public void focusLost(FocusEvent e)
|
||||||
{
|
{
|
||||||
changeConfiguration(listItem, config, button, cd, cid);
|
changeConfiguration(button, cd, cid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -538,23 +431,21 @@ public class ConfigPanel extends PluginPanel
|
|||||||
|
|
||||||
if (result == JOptionPane.YES_OPTION)
|
if (result == JOptionPane.YES_OPTION)
|
||||||
{
|
{
|
||||||
configManager.setDefaultConfiguration(config, true);
|
configManager.setDefaultConfiguration(pluginConfig.getConfig(), true);
|
||||||
|
|
||||||
// Reload configuration panel
|
rebuild();
|
||||||
openGroupConfigPanel(listItem, config, cd);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mainPanel.add(resetButton);
|
mainPanel.add(resetButton);
|
||||||
|
|
||||||
JButton backButton = new JButton("Back");
|
JButton backButton = new JButton("Back");
|
||||||
backButton.addActionListener(e -> openConfigList());
|
backButton.addActionListener(e -> pluginList.getMuxer().popState());
|
||||||
mainPanel.add(backButton);
|
mainPanel.add(backButton);
|
||||||
|
|
||||||
revalidate();
|
revalidate();
|
||||||
scrollPane.getVerticalScrollBar().setValue(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeConfiguration(PluginListItem listItem, Config config, Component component, ConfigDescriptor cd, ConfigItemDescriptor cid)
|
private void changeConfiguration(Component component, ConfigDescriptor cd, ConfigItemDescriptor cid)
|
||||||
{
|
{
|
||||||
final ConfigItem configItem = cid.getItem();
|
final ConfigItem configItem = cid.getItem();
|
||||||
|
|
||||||
@@ -566,7 +457,7 @@ public class ConfigPanel extends PluginPanel
|
|||||||
|
|
||||||
if (result != JOptionPane.YES_OPTION)
|
if (result != JOptionPane.YES_OPTION)
|
||||||
{
|
{
|
||||||
openGroupConfigPanel(listItem, config, cd);
|
rebuild();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,102 +494,32 @@ public class ConfigPanel extends PluginPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startPlugin(Plugin plugin, PluginListItem listItem)
|
|
||||||
{
|
|
||||||
executorService.submit(() ->
|
|
||||||
{
|
|
||||||
pluginManager.setPluginEnabled(plugin, true);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
pluginManager.startPlugin(plugin);
|
|
||||||
}
|
|
||||||
catch (PluginInstantiationException ex)
|
|
||||||
{
|
|
||||||
log.warn("Error when starting plugin {}", plugin.getClass().getSimpleName(), ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
listItem.setPluginEnabled(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopPlugin(Plugin plugin, PluginListItem listItem)
|
|
||||||
{
|
|
||||||
executorService.submit(() ->
|
|
||||||
{
|
|
||||||
pluginManager.setPluginEnabled(plugin, false);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
pluginManager.stopPlugin(plugin);
|
|
||||||
}
|
|
||||||
catch (PluginInstantiationException ex)
|
|
||||||
{
|
|
||||||
log.warn("Error when stopping plugin {}", plugin.getClass().getSimpleName(), ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
listItem.setPluginEnabled(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getPinnedPluginNames()
|
|
||||||
{
|
|
||||||
final String config = configManager.getConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY);
|
|
||||||
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Text.fromCSV(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
void savePinnedPlugins()
|
|
||||||
{
|
|
||||||
final String value = pluginList.stream()
|
|
||||||
.filter(PluginListItem::isPinned)
|
|
||||||
.map(PluginListItem::getName)
|
|
||||||
.collect(Collectors.joining(","));
|
|
||||||
|
|
||||||
configManager.setConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void openConfigurationPanel(String configGroup)
|
|
||||||
{
|
|
||||||
for (PluginListItem pluginListItem : pluginList)
|
|
||||||
{
|
|
||||||
if (pluginListItem.getName().equals(configGroup))
|
|
||||||
{
|
|
||||||
openGroupConfigPanel(pluginListItem, pluginListItem.getConfig(), pluginListItem.getConfigDescriptor());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivate()
|
|
||||||
{
|
|
||||||
super.onActivate();
|
|
||||||
|
|
||||||
if (searchBar.getParent() != null)
|
|
||||||
{
|
|
||||||
searchBar.requestFocusInWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dimension getPreferredSize()
|
public Dimension getPreferredSize()
|
||||||
{
|
{
|
||||||
return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height);
|
return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FixedWidthPanel extends JPanel
|
@Subscribe
|
||||||
|
public void onPluginChanged(PluginChanged event)
|
||||||
{
|
{
|
||||||
@Override
|
if (event.getPlugin() == this.pluginConfig.getPlugin())
|
||||||
public Dimension getPreferredSize()
|
|
||||||
{
|
{
|
||||||
return new Dimension(PANEL_WIDTH, super.getPreferredSize().height);
|
SwingUtilities.invokeLater(() ->
|
||||||
|
{
|
||||||
|
pluginToggle.setSelected(event.isLoaded());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onExternalPluginsChanged(ExternalPluginsChanged ev)
|
||||||
|
{
|
||||||
|
if (pluginManager.getPlugins().stream()
|
||||||
|
.noneMatch(p -> p == this.pluginConfig.getPlugin()))
|
||||||
|
{
|
||||||
|
pluginList.getMuxer().popState();
|
||||||
|
}
|
||||||
|
SwingUtilities.invokeLater(this::rebuild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
package net.runelite.client.plugins.config;
|
package net.runelite.client.plugins.config;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import net.runelite.api.MenuAction;
|
import net.runelite.api.MenuAction;
|
||||||
import net.runelite.client.config.ChatColorConfig;
|
import net.runelite.client.config.ChatColorConfig;
|
||||||
@@ -34,13 +34,10 @@ import net.runelite.client.config.ConfigManager;
|
|||||||
import net.runelite.client.config.RuneLiteConfig;
|
import net.runelite.client.config.RuneLiteConfig;
|
||||||
import net.runelite.client.eventbus.Subscribe;
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
import net.runelite.client.events.OverlayMenuClicked;
|
import net.runelite.client.events.OverlayMenuClicked;
|
||||||
import net.runelite.client.events.PluginChanged;
|
|
||||||
import net.runelite.client.plugins.Plugin;
|
import net.runelite.client.plugins.Plugin;
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
import net.runelite.client.plugins.PluginManager;
|
|
||||||
import net.runelite.client.ui.ClientToolbar;
|
import net.runelite.client.ui.ClientToolbar;
|
||||||
import net.runelite.client.ui.NavigationButton;
|
import net.runelite.client.ui.NavigationButton;
|
||||||
import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
|
|
||||||
import net.runelite.client.ui.overlay.Overlay;
|
import net.runelite.client.ui.overlay.Overlay;
|
||||||
import net.runelite.client.ui.overlay.OverlayMenuEntry;
|
import net.runelite.client.ui.overlay.OverlayMenuEntry;
|
||||||
import net.runelite.client.util.ImageUtil;
|
import net.runelite.client.util.ImageUtil;
|
||||||
@@ -55,31 +52,35 @@ public class ConfigPlugin extends Plugin
|
|||||||
@Inject
|
@Inject
|
||||||
private ClientToolbar clientToolbar;
|
private ClientToolbar clientToolbar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Provider<PluginListPanel> pluginListPanelProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ConfigManager configManager;
|
private ConfigManager configManager;
|
||||||
|
|
||||||
@Inject
|
|
||||||
private PluginManager pluginManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private ScheduledExecutorService executorService;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private RuneLiteConfig runeLiteConfig;
|
private RuneLiteConfig runeLiteConfig;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ChatColorConfig chatColorConfig;
|
private ChatColorConfig chatColorConfig;
|
||||||
|
|
||||||
@Inject
|
private PluginListPanel pluginListPanel;
|
||||||
private ColorPickerManager colorPickerManager;
|
|
||||||
|
|
||||||
private ConfigPanel configPanel;
|
|
||||||
private NavigationButton navButton;
|
private NavigationButton navButton;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void startUp() throws Exception
|
protected void startUp() throws Exception
|
||||||
{
|
{
|
||||||
configPanel = new ConfigPanel(pluginManager, configManager, executorService, runeLiteConfig, chatColorConfig, colorPickerManager);
|
pluginListPanel = pluginListPanelProvider.get();
|
||||||
|
pluginListPanel.addFakePlugin(new PluginConfigurationDescriptor(
|
||||||
|
"RuneLite", "RuneLite client settings", new String[]{"client"},
|
||||||
|
null, runeLiteConfig, configManager.getConfigDescriptor(runeLiteConfig)
|
||||||
|
),
|
||||||
|
new PluginConfigurationDescriptor(
|
||||||
|
"Chat Color", "Recolor chat text", new String[]{"colour", "messages"},
|
||||||
|
null, chatColorConfig, configManager.getConfigDescriptor(chatColorConfig)
|
||||||
|
));
|
||||||
|
pluginListPanel.rebuildPluginList();
|
||||||
|
|
||||||
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "config_icon.png");
|
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "config_icon.png");
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ public class ConfigPlugin extends Plugin
|
|||||||
.tooltip("Configuration")
|
.tooltip("Configuration")
|
||||||
.icon(icon)
|
.icon(icon)
|
||||||
.priority(0)
|
.priority(0)
|
||||||
.panel(configPanel)
|
.panel(pluginListPanel.getMuxer())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
clientToolbar.addNavigation(navButton);
|
clientToolbar.addNavigation(navButton);
|
||||||
@@ -99,12 +100,6 @@ public class ConfigPlugin extends Plugin
|
|||||||
clientToolbar.removeNavigation(navButton);
|
clientToolbar.removeNavigation(navButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onPluginChanged(PluginChanged event)
|
|
||||||
{
|
|
||||||
SwingUtilities.invokeLater(configPanel::refreshPluginList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked)
|
public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked)
|
||||||
{
|
{
|
||||||
@@ -126,7 +121,7 @@ public class ConfigPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
navButton.getOnSelect().run();
|
navButton.getOnSelect().run();
|
||||||
}
|
}
|
||||||
configPanel.openConfigurationPanel(descriptor.name());
|
pluginListPanel.openConfigurationPanel(descriptor.name());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||||
|
* 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.plugins.config;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import net.runelite.client.ui.PluginPanel;
|
||||||
|
|
||||||
|
class FixedWidthPanel extends JPanel
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize()
|
||||||
|
{
|
||||||
|
return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ import lombok.Getter;
|
|||||||
import net.runelite.client.config.Keybind;
|
import net.runelite.client.config.Keybind;
|
||||||
import net.runelite.client.config.ModifierlessKeybind;
|
import net.runelite.client.config.ModifierlessKeybind;
|
||||||
|
|
||||||
public class HotkeyButton extends JButton
|
class HotkeyButton extends JButton
|
||||||
{
|
{
|
||||||
@Getter
|
@Getter
|
||||||
private Keybind value;
|
private Keybind value;
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.plugins.config;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import lombok.Value;
|
||||||
|
import net.runelite.client.config.Config;
|
||||||
|
import net.runelite.client.config.ConfigDescriptor;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManager;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManifest;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.util.LinkBrowser;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
class PluginConfigurationDescriptor
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final String description;
|
||||||
|
private final String[] tags;
|
||||||
|
|
||||||
|
// Can be null if its not an actual plugin (RuneLite / ChatColors)
|
||||||
|
@Nullable
|
||||||
|
private final Plugin plugin;
|
||||||
|
|
||||||
|
// Can be null if it has no more configuration than the on/off toggle
|
||||||
|
@Nullable
|
||||||
|
private final Config config;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final ConfigDescriptor configDescriptor;
|
||||||
|
|
||||||
|
boolean hasConfigurables()
|
||||||
|
{
|
||||||
|
return configDescriptor != null && !configDescriptor.getItems().stream().allMatch(item -> item.getItem().hidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a menu item for linking to a support page for the plugin
|
||||||
|
*
|
||||||
|
* @return A {@link JMenuItem} which opens the plugin's wiki page URL in the browser when clicked
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
JMenuItem createSupportMenuItem()
|
||||||
|
{
|
||||||
|
ExternalPluginManifest mf = getExternalPluginManifest();
|
||||||
|
if (mf != null)
|
||||||
|
{
|
||||||
|
if (mf.getSupport() == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JMenuItem menuItem = new JMenuItem("Support");
|
||||||
|
menuItem.addActionListener(e -> LinkBrowser.browse(mf.getSupport().toString()));
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
JMenuItem menuItem = new JMenuItem("Wiki");
|
||||||
|
menuItem.addActionListener(e -> LinkBrowser.browse("https://github.com/runelite/runelite/wiki/" + name.replace(' ', '-')));
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ExternalPluginManifest getExternalPluginManifest()
|
||||||
|
{
|
||||||
|
if (plugin == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExternalPluginManager.getExternalPluginManifest(plugin.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,559 @@
|
|||||||
|
/*
|
||||||
|
* 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.plugins.config;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashMultimap;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.ToDoubleFunction;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.GroupLayout;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.LayoutStyle;
|
||||||
|
import javax.swing.ScrollPaneConstants;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.config.Config;
|
||||||
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
|
import net.runelite.client.events.ExternalPluginsChanged;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginClient;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManager;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManifest;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginManager;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.ui.PluginPanel;
|
||||||
|
import net.runelite.client.ui.components.IconTextField;
|
||||||
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
import net.runelite.client.util.LinkBrowser;
|
||||||
|
import net.runelite.client.util.SwingUtil;
|
||||||
|
import net.runelite.client.util.VerificationException;
|
||||||
|
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Singleton
|
||||||
|
class PluginHubPanel extends PluginPanel
|
||||||
|
{
|
||||||
|
private static final ImageIcon MISSING_ICON;
|
||||||
|
private static final ImageIcon HELP_ICON;
|
||||||
|
private static final ImageIcon HELP_ICON_HOVER;
|
||||||
|
private static final ImageIcon CONFIGURE_ICON;
|
||||||
|
private static final ImageIcon CONFIGURE_ICON_HOVER;
|
||||||
|
private static final Pattern SPACES = Pattern.compile(" +");
|
||||||
|
private static final JaroWinklerDistance DISTANCE = new JaroWinklerDistance();
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
BufferedImage missingIcon = ImageUtil.getResourceStreamFromClass(PluginHubPanel.class, "pluginhub_missingicon.png");
|
||||||
|
MISSING_ICON = new ImageIcon(missingIcon);
|
||||||
|
|
||||||
|
BufferedImage helpIcon = ImageUtil.getResourceStreamFromClass(PluginHubPanel.class, "pluginhub_help.png");
|
||||||
|
HELP_ICON = new ImageIcon(helpIcon);
|
||||||
|
HELP_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(helpIcon, -100));
|
||||||
|
|
||||||
|
BufferedImage configureIcon = ImageUtil.getResourceStreamFromClass(PluginHubPanel.class, "pluginhub_configure.png");
|
||||||
|
CONFIGURE_ICON = new ImageIcon(configureIcon);
|
||||||
|
CONFIGURE_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(configureIcon, -100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PluginItem extends JPanel
|
||||||
|
{
|
||||||
|
private static final int HEIGHT = 70;
|
||||||
|
private static final int ICON_WIDTH = 48;
|
||||||
|
private static final int BOTTOM_LINE_HEIGHT = 16;
|
||||||
|
static final float MIN_FILTER_SCORE = .8f;
|
||||||
|
|
||||||
|
private final ExternalPluginManifest manifest;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final boolean installed;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private float filter;
|
||||||
|
|
||||||
|
PluginItem(ExternalPluginManifest newManifest, Collection<Plugin> loadedPlugins, boolean installed)
|
||||||
|
{
|
||||||
|
ExternalPluginManifest loaded = null;
|
||||||
|
if (!loadedPlugins.isEmpty())
|
||||||
|
{
|
||||||
|
loaded = ExternalPluginManager.getExternalPluginManifest(loadedPlugins.iterator().next().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest = newManifest == null ? loaded : newManifest;
|
||||||
|
this.installed = installed;
|
||||||
|
|
||||||
|
setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
setOpaque(true);
|
||||||
|
|
||||||
|
GroupLayout layout = new GroupLayout(this);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
JLabel pluginName = new JLabel(manifest.getDisplayName());
|
||||||
|
pluginName.setFont(FontManager.getRunescapeBoldFont());
|
||||||
|
pluginName.setToolTipText(manifest.getDisplayName());
|
||||||
|
|
||||||
|
JLabel author = new JLabel(manifest.getAuthor());
|
||||||
|
author.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
author.setToolTipText(manifest.getAuthor());
|
||||||
|
|
||||||
|
JLabel version = new JLabel(manifest.getVersion());
|
||||||
|
version.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
version.setToolTipText(manifest.getVersion());
|
||||||
|
|
||||||
|
JLabel description = new JLabel(manifest.getDescription());
|
||||||
|
description.setToolTipText(manifest.getDescription());
|
||||||
|
|
||||||
|
JLabel icon = new JLabel();
|
||||||
|
icon.setHorizontalAlignment(JLabel.CENTER);
|
||||||
|
icon.setIcon(MISSING_ICON);
|
||||||
|
if (manifest.hasIcon())
|
||||||
|
{
|
||||||
|
executor.submit(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BufferedImage img = externalPluginClient.downloadIcon(manifest);
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() ->
|
||||||
|
{
|
||||||
|
icon.setIcon(new ImageIcon(img));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
log.info("Cannot download icon for plugin \"{}\"", manifest.getInternalName(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
JButton help = new JButton(HELP_ICON);
|
||||||
|
help.setRolloverIcon(HELP_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(help);
|
||||||
|
help.setToolTipText("Help");
|
||||||
|
help.setBorder(null);
|
||||||
|
if (manifest.getSupport() == null)
|
||||||
|
{
|
||||||
|
help.setVisible(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
help.addActionListener(ev -> LinkBrowser.browse(manifest.getSupport().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
JButton configure = new JButton(CONFIGURE_ICON);
|
||||||
|
configure.setRolloverIcon(CONFIGURE_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(configure);
|
||||||
|
configure.setToolTipText("Configure");
|
||||||
|
help.setBorder(null);
|
||||||
|
if (loaded != null)
|
||||||
|
{
|
||||||
|
String search = null;
|
||||||
|
if (loadedPlugins.size() > 1)
|
||||||
|
{
|
||||||
|
search = loaded.getInternalName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Plugin plugin = loadedPlugins.iterator().next();
|
||||||
|
Config cfg = pluginManager.getPluginConfigProxy(plugin);
|
||||||
|
if (cfg == null)
|
||||||
|
{
|
||||||
|
search = loaded.getInternalName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
configure.addActionListener(l -> pluginListPanel.openConfigurationPanel(plugin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search != null)
|
||||||
|
{
|
||||||
|
final String javaIsABadLanguage = search;
|
||||||
|
configure.addActionListener(l -> pluginListPanel.openWithFilter(javaIsABadLanguage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
configure.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean install = !installed;
|
||||||
|
boolean update = loaded != null && newManifest != null && !newManifest.equals(loaded);
|
||||||
|
boolean remove = !install && !update;
|
||||||
|
JButton addrm = new JButton();
|
||||||
|
if (install)
|
||||||
|
{
|
||||||
|
addrm.setText("Install");
|
||||||
|
addrm.setBackground(new Color(0x28BE28));
|
||||||
|
addrm.addActionListener(l -> externalPluginManager.install(manifest.getInternalName()));
|
||||||
|
}
|
||||||
|
else if (remove)
|
||||||
|
{
|
||||||
|
addrm.setText("Remove");
|
||||||
|
addrm.setBackground(new Color(0xBE2828));
|
||||||
|
addrm.addActionListener(l -> externalPluginManager.remove(manifest.getInternalName()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert update;
|
||||||
|
addrm.setText("Update");
|
||||||
|
addrm.setBackground(new Color(0x1F621F));
|
||||||
|
addrm.addActionListener(l -> externalPluginManager.update());
|
||||||
|
}
|
||||||
|
addrm.setBorder(new LineBorder(addrm.getBackground().darker()));
|
||||||
|
addrm.setFocusPainted(false);
|
||||||
|
|
||||||
|
layout.setHorizontalGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(icon, ICON_WIDTH, ICON_WIDTH, ICON_WIDTH)
|
||||||
|
.addGap(5)
|
||||||
|
.addGroup(layout.createParallelGroup()
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(pluginName, 0, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
|
||||||
|
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
|
||||||
|
.addComponent(author, 0, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE))
|
||||||
|
.addComponent(description, 0, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(version, 0, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
|
||||||
|
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, 100)
|
||||||
|
.addComponent(help, 0, 24, 24)
|
||||||
|
.addComponent(configure, 0, 24, 24)
|
||||||
|
.addComponent(addrm, 0, 50, GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addGap(5))));
|
||||||
|
|
||||||
|
layout.setVerticalGroup(layout.createParallelGroup()
|
||||||
|
.addComponent(icon, HEIGHT, HEIGHT, HEIGHT)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addGap(5)
|
||||||
|
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
||||||
|
.addComponent(pluginName)
|
||||||
|
.addComponent(author))
|
||||||
|
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
|
||||||
|
.addComponent(description)
|
||||||
|
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
|
||||||
|
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
|
||||||
|
.addComponent(version, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT)
|
||||||
|
.addComponent(help, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT)
|
||||||
|
.addComponent(configure, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT)
|
||||||
|
.addComponent(addrm, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT))
|
||||||
|
.addGap(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float setFilter(String[] filter)
|
||||||
|
{
|
||||||
|
ToDoubleFunction<String> match = r -> Stream.of(filter)
|
||||||
|
.mapToDouble(l -> Math.pow(DISTANCE.apply(l, r), 2))
|
||||||
|
.max()
|
||||||
|
.orElse(0.D);
|
||||||
|
|
||||||
|
double sim = SPACES.splitAsStream(manifest.getDisplayName()).collect(Collectors.averagingDouble(match)) * 2;
|
||||||
|
|
||||||
|
if (manifest.getTags() != null)
|
||||||
|
{
|
||||||
|
sim += Stream.of(manifest.getTags()).mapToDouble(match).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.filter = (float) sim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PluginListPanel pluginListPanel;
|
||||||
|
private final ExternalPluginManager externalPluginManager;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
private final ExternalPluginClient externalPluginClient;
|
||||||
|
private final ScheduledExecutorService executor;
|
||||||
|
|
||||||
|
private final IconTextField searchBar;
|
||||||
|
private final JLabel refreshing;
|
||||||
|
private final JPanel mainPanel;
|
||||||
|
private List<PluginItem> plugins = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PluginHubPanel(
|
||||||
|
PluginListPanel pluginListPanel,
|
||||||
|
ExternalPluginManager externalPluginManager,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
ExternalPluginClient externalPluginClient,
|
||||||
|
ScheduledExecutorService executor)
|
||||||
|
{
|
||||||
|
super(false);
|
||||||
|
this.pluginListPanel = pluginListPanel;
|
||||||
|
this.externalPluginManager = externalPluginManager;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.externalPluginClient = externalPluginClient;
|
||||||
|
this.executor = executor;
|
||||||
|
|
||||||
|
{
|
||||||
|
Object refresh = "this could just be a lambda, but no, it has to be abstracted";
|
||||||
|
getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), refresh);
|
||||||
|
getActionMap().put(refresh, new AbstractAction()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
reloadPluginList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupLayout layout = new GroupLayout(this);
|
||||||
|
setLayout(layout);
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
searchBar = new IconTextField();
|
||||||
|
searchBar.setIcon(IconTextField.Icon.SEARCH);
|
||||||
|
searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
|
||||||
|
searchBar.getDocument().addDocumentListener(new DocumentListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e)
|
||||||
|
{
|
||||||
|
filter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e)
|
||||||
|
{
|
||||||
|
filter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e)
|
||||||
|
{
|
||||||
|
filter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JLabel externalPluginWarning = new JLabel("<html>External plugins are not supported by the RuneLite Developers." +
|
||||||
|
"They may cause bugs or instability.</html>");
|
||||||
|
externalPluginWarning.setBackground(new Color(0xFFBB33));
|
||||||
|
externalPluginWarning.setForeground(Color.BLACK);
|
||||||
|
externalPluginWarning.setBorder(new EmptyBorder(5, 5, 5, 2));
|
||||||
|
externalPluginWarning.setOpaque(true);
|
||||||
|
|
||||||
|
JLabel externalPluginWarning2 = new JLabel("Use at your own risk!");
|
||||||
|
externalPluginWarning2.setHorizontalAlignment(JLabel.CENTER);
|
||||||
|
externalPluginWarning2.setFont(FontManager.getRunescapeBoldFont());
|
||||||
|
externalPluginWarning2.setBackground(externalPluginWarning.getBackground());
|
||||||
|
externalPluginWarning2.setForeground(externalPluginWarning.getForeground());
|
||||||
|
externalPluginWarning2.setBorder(new EmptyBorder(0, 5, 5, 5));
|
||||||
|
externalPluginWarning2.setOpaque(true);
|
||||||
|
|
||||||
|
JButton backButton = new JButton(ConfigPanel.BACK_ICON);
|
||||||
|
backButton.setRolloverIcon(ConfigPanel.BACK_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(backButton);
|
||||||
|
backButton.setToolTipText("Back");
|
||||||
|
backButton.addActionListener(l -> pluginListPanel.getMuxer().popState());
|
||||||
|
|
||||||
|
mainPanel = new JPanel();
|
||||||
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(0, 7, 7, 7));
|
||||||
|
mainPanel.setLayout(new DynamicGridLayout(0, 1, 0, 5));
|
||||||
|
mainPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
|
||||||
|
refreshing = new JLabel("Loading...");
|
||||||
|
refreshing.setHorizontalAlignment(JLabel.CENTER);
|
||||||
|
|
||||||
|
JPanel mainPanelWrapper = new JPanel();
|
||||||
|
mainPanelWrapper.setLayout(new BorderLayout());
|
||||||
|
mainPanelWrapper.add(mainPanel, BorderLayout.NORTH);
|
||||||
|
mainPanelWrapper.add(refreshing, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane();
|
||||||
|
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
scrollPane.setPreferredSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
|
||||||
|
scrollPane.getViewport().setLayout(new BorderLayout());
|
||||||
|
scrollPane.getViewport().add(mainPanelWrapper, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
layout.setVerticalGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(externalPluginWarning)
|
||||||
|
.addComponent(externalPluginWarning2)
|
||||||
|
.addGap(10)
|
||||||
|
.addGroup(layout.createParallelGroup()
|
||||||
|
.addComponent(backButton)
|
||||||
|
.addComponent(searchBar))
|
||||||
|
.addGap(10)
|
||||||
|
.addComponent(scrollPane));
|
||||||
|
|
||||||
|
layout.setHorizontalGroup(layout.createParallelGroup()
|
||||||
|
.addComponent(externalPluginWarning, 0, Short.MAX_VALUE, Short.MAX_VALUE)
|
||||||
|
.addComponent(externalPluginWarning2, 0, Short.MAX_VALUE, Short.MAX_VALUE)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(backButton)
|
||||||
|
.addComponent(searchBar)
|
||||||
|
.addGap(10))
|
||||||
|
.addComponent(scrollPane));
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
|
||||||
|
refreshing.setVisible(false);
|
||||||
|
reloadPluginList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadPluginList()
|
||||||
|
{
|
||||||
|
if (refreshing.isVisible())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshing.setVisible(true);
|
||||||
|
mainPanel.removeAll();
|
||||||
|
|
||||||
|
executor.submit(() ->
|
||||||
|
{
|
||||||
|
List<ExternalPluginManifest> manifest;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
manifest = externalPluginClient.downloadManifest();
|
||||||
|
}
|
||||||
|
catch (IOException | VerificationException e)
|
||||||
|
{
|
||||||
|
log.error("", e);
|
||||||
|
SwingUtilities.invokeLater(() ->
|
||||||
|
{
|
||||||
|
refreshing.setVisible(false);
|
||||||
|
mainPanel.add(new JLabel("Downloading the plugin manifest failed"));
|
||||||
|
|
||||||
|
JButton retry = new JButton("Retry");
|
||||||
|
retry.addActionListener(l -> reloadPluginList());
|
||||||
|
mainPanel.add(retry);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadPluginList(manifest);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadPluginList(List<ExternalPluginManifest> manifest)
|
||||||
|
{
|
||||||
|
Map<String, ExternalPluginManifest> manifests = manifest.stream()
|
||||||
|
.collect(ImmutableMap.toImmutableMap(ExternalPluginManifest::getInternalName, Function.identity()));
|
||||||
|
|
||||||
|
Multimap<String, Plugin> loadedPlugins = HashMultimap.create();
|
||||||
|
for (Plugin p : pluginManager.getPlugins())
|
||||||
|
{
|
||||||
|
Class<? extends Plugin> clazz = p.getClass();
|
||||||
|
ExternalPluginManifest mf = ExternalPluginManager.getExternalPluginManifest(clazz);
|
||||||
|
if (mf != null)
|
||||||
|
{
|
||||||
|
loadedPlugins.put(mf.getInternalName(), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> installed = new HashSet<>(externalPluginManager.getInstalledExternalPlugins());
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() ->
|
||||||
|
{
|
||||||
|
plugins = Sets.union(manifests.keySet(), loadedPlugins.keySet())
|
||||||
|
.stream()
|
||||||
|
.map(id -> new PluginItem(manifests.get(id), loadedPlugins.get(id), installed.contains(id)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
refreshing.setVisible(false);
|
||||||
|
filter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter()
|
||||||
|
{
|
||||||
|
if (refreshing.isVisible())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPanel.removeAll();
|
||||||
|
|
||||||
|
Stream<PluginItem> stream = plugins.stream();
|
||||||
|
|
||||||
|
String search = searchBar.getText();
|
||||||
|
boolean isSearching = search != null && !search.trim().isEmpty();
|
||||||
|
if (isSearching)
|
||||||
|
{
|
||||||
|
String[] searchArray = SPACES.split(search.toLowerCase());
|
||||||
|
stream = stream
|
||||||
|
.filter(p -> p.setFilter(searchArray) > PluginItem.MIN_FILTER_SCORE)
|
||||||
|
.sorted(Comparator.comparing(PluginItem::getFilter));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream = stream
|
||||||
|
.sorted(Comparator.comparing(PluginItem::isInstalled));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.forEach(mainPanel::add);
|
||||||
|
mainPanel.revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivate()
|
||||||
|
{
|
||||||
|
revalidate();
|
||||||
|
searchBar.setText("");
|
||||||
|
reloadPluginList();
|
||||||
|
searchBar.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onExternalPluginsChanged(ExternalPluginsChanged ev)
|
||||||
|
{
|
||||||
|
SwingUtilities.invokeLater(() -> reloadPluginList(ev.getLoadedManifest()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,171 +35,113 @@ import java.awt.event.MouseAdapter;
|
|||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.JToggleButton;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.runelite.client.config.Config;
|
import net.runelite.client.externalplugins.ExternalPluginManifest;
|
||||||
import net.runelite.client.config.ConfigDescriptor;
|
|
||||||
import net.runelite.client.plugins.Plugin;
|
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
|
||||||
import net.runelite.client.ui.ColorScheme;
|
import net.runelite.client.ui.ColorScheme;
|
||||||
import net.runelite.client.ui.PluginPanel;
|
import net.runelite.client.ui.PluginPanel;
|
||||||
import net.runelite.client.ui.components.IconButton;
|
|
||||||
import net.runelite.client.util.ImageUtil;
|
import net.runelite.client.util.ImageUtil;
|
||||||
import net.runelite.client.util.LinkBrowser;
|
import net.runelite.client.util.SwingUtil;
|
||||||
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
import org.apache.commons.text.similarity.JaroWinklerDistance;
|
||||||
|
|
||||||
class PluginListItem extends JPanel
|
class PluginListItem extends JPanel
|
||||||
{
|
{
|
||||||
private static final JaroWinklerDistance DISTANCE = new JaroWinklerDistance();
|
private static final JaroWinklerDistance DISTANCE = new JaroWinklerDistance();
|
||||||
private static final String RUNELITE_WIKI_FORMAT = "https://github.com/runelite/runelite/wiki/%s";
|
|
||||||
|
|
||||||
private static final ImageIcon CONFIG_ICON;
|
private static final ImageIcon CONFIG_ICON;
|
||||||
private static final ImageIcon CONFIG_ICON_HOVER;
|
private static final ImageIcon CONFIG_ICON_HOVER;
|
||||||
private static final ImageIcon ON_SWITCHER;
|
|
||||||
private static final ImageIcon OFF_SWITCHER;
|
|
||||||
private static final ImageIcon ON_STAR;
|
private static final ImageIcon ON_STAR;
|
||||||
private static final ImageIcon OFF_STAR;
|
private static final ImageIcon OFF_STAR;
|
||||||
|
|
||||||
private final ConfigPanel configPanel;
|
private final PluginListPanel pluginListPanel;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Nullable
|
private final PluginConfigurationDescriptor pluginConfig;
|
||||||
private final Plugin plugin;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
|
||||||
private final Config config;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
|
||||||
private final ConfigDescriptor configDescriptor;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final String description;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final IconButton configToggleButton;
|
|
||||||
|
|
||||||
private final List<String> keywords = new ArrayList<>();
|
private final List<String> keywords = new ArrayList<>();
|
||||||
|
|
||||||
private final IconButton pinButton = new IconButton(OFF_STAR);
|
private final JToggleButton pinButton;
|
||||||
private final IconButton configButton = new IconButton(CONFIG_ICON, CONFIG_ICON_HOVER);
|
private final JToggleButton onOffToggle;
|
||||||
private final IconButton toggleButton;
|
|
||||||
|
|
||||||
private boolean isPluginEnabled = false;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private boolean isPinned = false;
|
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
BufferedImage configIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "config_edit_icon.png");
|
BufferedImage configIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "config_edit_icon.png");
|
||||||
BufferedImage onSwitcher = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "switcher_on.png");
|
|
||||||
BufferedImage onStar = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "star_on.png");
|
BufferedImage onStar = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "star_on.png");
|
||||||
CONFIG_ICON = new ImageIcon(configIcon);
|
CONFIG_ICON = new ImageIcon(configIcon);
|
||||||
ON_SWITCHER = new ImageIcon(onSwitcher);
|
|
||||||
ON_STAR = new ImageIcon(onStar);
|
ON_STAR = new ImageIcon(onStar);
|
||||||
CONFIG_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(configIcon, -100));
|
CONFIG_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(configIcon, -100));
|
||||||
BufferedImage offSwitcherImage = ImageUtil.flipImage(
|
|
||||||
ImageUtil.grayscaleOffset(
|
BufferedImage offStar = ImageUtil.luminanceScale(
|
||||||
ImageUtil.grayscaleImage(onSwitcher),
|
|
||||||
0.61f
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
OFF_SWITCHER = new ImageIcon(offSwitcherImage);
|
|
||||||
BufferedImage offStar = ImageUtil.grayscaleOffset(
|
|
||||||
ImageUtil.grayscaleImage(onStar),
|
ImageUtil.grayscaleImage(onStar),
|
||||||
0.77f
|
0.77f
|
||||||
);
|
);
|
||||||
OFF_STAR = new ImageIcon(offStar);
|
OFF_STAR = new ImageIcon(offStar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig)
|
||||||
* Creates a new {@code PluginListItem} for a plugin.
|
|
||||||
* <p>
|
|
||||||
* Note that {@code config} and {@code configDescriptor} can be {@code null}
|
|
||||||
* if there is no configuration associated with the plugin.
|
|
||||||
*/
|
|
||||||
PluginListItem(ConfigPanel configPanel, Plugin plugin, PluginDescriptor descriptor,
|
|
||||||
@Nullable Config config, @Nullable ConfigDescriptor configDescriptor)
|
|
||||||
{
|
{
|
||||||
this(configPanel, plugin, config, configDescriptor,
|
this.pluginListPanel = pluginListPanel;
|
||||||
descriptor.name(), descriptor.description(), descriptor.tags());
|
this.pluginConfig = pluginConfig;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Collections.addAll(keywords, pluginConfig.getName().toLowerCase().split(" "));
|
||||||
* Creates a new {@code PluginListItem} for a core configuration.
|
Collections.addAll(keywords, pluginConfig.getDescription().toLowerCase().split(" "));
|
||||||
*/
|
Collections.addAll(keywords, pluginConfig.getTags());
|
||||||
PluginListItem(ConfigPanel configPanel, Config config, ConfigDescriptor configDescriptor,
|
ExternalPluginManifest mf = pluginConfig.getExternalPluginManifest();
|
||||||
String name, String description, String... tags)
|
if (mf != null)
|
||||||
{
|
{
|
||||||
this(configPanel, null, config, configDescriptor, name, description, tags);
|
keywords.add(mf.getInternalName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PluginListItem(ConfigPanel configPanel, @Nullable Plugin plugin, @Nullable Config config,
|
|
||||||
@Nullable ConfigDescriptor configDescriptor, String name, String description, String... tags)
|
|
||||||
{
|
|
||||||
this.configPanel = configPanel;
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.config = config;
|
|
||||||
this.configDescriptor = configDescriptor;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
Collections.addAll(keywords, name.toLowerCase().split(" "));
|
|
||||||
Collections.addAll(keywords, description.toLowerCase().split(" "));
|
|
||||||
Collections.addAll(keywords, tags);
|
|
||||||
|
|
||||||
final List<JMenuItem> popupMenuItems = new ArrayList<>();
|
final List<JMenuItem> popupMenuItems = new ArrayList<>();
|
||||||
|
|
||||||
setLayout(new BorderLayout(3, 0));
|
setLayout(new BorderLayout(3, 0));
|
||||||
setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, 20));
|
setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH, 20));
|
||||||
|
|
||||||
JLabel nameLabel = new JLabel(name);
|
JLabel nameLabel = new JLabel(pluginConfig.getName());
|
||||||
nameLabel.setForeground(Color.WHITE);
|
nameLabel.setForeground(Color.WHITE);
|
||||||
|
|
||||||
if (!description.isEmpty())
|
if (!pluginConfig.getDescription().isEmpty())
|
||||||
{
|
{
|
||||||
nameLabel.setToolTipText("<html>" + name + ":<br>" + description + "</html>");
|
nameLabel.setToolTipText("<html>" + pluginConfig.getName() + ":<br>" + pluginConfig.getDescription() + "</html>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pinButton = new JToggleButton(OFF_STAR);
|
||||||
|
pinButton.setSelectedIcon(ON_STAR);
|
||||||
|
SwingUtil.removeButtonDecorations(pinButton);
|
||||||
|
SwingUtil.addModalTooltip(pinButton, "Unpin plugin", "Pin plugin");
|
||||||
pinButton.setPreferredSize(new Dimension(21, 0));
|
pinButton.setPreferredSize(new Dimension(21, 0));
|
||||||
add(pinButton, BorderLayout.LINE_START);
|
add(pinButton, BorderLayout.LINE_START);
|
||||||
|
|
||||||
pinButton.addActionListener(e ->
|
pinButton.addActionListener(e ->
|
||||||
{
|
{
|
||||||
setPinned(!isPinned);
|
pluginListPanel.savePinnedPlugins();
|
||||||
configPanel.savePinnedPlugins();
|
pluginListPanel.refresh();
|
||||||
configPanel.openConfigList();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final JPanel buttonPanel = new JPanel();
|
final JPanel buttonPanel = new JPanel();
|
||||||
buttonPanel.setLayout(new GridLayout(1, 2));
|
buttonPanel.setLayout(new GridLayout(1, 2));
|
||||||
add(buttonPanel, BorderLayout.LINE_END);
|
add(buttonPanel, BorderLayout.LINE_END);
|
||||||
|
|
||||||
configButton.setPreferredSize(new Dimension(25, 0));
|
JMenuItem configMenuItem = null;
|
||||||
configButton.setVisible(false);
|
if (pluginConfig.hasConfigurables())
|
||||||
buttonPanel.add(configButton);
|
|
||||||
|
|
||||||
// add a listener to configButton only if there are config items to show
|
|
||||||
if (config != null && !configDescriptor.getItems().stream().allMatch(item -> item.getItem().hidden()))
|
|
||||||
{
|
{
|
||||||
|
JButton configButton = new JButton(CONFIG_ICON);
|
||||||
|
configButton.setRolloverIcon(CONFIG_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(configButton);
|
||||||
|
configButton.setPreferredSize(new Dimension(25, 0));
|
||||||
|
configButton.setVisible(false);
|
||||||
|
buttonPanel.add(configButton);
|
||||||
|
|
||||||
configButton.addActionListener(e ->
|
configButton.addActionListener(e ->
|
||||||
{
|
{
|
||||||
configButton.setIcon(CONFIG_ICON);
|
configButton.setIcon(CONFIG_ICON);
|
||||||
@@ -209,77 +151,60 @@ class PluginListItem extends JPanel
|
|||||||
configButton.setVisible(true);
|
configButton.setVisible(true);
|
||||||
configButton.setToolTipText("Edit plugin configuration");
|
configButton.setToolTipText("Edit plugin configuration");
|
||||||
|
|
||||||
final JMenuItem configMenuItem = new JMenuItem("Configure");
|
configMenuItem = new JMenuItem("Configure");
|
||||||
configMenuItem.addActionListener(e -> openGroupConfigPanel());
|
configMenuItem.addActionListener(e -> openGroupConfigPanel());
|
||||||
popupMenuItems.add(configMenuItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
popupMenuItems.add(wikiLinkMenuItem(name));
|
JMenuItem uninstallItem = null;
|
||||||
addLabelPopupMenu(nameLabel, popupMenuItems);
|
if (mf != null)
|
||||||
|
{
|
||||||
|
uninstallItem = new JMenuItem("Uninstall");
|
||||||
|
uninstallItem.addActionListener(ev -> pluginListPanel.getExternalPluginManager().remove(mf.getInternalName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
addLabelPopupMenu(nameLabel, configMenuItem, pluginConfig.createSupportMenuItem(), uninstallItem);
|
||||||
add(nameLabel, BorderLayout.CENTER);
|
add(nameLabel, BorderLayout.CENTER);
|
||||||
|
|
||||||
toggleButton = createToggleButton();
|
onOffToggle = new PluginToggleButton();
|
||||||
buttonPanel.add(toggleButton);
|
buttonPanel.add(onOffToggle);
|
||||||
|
if (pluginConfig.getPlugin() != null)
|
||||||
configToggleButton = createToggleButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void attachToggleButtonListener(IconButton button)
|
|
||||||
{
|
|
||||||
// no need for a listener if there is no plugin to enable / disable
|
|
||||||
if (plugin == null)
|
|
||||||
{
|
{
|
||||||
button.setVisible(false);
|
onOffToggle.addItemListener(i ->
|
||||||
return;
|
{
|
||||||
|
if (onOffToggle.isSelected())
|
||||||
|
{
|
||||||
|
pluginListPanel.startPlugin(pluginConfig.getPlugin());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginListPanel.stopPlugin(pluginConfig.getPlugin());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
button.addActionListener(e ->
|
|
||||||
{
|
{
|
||||||
if (isPluginEnabled)
|
onOffToggle.setVisible(false);
|
||||||
{
|
}
|
||||||
configPanel.stopPlugin(plugin, PluginListItem.this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
configPanel.startPlugin(plugin, PluginListItem.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPluginEnabled(!isPluginEnabled);
|
|
||||||
updateToggleButton(button);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IconButton createToggleButton()
|
boolean isPinned()
|
||||||
{
|
{
|
||||||
final IconButton button = new IconButton(OFF_SWITCHER);
|
return pinButton.isSelected();
|
||||||
button.setPreferredSize(new Dimension(25, 0));
|
|
||||||
updateToggleButton(button);
|
|
||||||
attachToggleButtonListener(button);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPluginEnabled(boolean enabled)
|
|
||||||
{
|
|
||||||
isPluginEnabled = enabled;
|
|
||||||
updateToggleButton(toggleButton);
|
|
||||||
updateToggleButton(configToggleButton);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPinned(boolean pinned)
|
void setPinned(boolean pinned)
|
||||||
{
|
{
|
||||||
isPinned = pinned;
|
pinButton.setSelected(pinned);
|
||||||
pinButton.setIcon(pinned ? ON_STAR : OFF_STAR);
|
|
||||||
pinButton.setToolTipText(pinned ? "Unpin plugin" : "Pin plugin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateToggleButton(IconButton button)
|
void setPluginEnabled(boolean enabled)
|
||||||
{
|
{
|
||||||
button.setIcon(isPluginEnabled ? ON_SWITCHER : OFF_SWITCHER);
|
onOffToggle.setSelected(enabled);
|
||||||
button.setToolTipText(isPluginEnabled ? "Disable plugin" : "Enable plugin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if all the search terms in the given list matches at least one keyword.
|
* Checks if all the search terms in the given list matches at least one keyword.
|
||||||
|
*
|
||||||
* @return true if all search terms matches at least one keyword, or false if otherwise.
|
* @return true if all search terms matches at least one keyword, or false if otherwise.
|
||||||
*/
|
*/
|
||||||
boolean matchesSearchTerms(String[] searchTerms)
|
boolean matchesSearchTerms(String[] searchTerms)
|
||||||
@@ -297,19 +222,7 @@ class PluginListItem extends JPanel
|
|||||||
|
|
||||||
private void openGroupConfigPanel()
|
private void openGroupConfigPanel()
|
||||||
{
|
{
|
||||||
configPanel.openGroupConfigPanel(PluginListItem.this, config, configDescriptor);
|
pluginListPanel.openConfigurationPanel(pluginConfig);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a mouseover effect to change the text of the passed label to {@link ColorScheme#BRAND_ORANGE} color, and
|
|
||||||
* adds the passed menu item to a popup menu shown when the label is clicked.
|
|
||||||
*
|
|
||||||
* @param label The label to attach the mouseover and click effects to
|
|
||||||
* @param menuItem The menu item to be shown when the label is clicked
|
|
||||||
*/
|
|
||||||
static void addLabelPopupMenu(final JLabel label, final JMenuItem menuItem)
|
|
||||||
{
|
|
||||||
addLabelPopupMenu(label, Collections.singletonList(menuItem));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -319,7 +232,7 @@ class PluginListItem extends JPanel
|
|||||||
* @param label The label to attach the mouseover and click effects to
|
* @param label The label to attach the mouseover and click effects to
|
||||||
* @param menuItems The menu items to be shown when the label is clicked
|
* @param menuItems The menu items to be shown when the label is clicked
|
||||||
*/
|
*/
|
||||||
static void addLabelPopupMenu(final JLabel label, final Collection<JMenuItem> menuItems)
|
static void addLabelPopupMenu(JLabel label, JMenuItem... menuItems)
|
||||||
{
|
{
|
||||||
final JPopupMenu menu = new JPopupMenu();
|
final JPopupMenu menu = new JPopupMenu();
|
||||||
final Color labelForeground = label.getForeground();
|
final Color labelForeground = label.getForeground();
|
||||||
@@ -327,6 +240,11 @@ class PluginListItem extends JPanel
|
|||||||
|
|
||||||
for (final JMenuItem menuItem : menuItems)
|
for (final JMenuItem menuItem : menuItems)
|
||||||
{
|
{
|
||||||
|
if (menuItem == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Some machines register mouseEntered through a popup menu, and do not register mouseExited when a popup
|
// Some machines register mouseEntered through a popup menu, and do not register mouseExited when a popup
|
||||||
// menu item is clicked, so reset the label's color when we click one of these options.
|
// menu item is clicked, so reset the label's color when we click one of these options.
|
||||||
menuItem.addActionListener(e -> label.setForeground(labelForeground));
|
menuItem.addActionListener(e -> label.setForeground(labelForeground));
|
||||||
@@ -360,19 +278,4 @@ class PluginListItem extends JPanel
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a menu item for linking to a wiki page which, when clicked, opens a link to the plugin's wiki page for
|
|
||||||
* the passed plugin name.
|
|
||||||
*
|
|
||||||
* @param pluginName The name of the plugin which should be linked to
|
|
||||||
* @return A {@link JMenuItem} which opens the plugin's wiki page URL in the browser when clicked
|
|
||||||
*/
|
|
||||||
static JMenuItem wikiLinkMenuItem(final String pluginName)
|
|
||||||
{
|
|
||||||
final JMenuItem menuItem = new JMenuItem("Wiki");
|
|
||||||
final String sanitizedName = pluginName.replace(' ', '-');
|
|
||||||
menuItem.addActionListener(e -> LinkBrowser.browse(String.format(RUNELITE_WIKI_FORMAT, sanitizedName)));
|
|
||||||
return menuItem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||||
|
* 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.plugins.config;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.ScrollPaneConstants;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.config.Config;
|
||||||
|
import net.runelite.client.config.ConfigDescriptor;
|
||||||
|
import net.runelite.client.config.ConfigGroup;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.config.RuneLiteConfig;
|
||||||
|
import net.runelite.client.eventbus.EventBus;
|
||||||
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
|
import net.runelite.client.events.ExternalPluginsChanged;
|
||||||
|
import net.runelite.client.events.PluginChanged;
|
||||||
|
import net.runelite.client.externalplugins.ExternalPluginManager;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
|
import net.runelite.client.plugins.PluginInstantiationException;
|
||||||
|
import net.runelite.client.plugins.PluginManager;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
|
import net.runelite.client.ui.MultiplexingPluginPanel;
|
||||||
|
import net.runelite.client.ui.PluginPanel;
|
||||||
|
import net.runelite.client.ui.components.IconTextField;
|
||||||
|
import net.runelite.client.util.Text;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Singleton
|
||||||
|
class PluginListPanel extends PluginPanel
|
||||||
|
{
|
||||||
|
private static final String RUNELITE_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value();
|
||||||
|
private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins";
|
||||||
|
|
||||||
|
private final ConfigManager configManager;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
private final ScheduledExecutorService executorService;
|
||||||
|
private final Provider<ConfigPanel> configPanelProvider;
|
||||||
|
private final List<PluginConfigurationDescriptor> fakePlugins = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final ExternalPluginManager externalPluginManager;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final MultiplexingPluginPanel muxer;
|
||||||
|
|
||||||
|
private final IconTextField searchBar;
|
||||||
|
private final JScrollPane scrollPane;
|
||||||
|
private final FixedWidthPanel mainPanel;
|
||||||
|
private List<PluginListItem> pluginList;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PluginListPanel(
|
||||||
|
ConfigManager configManager,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
ExternalPluginManager externalPluginManager,
|
||||||
|
ScheduledExecutorService executorService,
|
||||||
|
EventBus eventBus,
|
||||||
|
Provider<ConfigPanel> configPanelProvider,
|
||||||
|
Provider<PluginHubPanel> pluginHubPanelProvider)
|
||||||
|
{
|
||||||
|
super(false);
|
||||||
|
|
||||||
|
this.configManager = configManager;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.externalPluginManager = externalPluginManager;
|
||||||
|
this.executorService = executorService;
|
||||||
|
this.configPanelProvider = configPanelProvider;
|
||||||
|
|
||||||
|
muxer = new MultiplexingPluginPanel(this)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void onAdd(PluginPanel p)
|
||||||
|
{
|
||||||
|
eventBus.register(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemove(PluginPanel p)
|
||||||
|
{
|
||||||
|
eventBus.unregister(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
searchBar = new IconTextField();
|
||||||
|
searchBar.setIcon(IconTextField.Icon.SEARCH);
|
||||||
|
searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30));
|
||||||
|
searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
|
||||||
|
searchBar.getDocument().addDocumentListener(new DocumentListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e)
|
||||||
|
{
|
||||||
|
onSearchBarChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e)
|
||||||
|
{
|
||||||
|
onSearchBarChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e)
|
||||||
|
{
|
||||||
|
onSearchBarChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
JPanel topPanel = new JPanel();
|
||||||
|
topPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
|
topPanel.setLayout(new BorderLayout(0, BORDER_OFFSET));
|
||||||
|
topPanel.add(searchBar, BorderLayout.CENTER);
|
||||||
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
mainPanel = new FixedWidthPanel();
|
||||||
|
mainPanel.setBorder(new EmptyBorder(8, 10, 10, 10));
|
||||||
|
mainPanel.setLayout(new DynamicGridLayout(0, 1, 0, 5));
|
||||||
|
mainPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
|
||||||
|
JButton externalPluginButton = new JButton("Plugin Hub");
|
||||||
|
externalPluginButton.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
externalPluginButton.setLayout(new BorderLayout(0, BORDER_OFFSET));
|
||||||
|
externalPluginButton.addActionListener(l -> muxer.pushState(pluginHubPanelProvider.get()));
|
||||||
|
|
||||||
|
JPanel northPanel = new FixedWidthPanel();
|
||||||
|
northPanel.setLayout(new BorderLayout());
|
||||||
|
northPanel.add(mainPanel, BorderLayout.NORTH);
|
||||||
|
northPanel.add(externalPluginButton, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
scrollPane = new JScrollPane(northPanel);
|
||||||
|
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildPluginList()
|
||||||
|
{
|
||||||
|
final List<String> pinnedPlugins = getPinnedPluginNames();
|
||||||
|
|
||||||
|
// populate pluginList with all non-hidden plugins
|
||||||
|
pluginList = Stream.concat(
|
||||||
|
fakePlugins.stream(),
|
||||||
|
pluginManager.getPlugins().stream()
|
||||||
|
.filter(plugin -> !plugin.getClass().getAnnotation(PluginDescriptor.class).hidden())
|
||||||
|
.map(plugin ->
|
||||||
|
{
|
||||||
|
PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class);
|
||||||
|
Config config = pluginManager.getPluginConfigProxy(plugin);
|
||||||
|
ConfigDescriptor configDescriptor = config == null ? null : configManager.getConfigDescriptor(config);
|
||||||
|
|
||||||
|
return new PluginConfigurationDescriptor(
|
||||||
|
descriptor.name(),
|
||||||
|
descriptor.description(),
|
||||||
|
descriptor.tags(),
|
||||||
|
plugin,
|
||||||
|
config,
|
||||||
|
configDescriptor);
|
||||||
|
})
|
||||||
|
).map(desc ->
|
||||||
|
{
|
||||||
|
PluginListItem listItem = new PluginListItem(this, desc);
|
||||||
|
listItem.setPinned(pinnedPlugins.contains(desc.getName()));
|
||||||
|
return listItem;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
pluginList.sort(Comparator.comparing(p -> p.getPluginConfig().getName()));
|
||||||
|
mainPanel.removeAll();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addFakePlugin(PluginConfigurationDescriptor... descriptor)
|
||||||
|
{
|
||||||
|
Collections.addAll(fakePlugins, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh()
|
||||||
|
{
|
||||||
|
// update enabled / disabled status of all items
|
||||||
|
pluginList.forEach(listItem ->
|
||||||
|
{
|
||||||
|
final Plugin plugin = listItem.getPluginConfig().getPlugin();
|
||||||
|
if (plugin != null)
|
||||||
|
{
|
||||||
|
listItem.setPluginEnabled(pluginManager.isPluginEnabled(plugin));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int scrollBarPosition = scrollPane.getVerticalScrollBar().getValue();
|
||||||
|
|
||||||
|
onSearchBarChanged();
|
||||||
|
searchBar.requestFocusInWindow();
|
||||||
|
validate();
|
||||||
|
|
||||||
|
scrollPane.getVerticalScrollBar().setValue(scrollBarPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openWithFilter(String filter)
|
||||||
|
{
|
||||||
|
searchBar.setText(filter);
|
||||||
|
onSearchBarChanged();
|
||||||
|
muxer.pushState(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSearchBarChanged()
|
||||||
|
{
|
||||||
|
final String text = searchBar.getText();
|
||||||
|
|
||||||
|
pluginList.forEach(mainPanel::remove);
|
||||||
|
|
||||||
|
showMatchingPlugins(true, text);
|
||||||
|
showMatchingPlugins(false, text);
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMatchingPlugins(boolean pinned, String text)
|
||||||
|
{
|
||||||
|
if (text.isEmpty())
|
||||||
|
{
|
||||||
|
pluginList.stream().filter(item -> pinned == item.isPinned()).forEach(mainPanel::add);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] searchTerms = text.toLowerCase().split(" ");
|
||||||
|
pluginList.forEach(listItem ->
|
||||||
|
{
|
||||||
|
if (pinned == listItem.isPinned() && listItem.matchesSearchTerms(searchTerms))
|
||||||
|
{
|
||||||
|
mainPanel.add(listItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void openConfigurationPanel(String configGroup)
|
||||||
|
{
|
||||||
|
for (PluginListItem pluginListItem : pluginList)
|
||||||
|
{
|
||||||
|
if (pluginListItem.getPluginConfig().getName().equals(configGroup))
|
||||||
|
{
|
||||||
|
openConfigurationPanel(pluginListItem.getPluginConfig());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void openConfigurationPanel(Plugin plugin)
|
||||||
|
{
|
||||||
|
for (PluginListItem pluginListItem : pluginList)
|
||||||
|
{
|
||||||
|
if (pluginListItem.getPluginConfig().getPlugin() == plugin)
|
||||||
|
{
|
||||||
|
openConfigurationPanel(pluginListItem.getPluginConfig());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void openConfigurationPanel(PluginConfigurationDescriptor plugin)
|
||||||
|
{
|
||||||
|
ConfigPanel panel = configPanelProvider.get();
|
||||||
|
panel.init(plugin);
|
||||||
|
muxer.pushState(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPlugin(Plugin plugin)
|
||||||
|
{
|
||||||
|
executorService.submit(() ->
|
||||||
|
{
|
||||||
|
pluginManager.setPluginEnabled(plugin, true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pluginManager.startPlugin(plugin);
|
||||||
|
}
|
||||||
|
catch (PluginInstantiationException ex)
|
||||||
|
{
|
||||||
|
log.warn("Error when starting plugin {}", plugin.getClass().getSimpleName(), ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopPlugin(Plugin plugin)
|
||||||
|
{
|
||||||
|
executorService.submit(() ->
|
||||||
|
{
|
||||||
|
pluginManager.setPluginEnabled(plugin, false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pluginManager.stopPlugin(plugin);
|
||||||
|
}
|
||||||
|
catch (PluginInstantiationException ex)
|
||||||
|
{
|
||||||
|
log.warn("Error when stopping plugin {}", plugin.getClass().getSimpleName(), ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getPinnedPluginNames()
|
||||||
|
{
|
||||||
|
final String config = configManager.getConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY);
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text.fromCSV(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void savePinnedPlugins()
|
||||||
|
{
|
||||||
|
final String value = pluginList.stream()
|
||||||
|
.filter(PluginListItem::isPinned)
|
||||||
|
.map(p -> p.getPluginConfig().getName())
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
|
||||||
|
configManager.setConfiguration(RUNELITE_GROUP_NAME, PINNED_PLUGINS_CONFIG_KEY, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPluginChanged(PluginChanged event)
|
||||||
|
{
|
||||||
|
SwingUtilities.invokeLater(this::refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize()
|
||||||
|
{
|
||||||
|
return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivate()
|
||||||
|
{
|
||||||
|
super.onActivate();
|
||||||
|
|
||||||
|
if (searchBar.getParent() != null)
|
||||||
|
{
|
||||||
|
searchBar.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onExternalPluginsChanged(ExternalPluginsChanged ev)
|
||||||
|
{
|
||||||
|
SwingUtilities.invokeLater(this::rebuildPluginList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
* Copyright (c) 2019 Abex
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@@ -22,50 +22,41 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.ui.components;
|
|
||||||
|
|
||||||
import java.awt.Insets;
|
package net.runelite.client.plugins.config;
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JToggleButton;
|
||||||
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
import net.runelite.client.util.SwingUtil;
|
||||||
|
|
||||||
/**
|
class PluginToggleButton extends JToggleButton
|
||||||
* A button that consists of an icon, without any background, borders, or margins.
|
|
||||||
*/
|
|
||||||
public class IconButton extends JButton
|
|
||||||
{
|
{
|
||||||
public IconButton(ImageIcon icon)
|
private static final ImageIcon ON_SWITCHER;
|
||||||
|
private static final ImageIcon OFF_SWITCHER;
|
||||||
|
|
||||||
|
static
|
||||||
{
|
{
|
||||||
this(icon, null);
|
BufferedImage onSwitcher = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "switcher_on.png");
|
||||||
|
ON_SWITCHER = new ImageIcon(onSwitcher);
|
||||||
|
OFF_SWITCHER = new ImageIcon(ImageUtil.flipImage(
|
||||||
|
ImageUtil.luminanceScale(
|
||||||
|
ImageUtil.grayscaleImage(onSwitcher),
|
||||||
|
0.61f
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IconButton(ImageIcon icon, ImageIcon hoverIcon)
|
public PluginToggleButton()
|
||||||
{
|
{
|
||||||
setIcon(icon);
|
super(OFF_SWITCHER);
|
||||||
setBorderPainted(false);
|
setSelectedIcon(ON_SWITCHER);
|
||||||
setContentAreaFilled(false);
|
SwingUtil.removeButtonDecorations(this);
|
||||||
setFocusPainted(false);
|
setPreferredSize(new Dimension(25, 0));
|
||||||
setMargin(new Insets(0, 0, 0, 0));
|
SwingUtil.addModalTooltip(this, "Disable plugin", "Enable plugin");
|
||||||
setOpaque(false);
|
|
||||||
setRolloverEnabled(false);
|
|
||||||
|
|
||||||
if (hoverIcon != null)
|
|
||||||
{
|
|
||||||
addMouseListener(new MouseAdapter()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void mouseEntered(MouseEvent e)
|
|
||||||
{
|
|
||||||
setIcon(hoverIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseExited(MouseEvent e)
|
|
||||||
{
|
|
||||||
setIcon(icon);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,6 @@ import com.google.inject.Inject;
|
|||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.GridBagConstraints;
|
import java.awt.GridBagConstraints;
|
||||||
import java.awt.GridBagLayout;
|
import java.awt.GridBagLayout;
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -52,7 +50,7 @@ import net.runelite.client.util.ImageUtil;
|
|||||||
class KourendLibraryPanel extends PluginPanel
|
class KourendLibraryPanel extends PluginPanel
|
||||||
{
|
{
|
||||||
private static final ImageIcon RESET_ICON;
|
private static final ImageIcon RESET_ICON;
|
||||||
private static final ImageIcon RESET_CLICK_ICON;
|
private static final ImageIcon RESET_HOVER_ICON;
|
||||||
|
|
||||||
private final KourendLibraryConfig config;
|
private final KourendLibraryConfig config;
|
||||||
private final Library library;
|
private final Library library;
|
||||||
@@ -63,7 +61,7 @@ class KourendLibraryPanel extends PluginPanel
|
|||||||
{
|
{
|
||||||
final BufferedImage resetIcon = ImageUtil.getResourceStreamFromClass(KourendLibraryPanel.class, "/util/reset.png");
|
final BufferedImage resetIcon = ImageUtil.getResourceStreamFromClass(KourendLibraryPanel.class, "/util/reset.png");
|
||||||
RESET_ICON = new ImageIcon(resetIcon);
|
RESET_ICON = new ImageIcon(resetIcon);
|
||||||
RESET_CLICK_ICON = new ImageIcon(ImageUtil.alphaOffset(resetIcon, -100));
|
RESET_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(resetIcon, -100));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -100,21 +98,11 @@ class KourendLibraryPanel extends PluginPanel
|
|||||||
});
|
});
|
||||||
|
|
||||||
JButton reset = new JButton("Reset", RESET_ICON);
|
JButton reset = new JButton("Reset", RESET_ICON);
|
||||||
reset.addMouseListener(new MouseAdapter()
|
reset.setRolloverIcon(RESET_HOVER_ICON);
|
||||||
|
reset.addActionListener(ev ->
|
||||||
{
|
{
|
||||||
@Override
|
library.reset();
|
||||||
public void mousePressed(MouseEvent mouseEvent)
|
update();
|
||||||
{
|
|
||||||
reset.setIcon(RESET_CLICK_ICON);
|
|
||||||
library.reset();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased(MouseEvent mouseEvent)
|
|
||||||
{
|
|
||||||
reset.setIcon(RESET_ICON);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add(reset, BorderLayout.NORTH);
|
add(reset, BorderLayout.NORTH);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class ScreenMarkerPanel extends JPanel
|
|||||||
static
|
static
|
||||||
{
|
{
|
||||||
final BufferedImage borderImg = ImageUtil.getResourceStreamFromClass(ScreenMarkerPlugin.class, "border_color_icon.png");
|
final BufferedImage borderImg = ImageUtil.getResourceStreamFromClass(ScreenMarkerPlugin.class, "border_color_icon.png");
|
||||||
final BufferedImage borderImgHover = ImageUtil.grayscaleOffset(borderImg, -150);
|
final BufferedImage borderImgHover = ImageUtil.luminanceOffset(borderImg, -150);
|
||||||
BORDER_COLOR_ICON = new ImageIcon(borderImg);
|
BORDER_COLOR_ICON = new ImageIcon(borderImg);
|
||||||
BORDER_COLOR_HOVER_ICON = new ImageIcon(borderImgHover);
|
BORDER_COLOR_HOVER_ICON = new ImageIcon(borderImgHover);
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class ScreenMarkerPanel extends JPanel
|
|||||||
NO_BORDER_COLOR_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(borderImgHover, -100));
|
NO_BORDER_COLOR_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(borderImgHover, -100));
|
||||||
|
|
||||||
final BufferedImage fillImg = ImageUtil.getResourceStreamFromClass(ScreenMarkerPlugin.class, "fill_color_icon.png");
|
final BufferedImage fillImg = ImageUtil.getResourceStreamFromClass(ScreenMarkerPlugin.class, "fill_color_icon.png");
|
||||||
final BufferedImage fillImgHover = ImageUtil.grayscaleOffset(fillImg, -150);
|
final BufferedImage fillImgHover = ImageUtil.luminanceOffset(fillImg, -150);
|
||||||
FILL_COLOR_ICON = new ImageIcon(fillImg);
|
FILL_COLOR_ICON = new ImageIcon(fillImg);
|
||||||
FILL_COLOR_HOVER_ICON = new ImageIcon(fillImgHover);
|
FILL_COLOR_HOVER_ICON = new ImageIcon(fillImgHover);
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ class ScreenMarkerPanel extends JPanel
|
|||||||
NO_FILL_COLOR_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(fillImgHover, -100));
|
NO_FILL_COLOR_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(fillImgHover, -100));
|
||||||
|
|
||||||
final BufferedImage opacityImg = ImageUtil.getResourceStreamFromClass(ScreenMarkerPlugin.class, "opacity_icon.png");
|
final BufferedImage opacityImg = ImageUtil.getResourceStreamFromClass(ScreenMarkerPlugin.class, "opacity_icon.png");
|
||||||
final BufferedImage opacityImgHover = ImageUtil.grayscaleOffset(opacityImg, -150);
|
final BufferedImage opacityImgHover = ImageUtil.luminanceOffset(opacityImg, -150);
|
||||||
FULL_OPACITY_ICON = new ImageIcon(opacityImg);
|
FULL_OPACITY_ICON = new ImageIcon(opacityImg);
|
||||||
FULL_OPACITY_HOVER_ICON = new ImageIcon(opacityImgHover);
|
FULL_OPACITY_HOVER_ICON = new ImageIcon(opacityImgHover);
|
||||||
|
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ import java.awt.Dimension;
|
|||||||
import java.awt.FlowLayout;
|
import java.awt.FlowLayout;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.FocusEvent;
|
||||||
import java.awt.event.FocusListener;
|
import java.awt.event.FocusListener;
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.JButton;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JToggleButton;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
import javax.swing.border.CompoundBorder;
|
import javax.swing.border.CompoundBorder;
|
||||||
@@ -43,7 +43,7 @@ import javax.swing.border.EmptyBorder;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.runelite.client.ui.ColorScheme;
|
import net.runelite.client.ui.ColorScheme;
|
||||||
import net.runelite.client.ui.components.FlatTextField;
|
import net.runelite.client.ui.components.FlatTextField;
|
||||||
import net.runelite.client.ui.components.IconButton;
|
import net.runelite.client.util.SwingUtil;
|
||||||
|
|
||||||
abstract class ClockPanel extends JPanel
|
abstract class ClockPanel extends JPanel
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ abstract class ClockPanel extends JPanel
|
|||||||
final JPanel rightActions;
|
final JPanel rightActions;
|
||||||
|
|
||||||
private final FlatTextField nameInput;
|
private final FlatTextField nameInput;
|
||||||
private final IconButton startPauseButton;
|
private final JToggleButton startPauseButton;
|
||||||
private final FlatTextField displayInput;
|
private final FlatTextField displayInput;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -167,28 +167,17 @@ abstract class ClockPanel extends JPanel
|
|||||||
leftActions = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
leftActions = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||||
leftActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
leftActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
startPauseButton = new IconButton(ClockTabPanel.START_ICON);
|
startPauseButton = new JToggleButton(ClockTabPanel.START_ICON);
|
||||||
|
startPauseButton.setRolloverIcon(ClockTabPanel.START_ICON_HOVER);
|
||||||
|
startPauseButton.setSelectedIcon(ClockTabPanel.PAUSE_ICON);
|
||||||
|
startPauseButton.setRolloverSelectedIcon(ClockTabPanel.PAUSE_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(startPauseButton);
|
||||||
startPauseButton.setPreferredSize(new Dimension(16, 14));
|
startPauseButton.setPreferredSize(new Dimension(16, 14));
|
||||||
updateActivityStatus();
|
updateActivityStatus();
|
||||||
|
|
||||||
startPauseButton.addMouseListener(new MouseAdapter()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void mouseEntered(MouseEvent e)
|
|
||||||
{
|
|
||||||
startPauseButton.setIcon(clock.isActive() ? ClockTabPanel.PAUSE_ICON_HOVER : ClockTabPanel.START_ICON_HOVER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseExited(MouseEvent e)
|
|
||||||
{
|
|
||||||
startPauseButton.setIcon(clock.isActive() ? ClockTabPanel.PAUSE_ICON : ClockTabPanel.START_ICON);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
startPauseButton.addActionListener(e ->
|
startPauseButton.addActionListener(e ->
|
||||||
{
|
{
|
||||||
if (clock.isActive())
|
if (!startPauseButton.isSelected())
|
||||||
{
|
{
|
||||||
clock.pause();
|
clock.pause();
|
||||||
}
|
}
|
||||||
@@ -201,7 +190,9 @@ abstract class ClockPanel extends JPanel
|
|||||||
clockManager.saveToConfig();
|
clockManager.saveToConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
IconButton resetButton = new IconButton(ClockTabPanel.RESET_ICON, ClockTabPanel.RESET_ICON_HOVER);
|
JButton resetButton = new JButton(ClockTabPanel.RESET_ICON);
|
||||||
|
resetButton.setRolloverIcon(ClockTabPanel.RESET_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(resetButton);
|
||||||
resetButton.setPreferredSize(new Dimension(16, 14));
|
resetButton.setPreferredSize(new Dimension(16, 14));
|
||||||
resetButton.setToolTipText("Reset " + clockType);
|
resetButton.setToolTipText("Reset " + clockType);
|
||||||
|
|
||||||
@@ -249,7 +240,7 @@ abstract class ClockPanel extends JPanel
|
|||||||
displayInput.setEditable(editable && !isActive);
|
displayInput.setEditable(editable && !isActive);
|
||||||
displayInput.getTextField().setForeground(isActive ? ACTIVE_CLOCK_COLOR : INACTIVE_CLOCK_COLOR);
|
displayInput.getTextField().setForeground(isActive ? ACTIVE_CLOCK_COLOR : INACTIVE_CLOCK_COLOR);
|
||||||
startPauseButton.setToolTipText(isActive ? "Pause " + clockType : "Start " + clockType);
|
startPauseButton.setToolTipText(isActive ? "Pause " + clockType : "Start " + clockType);
|
||||||
startPauseButton.setIcon(isActive ? ClockTabPanel.PAUSE_ICON : ClockTabPanel.START_ICON);
|
startPauseButton.setSelected(isActive);
|
||||||
|
|
||||||
if (editable && clock.getDisplayTime() == 0 && !isActive)
|
if (editable && clock.getDisplayTime() == 0 && !isActive)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JButton;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
@@ -40,9 +41,9 @@ import net.runelite.client.plugins.timetracking.TimeTrackingPlugin;
|
|||||||
import net.runelite.client.ui.ColorScheme;
|
import net.runelite.client.ui.ColorScheme;
|
||||||
import net.runelite.client.ui.DynamicGridLayout;
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
import net.runelite.client.ui.FontManager;
|
import net.runelite.client.ui.FontManager;
|
||||||
import net.runelite.client.ui.components.IconButton;
|
|
||||||
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
||||||
import net.runelite.client.util.ImageUtil;
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
import net.runelite.client.util.SwingUtil;
|
||||||
|
|
||||||
public class ClockTabPanel extends TabContentPanel
|
public class ClockTabPanel extends TabContentPanel
|
||||||
{
|
{
|
||||||
@@ -74,15 +75,15 @@ public class ClockTabPanel extends TabContentPanel
|
|||||||
BufferedImage addIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "add_icon.png");
|
BufferedImage addIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "add_icon.png");
|
||||||
|
|
||||||
DELETE_ICON = new ImageIcon(deleteIcon);
|
DELETE_ICON = new ImageIcon(deleteIcon);
|
||||||
DELETE_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(deleteIcon, -80));
|
DELETE_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(deleteIcon, -80));
|
||||||
LAP_ICON = new ImageIcon(lapIcon);
|
LAP_ICON = new ImageIcon(lapIcon);
|
||||||
LAP_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(lapIcon, -80));
|
LAP_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(lapIcon, -80));
|
||||||
PAUSE_ICON = new ImageIcon(pauseIcon);
|
PAUSE_ICON = new ImageIcon(pauseIcon);
|
||||||
PAUSE_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(pauseIcon, -80));
|
PAUSE_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(pauseIcon, -80));
|
||||||
RESET_ICON = new ImageIcon(resetIcon);
|
RESET_ICON = new ImageIcon(resetIcon);
|
||||||
RESET_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(resetIcon, -80));
|
RESET_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(resetIcon, -80));
|
||||||
START_ICON = new ImageIcon(startIcon);
|
START_ICON = new ImageIcon(startIcon);
|
||||||
START_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(startIcon, -80));
|
START_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(startIcon, -80));
|
||||||
ADD_ICON = new ImageIcon(addIcon);
|
ADD_ICON = new ImageIcon(addIcon);
|
||||||
ADD_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f));
|
ADD_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f));
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,9 @@ public class ClockTabPanel extends TabContentPanel
|
|||||||
headerLabel.setFont(FontManager.getRunescapeSmallFont());
|
headerLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
panel.add(headerLabel, BorderLayout.CENTER);
|
panel.add(headerLabel, BorderLayout.CENTER);
|
||||||
|
|
||||||
IconButton addButton = new IconButton(ADD_ICON, ADD_ICON_HOVER);
|
JButton addButton = new JButton(ADD_ICON);
|
||||||
|
addButton.setRolloverIcon(ADD_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(addButton);
|
||||||
addButton.setPreferredSize(new Dimension(14, 14));
|
addButton.setPreferredSize(new Dimension(14, 14));
|
||||||
addButton.setToolTipText("Add a " + type);
|
addButton.setToolTipText("Add a " + type);
|
||||||
addButton.addActionListener(actionListener);
|
addButton.addActionListener(actionListener);
|
||||||
|
|||||||
@@ -30,13 +30,14 @@ import java.awt.GridBagConstraints;
|
|||||||
import java.awt.GridBagLayout;
|
import java.awt.GridBagLayout;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import javax.swing.JButton;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import net.runelite.client.ui.ColorScheme;
|
import net.runelite.client.ui.ColorScheme;
|
||||||
import net.runelite.client.ui.FontManager;
|
import net.runelite.client.ui.FontManager;
|
||||||
import net.runelite.client.ui.components.IconButton;
|
import net.runelite.client.util.SwingUtil;
|
||||||
|
|
||||||
class StopwatchPanel extends ClockPanel
|
class StopwatchPanel extends ClockPanel
|
||||||
{
|
{
|
||||||
@@ -57,7 +58,9 @@ class StopwatchPanel extends ClockPanel
|
|||||||
|
|
||||||
contentContainer.add(lapsContainer);
|
contentContainer.add(lapsContainer);
|
||||||
|
|
||||||
IconButton lapButton = new IconButton(ClockTabPanel.LAP_ICON, ClockTabPanel.LAP_ICON_HOVER);
|
JButton lapButton = new JButton(ClockTabPanel.LAP_ICON);
|
||||||
|
lapButton.setRolloverIcon(ClockTabPanel.LAP_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(lapButton);
|
||||||
lapButton.setPreferredSize(new Dimension(16, 14));
|
lapButton.setPreferredSize(new Dimension(16, 14));
|
||||||
lapButton.setToolTipText("Add lap time");
|
lapButton.setToolTipText("Add lap time");
|
||||||
|
|
||||||
@@ -70,7 +73,9 @@ class StopwatchPanel extends ClockPanel
|
|||||||
|
|
||||||
leftActions.add(lapButton);
|
leftActions.add(lapButton);
|
||||||
|
|
||||||
IconButton deleteButton = new IconButton(ClockTabPanel.DELETE_ICON, ClockTabPanel.DELETE_ICON_HOVER);
|
JButton deleteButton = new JButton(ClockTabPanel.DELETE_ICON);
|
||||||
|
deleteButton.setRolloverIcon(ClockTabPanel.DELETE_ICON_HOVER);
|
||||||
|
SwingUtil.removeButtonDecorations(deleteButton);
|
||||||
deleteButton.setPreferredSize(new Dimension(16, 14));
|
deleteButton.setPreferredSize(new Dimension(16, 14));
|
||||||
deleteButton.setToolTipText("Delete stopwatch");
|
deleteButton.setToolTipText("Delete stopwatch");
|
||||||
deleteButton.addActionListener(e -> clockManager.removeStopwatch(stopwatch));
|
deleteButton.addActionListener(e -> clockManager.removeStopwatch(stopwatch));
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
package net.runelite.client.plugins.timetracking.clocks;
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import net.runelite.client.ui.components.IconButton;
|
import javax.swing.JButton;
|
||||||
|
import net.runelite.client.util.SwingUtil;
|
||||||
|
|
||||||
class TimerPanel extends ClockPanel
|
class TimerPanel extends ClockPanel
|
||||||
{
|
{
|
||||||
@@ -33,7 +34,9 @@ class TimerPanel extends ClockPanel
|
|||||||
{
|
{
|
||||||
super(clockManager, timer, "timer", true);
|
super(clockManager, timer, "timer", true);
|
||||||
|
|
||||||
IconButton deleteButton = new IconButton(ClockTabPanel.DELETE_ICON, ClockTabPanel.DELETE_ICON_HOVER);
|
JButton deleteButton = new JButton(ClockTabPanel.DELETE_ICON);
|
||||||
|
SwingUtil.removeButtonDecorations(deleteButton);
|
||||||
|
deleteButton.setRolloverIcon(ClockTabPanel.DELETE_ICON_HOVER);
|
||||||
deleteButton.setPreferredSize(new Dimension(16, 14));
|
deleteButton.setPreferredSize(new Dimension(16, 14));
|
||||||
deleteButton.setToolTipText("Delete timer");
|
deleteButton.setToolTipText("Delete timer");
|
||||||
deleteButton.addActionListener(e -> clockManager.removeTimer(timer));
|
deleteButton.addActionListener(e -> clockManager.removeTimer(timer));
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class WorldTableHeader extends JPanel
|
|||||||
{
|
{
|
||||||
final BufferedImage arrowDown = ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "arrow_down.png");
|
final BufferedImage arrowDown = ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "arrow_down.png");
|
||||||
final BufferedImage arrowUp = ImageUtil.rotateImage(arrowDown, Math.PI);
|
final BufferedImage arrowUp = ImageUtil.rotateImage(arrowDown, Math.PI);
|
||||||
final BufferedImage arrowUpFaded = ImageUtil.grayscaleOffset(arrowUp, -80);
|
final BufferedImage arrowUpFaded = ImageUtil.luminanceOffset(arrowUp, -80);
|
||||||
ARROW_UP = new ImageIcon(arrowUpFaded);
|
ARROW_UP = new ImageIcon(arrowUpFaded);
|
||||||
|
|
||||||
final BufferedImage highlightArrowDown = ImageUtil.fillImage(arrowDown, HIGHLIGHT_COLOR);
|
final BufferedImage highlightArrowDown = ImageUtil.fillImage(arrowDown, HIGHLIGHT_COLOR);
|
||||||
|
|||||||
@@ -65,8 +65,10 @@ import static net.runelite.client.rs.ClientUpdateCheckMode.NONE;
|
|||||||
import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA;
|
import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA;
|
||||||
import net.runelite.client.ui.FatalErrorDialog;
|
import net.runelite.client.ui.FatalErrorDialog;
|
||||||
import net.runelite.client.ui.SplashScreen;
|
import net.runelite.client.ui.SplashScreen;
|
||||||
|
import net.runelite.client.util.CountingInputStream;
|
||||||
import net.runelite.http.api.RuneLiteAPI;
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
import net.runelite.http.api.worlds.World;
|
import net.runelite.http.api.worlds.World;
|
||||||
|
import net.runelite.client.util.VerificationException;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|||||||
@@ -100,8 +100,11 @@ public class DynamicGridLayout extends GridLayout
|
|||||||
|
|
||||||
// scaling factors
|
// scaling factors
|
||||||
final Dimension pd = preferredLayoutSize(parent);
|
final Dimension pd = preferredLayoutSize(parent);
|
||||||
final double sw = (1.0 * parent.getWidth()) / pd.width;
|
final Insets parentInsets = parent.getInsets();
|
||||||
final double sh = (1.0 * parent.getHeight()) / pd.height;
|
int wborder = parentInsets.left + parentInsets.right;
|
||||||
|
int hborder = parentInsets.top + parentInsets.bottom;
|
||||||
|
final double sw = (1.0 * parent.getWidth() - wborder) / (pd.width - wborder);
|
||||||
|
final double sh = (1.0 * parent.getHeight() - hborder) / (pd.height - hborder);
|
||||||
|
|
||||||
final int[] w = new int[ncols];
|
final int[] w = new int[ncols];
|
||||||
final int[] h = new int[nrows];
|
final int[] h = new int[nrows];
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import javax.swing.border.EmptyBorder;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.runelite.client.RuneLite;
|
import net.runelite.client.RuneLite;
|
||||||
import net.runelite.client.RuneLiteProperties;
|
import net.runelite.client.RuneLiteProperties;
|
||||||
import net.runelite.client.rs.VerificationException;
|
import net.runelite.client.util.VerificationException;
|
||||||
import net.runelite.client.util.LinkBrowser;
|
import net.runelite.client.util.LinkBrowser;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 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.CardLayout;
|
||||||
|
|
||||||
|
public class MultiplexingPluginPanel extends PluginPanel
|
||||||
|
{
|
||||||
|
private final CardLayout layout;
|
||||||
|
private boolean active = false;
|
||||||
|
private PluginPanel current;
|
||||||
|
|
||||||
|
public MultiplexingPluginPanel(PluginPanel root)
|
||||||
|
{
|
||||||
|
super(false);
|
||||||
|
|
||||||
|
layout = new CardLayout();
|
||||||
|
setLayout(layout);
|
||||||
|
pushState(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
for (int i = getComponentCount() - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
onRemove((PluginPanel) getComponent(i));
|
||||||
|
remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushState(PluginPanel subpanel)
|
||||||
|
{
|
||||||
|
int index = -1;
|
||||||
|
for (int i = getComponentCount() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (getComponent(i) == subpanel)
|
||||||
|
{
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
current.onDeactivate();
|
||||||
|
subpanel.onActivate();
|
||||||
|
}
|
||||||
|
current = subpanel;
|
||||||
|
|
||||||
|
String name = System.identityHashCode(subpanel) + "";
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
for (int i = getComponentCount() - 1; i > index; i--)
|
||||||
|
{
|
||||||
|
popState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add(subpanel, name);
|
||||||
|
onAdd(subpanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.show(this, name);
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popState()
|
||||||
|
{
|
||||||
|
int count = getComponentCount();
|
||||||
|
if (count <= 1)
|
||||||
|
{
|
||||||
|
assert false : "Cannot pop last component";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginPanel subpanel = (PluginPanel) getComponent(count - 2);
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
current.onDeactivate();
|
||||||
|
subpanel.onActivate();
|
||||||
|
current = subpanel;
|
||||||
|
}
|
||||||
|
layout.show(this, System.identityHashCode(subpanel) + "");
|
||||||
|
onRemove((PluginPanel) getComponent(count - 1));
|
||||||
|
remove(count - 1);
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onAdd(PluginPanel p)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onRemove(PluginPanel p)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivate()
|
||||||
|
{
|
||||||
|
active = true;
|
||||||
|
current.onActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeactivate()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
current.onDeactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,9 +36,9 @@ import lombok.Getter;
|
|||||||
public abstract class PluginPanel extends JPanel
|
public abstract class PluginPanel extends JPanel
|
||||||
{
|
{
|
||||||
public static final int PANEL_WIDTH = 225;
|
public static final int PANEL_WIDTH = 225;
|
||||||
private static final int SCROLLBAR_WIDTH = 17;
|
public static final int SCROLLBAR_WIDTH = 17;
|
||||||
private static final int OFFSET = 6;
|
public static final int BORDER_OFFSET = 6;
|
||||||
private static final EmptyBorder BORDER_PADDING = new EmptyBorder(OFFSET, OFFSET, OFFSET, OFFSET);
|
private static final EmptyBorder BORDER_PADDING = new EmptyBorder(BORDER_OFFSET, BORDER_OFFSET, BORDER_OFFSET, BORDER_OFFSET);
|
||||||
private static final Dimension OUTER_PREFERRED_SIZE = new Dimension(PluginPanel.PANEL_WIDTH + SCROLLBAR_WIDTH, 0);
|
private static final Dimension OUTER_PREFERRED_SIZE = new Dimension(PluginPanel.PANEL_WIDTH + SCROLLBAR_WIDTH, 0);
|
||||||
|
|
||||||
@Getter(AccessLevel.PROTECTED)
|
@Getter(AccessLevel.PROTECTED)
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ import javax.swing.UIManager;
|
|||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import javax.swing.plaf.basic.BasicProgressBarUI;
|
import javax.swing.plaf.basic.BasicProgressBarUI;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.ui.skin.SubstanceRuneLiteLookAndFeel;
|
||||||
|
import org.pushingpixels.substance.internal.SubstanceSynapse;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SplashScreen extends JFrame implements ActionListener
|
public class SplashScreen extends JFrame implements ActionListener
|
||||||
@@ -151,6 +153,11 @@ public class SplashScreen extends JFrame implements ActionListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isOpen()
|
||||||
|
{
|
||||||
|
return INSTANCE != null;
|
||||||
|
}
|
||||||
|
|
||||||
public static void init()
|
public static void init()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -164,8 +171,16 @@ public class SplashScreen extends JFrame implements ActionListener
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
|
boolean hasLAF = UIManager.getLookAndFeel() instanceof SubstanceRuneLiteLookAndFeel;
|
||||||
|
if (!hasLAF)
|
||||||
|
{
|
||||||
|
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
|
||||||
|
}
|
||||||
INSTANCE = new SplashScreen();
|
INSTANCE = new SplashScreen();
|
||||||
|
if (hasLAF)
|
||||||
|
{
|
||||||
|
INSTANCE.getRootPane().putClientProperty(SubstanceSynapse.COLORIZATION_FACTOR, 1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -206,8 +221,9 @@ public class SplashScreen extends JFrame implements ActionListener
|
|||||||
String progress;
|
String progress;
|
||||||
if (mib)
|
if (mib)
|
||||||
{
|
{
|
||||||
final double MiB = 1024 * 1042;
|
final double MiB = 1024 * 1024;
|
||||||
progress = String.format("%.1f / %.1f MiB", done / MiB, total / MiB);
|
final double CEIL = 1.d / 10.d;
|
||||||
|
progress = String.format("%.1f / %.1f MiB", done / MiB, (total / MiB) + CEIL);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,18 +22,18 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.rs;
|
package net.runelite.client.util;
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
class CountingInputStream extends FilterInputStream
|
public class CountingInputStream extends FilterInputStream
|
||||||
{
|
{
|
||||||
private final IntConsumer changed;
|
private final IntConsumer changed;
|
||||||
|
|
||||||
CountingInputStream(InputStream in, IntConsumer changed)
|
public CountingInputStream(InputStream in, IntConsumer changed)
|
||||||
{
|
{
|
||||||
super(in);
|
super(in);
|
||||||
this.changed = changed;
|
this.changed = changed;
|
||||||
@@ -70,23 +70,37 @@ public class ImageUtil
|
|||||||
return (BufferedImage) image;
|
return (BufferedImage) image;
|
||||||
}
|
}
|
||||||
|
|
||||||
final BufferedImage out = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
|
return toARGB(image);
|
||||||
final Graphics2D g2d = out.createGraphics();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ARGB {@link BufferedImage} from an {@link Image}.
|
||||||
|
*/
|
||||||
|
public static BufferedImage toARGB(final Image image)
|
||||||
|
{
|
||||||
|
if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_ARGB)
|
||||||
|
{
|
||||||
|
return (BufferedImage) image;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage out = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g2d = out.createGraphics();
|
||||||
g2d.drawImage(image, 0, 0, null);
|
g2d.drawImage(image, 0, 0, null);
|
||||||
g2d.dispose();
|
g2d.dispose();
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offsets an image in the grayscale (darkens/brightens) by a given offset.
|
* Offsets an image's luminance by a given value.
|
||||||
*
|
*
|
||||||
* @param image The image to be darkened or brightened.
|
* @param rawImg The image to be darkened or brightened.
|
||||||
* @param offset A signed 8-bit integer value to brighten or darken the image with.
|
* @param offset A signed 8-bit integer value to brighten or darken the image with.
|
||||||
* Values above 0 will brighten, and values below 0 will darken.
|
* Values above 0 will brighten, and values below 0 will darken.
|
||||||
* @return The given image with its brightness adjusted by the given offset.
|
* @return The given image with its brightness adjusted by the given offset.
|
||||||
*/
|
*/
|
||||||
public static BufferedImage grayscaleOffset(final BufferedImage image, final int offset)
|
public static BufferedImage luminanceOffset(final Image rawImg, final int offset)
|
||||||
{
|
{
|
||||||
|
BufferedImage image = toARGB(rawImg);
|
||||||
final float offsetFloat = (float) offset;
|
final float offsetFloat = (float) offset;
|
||||||
final int numComponents = image.getColorModel().getNumComponents();
|
final int numComponents = image.getColorModel().getNumComponents();
|
||||||
final float[] scales = new float[numComponents];
|
final float[] scales = new float[numComponents];
|
||||||
@@ -104,15 +118,16 @@ public class ImageUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offsets an image in the grayscale (darkens/brightens) by a given percentage.
|
* Changes an images luminance by a scaling factor
|
||||||
*
|
*
|
||||||
* @param image The image to be darkened or brightened.
|
* @param rawImg The image to be darkened or brightened.
|
||||||
* @param percentage The ratio to darken or brighten the given image.
|
* @param percentage The ratio to darken or brighten the given image.
|
||||||
* Values above 1 will brighten, and values below 1 will darken.
|
* Values above 1 will brighten, and values below 1 will darken.
|
||||||
* @return The given image with its brightness scaled by the given percentage.
|
* @return The given image with its brightness scaled by the given percentage.
|
||||||
*/
|
*/
|
||||||
public static BufferedImage grayscaleOffset(final BufferedImage image, final float percentage)
|
public static BufferedImage luminanceScale(final Image rawImg, final float percentage)
|
||||||
{
|
{
|
||||||
|
BufferedImage image = toARGB(rawImg);
|
||||||
final int numComponents = image.getColorModel().getNumComponents();
|
final int numComponents = image.getColorModel().getNumComponents();
|
||||||
final float[] scales = new float[numComponents];
|
final float[] scales = new float[numComponents];
|
||||||
final float[] offsets = new float[numComponents];
|
final float[] offsets = new float[numComponents];
|
||||||
@@ -131,14 +146,15 @@ public class ImageUtil
|
|||||||
/**
|
/**
|
||||||
* Offsets an image's alpha component by a given offset.
|
* Offsets an image's alpha component by a given offset.
|
||||||
*
|
*
|
||||||
* @param image The image to be made more or less transparent.
|
* @param rawImg The image to be made more or less transparent.
|
||||||
* @param offset A signed 8-bit integer value to modify the image's alpha component with.
|
* @param offset A signed 8-bit integer value to modify the image's alpha component with.
|
||||||
* Values above 0 will increase transparency, and values below 0 will decrease
|
* Values above 0 will increase transparency, and values below 0 will decrease
|
||||||
* transparency.
|
* transparency.
|
||||||
* @return The given image with its alpha component adjusted by the given offset.
|
* @return The given image with its alpha component adjusted by the given offset.
|
||||||
*/
|
*/
|
||||||
public static BufferedImage alphaOffset(final BufferedImage image, final int offset)
|
public static BufferedImage alphaOffset(final Image rawImg, final int offset)
|
||||||
{
|
{
|
||||||
|
BufferedImage image = toARGB(rawImg);
|
||||||
final float offsetFloat = (float) offset;
|
final float offsetFloat = (float) offset;
|
||||||
final int numComponents = image.getColorModel().getNumComponents();
|
final int numComponents = image.getColorModel().getNumComponents();
|
||||||
final float[] scales = new float[numComponents];
|
final float[] scales = new float[numComponents];
|
||||||
@@ -153,14 +169,15 @@ public class ImageUtil
|
|||||||
/**
|
/**
|
||||||
* Offsets an image's alpha component by a given percentage.
|
* Offsets an image's alpha component by a given percentage.
|
||||||
*
|
*
|
||||||
* @param image The image to be made more or less transparent.
|
* @param rawImg The image to be made more or less transparent.
|
||||||
* @param percentage The ratio to modify the image's alpha component with.
|
* @param percentage The ratio to modify the image's alpha component with.
|
||||||
* Values above 1 will increase transparency, and values below 1 will decrease
|
* Values above 1 will increase transparency, and values below 1 will decrease
|
||||||
* transparency.
|
* transparency.
|
||||||
* @return The given image with its alpha component scaled by the given percentage.
|
* @return The given image with its alpha component scaled by the given percentage.
|
||||||
*/
|
*/
|
||||||
public static BufferedImage alphaOffset(final BufferedImage image, final float percentage)
|
public static BufferedImage alphaOffset(final Image rawImg, final float percentage)
|
||||||
{
|
{
|
||||||
|
BufferedImage image = toARGB(rawImg);
|
||||||
final int numComponents = image.getColorModel().getNumComponents();
|
final int numComponents = image.getColorModel().getNumComponents();
|
||||||
final float[] scales = new float[numComponents];
|
final float[] scales = new float[numComponents];
|
||||||
final float[] offsets = new float[numComponents];
|
final float[] offsets = new float[numComponents];
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import java.awt.Color;
|
|||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Frame;
|
import java.awt.Frame;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
|
import java.awt.Insets;
|
||||||
import java.awt.SystemTray;
|
import java.awt.SystemTray;
|
||||||
import java.awt.TrayIcon;
|
import java.awt.TrayIcon;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
@@ -41,6 +42,7 @@ import java.util.concurrent.Callable;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.swing.AbstractButton;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
@@ -277,4 +279,18 @@ public class SwingUtil
|
|||||||
navigationButton.setOnSelect(button::doClick);
|
navigationButton.setOnSelect(button::doClick);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void removeButtonDecorations(AbstractButton button)
|
||||||
|
{
|
||||||
|
button.setBorderPainted(false);
|
||||||
|
button.setContentAreaFilled(false);
|
||||||
|
button.setFocusPainted(false);
|
||||||
|
button.setMargin(new Insets(0, 0, 0, 0));
|
||||||
|
button.setOpaque(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addModalTooltip(AbstractButton button, String on, String off)
|
||||||
|
{
|
||||||
|
button.addItemListener(l -> button.setToolTipText(button.isSelected() ? on : off));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.rs;
|
package net.runelite.client.util;
|
||||||
|
|
||||||
public class VerificationException extends Exception
|
public class VerificationException extends Exception
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDDDCCAfSgAwIBAgIJAK8uBanmNQZaMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||||
|
BAMMEHJ1bmVsaXRlLXBsdWdpbnMwHhcNMTkxMjEyMjEwNzUxWhcNMjUxMjEwMjEw
|
||||||
|
NzUxWjAbMRkwFwYDVQQDDBBydW5lbGl0ZS1wbHVnaW5zMIIBIjANBgkqhkiG9w0B
|
||||||
|
AQEFAAOCAQ8AMIIBCgKCAQEApu11OVANSU+pHaXRxB7fIZapucJ6BT46neicEixs
|
||||||
|
NVPuK/QRVjO/G8F++MXFD/tlZUOEDllDN8uaHBIVwxilqEVYL7oX65Esl7qqC1TZ
|
||||||
|
WGdjiMyYoK3CXWEWB4w+CdB31T7JG2HqH45ZsVs+U9OVWBkNkL5nNQNPOmZFd+3A
|
||||||
|
yCb9nGlO7SxduiHpwh3CV19jY47y8tevyo5qpaBuQeWtu3vbpeer0kbDarwD3xoF
|
||||||
|
yUMPRK518gxRUSmOpsSG5viQ731mKVCUUfIXz91d3s+kJYAjORHS4zJe9s+1dljp
|
||||||
|
oLYNLkaP6m3CmNtC84OxkmognvZTNMbiQ3GQm/BK4sdjPQIDAQABo1MwUTAdBgNV
|
||||||
|
HQ4EFgQUxrkiRXNd0OHPMkqgl9UgV1//OuQwHwYDVR0jBBgwFoAUxrkiRXNd0OHP
|
||||||
|
Mkqgl9UgV1//OuQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA
|
||||||
|
StPyblz3aqOM5z2KqHX1B7Z3Q8B58g55YSefpcfwWEc6LT4HCztszcZDteWpV3W2
|
||||||
|
ERfemkGKgsDhQ0qkzIt7tS5eNN3PPj7RZZm7vl5HquQ1vC/33ri/Z3CEKzbW7knt
|
||||||
|
i1iEpx8E9DKb9J9DjdKwNxSomOyCOFUt9YoQJs80xc1mwPDd6aWR3xwvnEUimkm+
|
||||||
|
Dbj7HMOXLeyN810wkeWcT8nC5GhxH3ZAmVExBHsaIOB876RntzshBehjY8s8JQhw
|
||||||
|
R+fT1e8EhYMM9ylYDk1KIWFWrAujjU04lS9tXZ5C2e7fr9R953XN6Y0PNM/taNTU
|
||||||
|
GzwGroJZI02V+1ADO14rRA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 410 B |
Binary file not shown.
|
After Width: | Height: | Size: 477 B |
Binary file not shown.
|
After Width: | Height: | Size: 764 B |
@@ -11,3 +11,5 @@ runelite.wiki.building.link=https://github.com/runelite/runelite/wiki/Building-w
|
|||||||
runelite.dnschange.link=https://1.1.1.1/dns/
|
runelite.dnschange.link=https://1.1.1.1/dns/
|
||||||
runelite.jav_config=http://oldschool.runescape.com/jav_config.ws
|
runelite.jav_config=http://oldschool.runescape.com/jav_config.ws
|
||||||
runelite.jav_config_backup=http://static.runelite.net/jav_config.ws
|
runelite.jav_config_backup=http://static.runelite.net/jav_config.ws
|
||||||
|
runelite.pluginhub.url=https://repo.runelite.net/plugins
|
||||||
|
runelite.pluginhub.version=${project.version}
|
||||||
@@ -81,25 +81,25 @@ public class ImageUtilTest
|
|||||||
public void grayscaleOffset()
|
public void grayscaleOffset()
|
||||||
{
|
{
|
||||||
// grayscaleOffset(BufferedImage image, int offset)
|
// grayscaleOffset(BufferedImage image, int offset)
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(BLACK), -255)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceOffset(oneByOne(BLACK), -255)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(new Color(50, 50, 50)), ImageUtil.grayscaleOffset(oneByOne(BLACK), 50)));
|
assertTrue(bufferedImagesEqual(oneByOne(new Color(50, 50, 50)), ImageUtil.luminanceOffset(oneByOne(BLACK), 50)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(GRAY), ImageUtil.grayscaleOffset(oneByOne(BLACK), 128)));
|
assertTrue(bufferedImagesEqual(oneByOne(GRAY), ImageUtil.luminanceOffset(oneByOne(BLACK), 128)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(GRAY), -255)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceOffset(oneByOne(GRAY), -255)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.grayscaleOffset(oneByOne(BLACK), 255)));
|
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.luminanceOffset(oneByOne(BLACK), 255)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(new Color(200, 200, 200)), ImageUtil.grayscaleOffset(oneByOne(WHITE), -55)));
|
assertTrue(bufferedImagesEqual(oneByOne(new Color(200, 200, 200)), ImageUtil.luminanceOffset(oneByOne(WHITE), -55)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.grayscaleOffset(oneByOne(WHITE), 55)));
|
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.luminanceOffset(oneByOne(WHITE), 55)));
|
||||||
|
|
||||||
// grayscaleOffset(BufferedImage image, float percentage)
|
// grayscaleOffset(BufferedImage image, float percentage)
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(BLACK), 0f)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceScale(oneByOne(BLACK), 0f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(BLACK), 1f)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceScale(oneByOne(BLACK), 1f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(BLACK), 2f)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceScale(oneByOne(BLACK), 2f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(GRAY), 0f)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceScale(oneByOne(GRAY), 0f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(GRAY), ImageUtil.grayscaleOffset(oneByOne(GRAY), 1f)));
|
assertTrue(bufferedImagesEqual(oneByOne(GRAY), ImageUtil.luminanceScale(oneByOne(GRAY), 1f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.grayscaleOffset(oneByOne(GRAY), 2f)));
|
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.luminanceScale(oneByOne(GRAY), 2f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.grayscaleOffset(oneByOne(WHITE), 0f)));
|
assertTrue(bufferedImagesEqual(oneByOne(BLACK), ImageUtil.luminanceScale(oneByOne(WHITE), 0f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(GRAY), ImageUtil.grayscaleOffset(oneByOne(WHITE), 0.503f))); // grayscaleOffset does Math.floor
|
assertTrue(bufferedImagesEqual(oneByOne(GRAY), ImageUtil.luminanceScale(oneByOne(WHITE), 0.503f))); // grayscaleOffset does Math.floor
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.grayscaleOffset(oneByOne(WHITE), 1f)));
|
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.luminanceScale(oneByOne(WHITE), 1f)));
|
||||||
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.grayscaleOffset(oneByOne(WHITE), 2f)));
|
assertTrue(bufferedImagesEqual(oneByOne(WHITE), ImageUtil.luminanceScale(oneByOne(WHITE), 2f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user