diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index db09d65a93..6fdff16707 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -35,7 +35,6 @@ import java.applet.Applet; import java.io.File; import java.util.Optional; import javax.inject.Singleton; -import javax.swing.SwingUtilities; import joptsimple.OptionParser; import joptsimple.OptionSet; import lombok.extern.slf4j.Slf4j; @@ -43,12 +42,11 @@ import net.runelite.api.Client; import net.runelite.client.account.SessionManager; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.config.ConfigManager; -import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.discord.DiscordService; -import net.runelite.client.events.ClientUILoaded; import net.runelite.client.menus.MenuManager; import net.runelite.client.plugins.PluginManager; import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.TitleToolbar; import net.runelite.client.ui.overlay.OverlayRenderer; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -66,9 +64,6 @@ public class RuneLite private static Injector injector; private static OptionSet options; - @Inject - private RuneLiteProperties properties; - @Inject private PluginManager pluginManager; @@ -90,17 +85,19 @@ public class RuneLite @Inject private SessionManager sessionManager; - @Inject - private RuneLiteConfig runeliteConfig; - @Inject private DiscordService discordService; @Inject private ClientSessionManager clientSessionManager; + @Inject + private ClientUI clientUI; + + @Inject + private TitleToolbar titleToolbar; + Client client; - ClientUI gui; public static void main(String[] args) throws Exception { @@ -147,8 +144,8 @@ public class RuneLite this.client = (Client) client; } - // Load swing UI - SwingUtilities.invokeAndWait(() -> setGui(ClientUI.create(this, properties, client))); + // Initialize UI + clientUI.init(client); // Initialize Discord service discordService.init(); @@ -157,10 +154,10 @@ public class RuneLite configManager.load(); // Register event listeners + eventBus.register(clientUI); eventBus.register(overlayRenderer); eventBus.register(menuManager); eventBus.register(chatMessageManager); - eventBus.register(gui); eventBus.register(pluginManager); // Tell the plugin manager if client is outdated or not @@ -183,28 +180,11 @@ public class RuneLite // Load the session, including saved configuration sessionManager.loadSession(); - SwingUtilities.invokeAndWait(() -> - { - if (client != null) - { - client.setSize(runeliteConfig.gameSize()); - client.setPreferredSize(runeliteConfig.gameSize()); + // Refresh title toolbar + titleToolbar.refresh(); - client.getParent().setPreferredSize(runeliteConfig.gameSize()); - client.getParent().setSize(runeliteConfig.gameSize()); - } - - gui.showWithChrome(runeliteConfig.enableCustomChrome()); - - if (gui.isAlwaysOnTopSupported()) - { - gui.setAlwaysOnTop(runeliteConfig.gameAlwaysOnTop()); - } - - gui.setResizable(!runeliteConfig.lockWindowSize()); - }); - - eventBus.post(new ClientUILoaded()); + // Show UI after all plugins are loaded + clientUI.show(); } public void shutdown() @@ -213,11 +193,6 @@ public class RuneLite discordService.close(); } - public void setGui(ClientUI gui) - { - this.gui = gui; - } - @VisibleForTesting public void setClient(Client client) { diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java index 24c49fd308..3110e16736 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java @@ -41,7 +41,6 @@ import net.runelite.client.game.ItemManager; import net.runelite.client.menus.MenuManager; import net.runelite.client.plugins.PluginManager; import net.runelite.client.task.Scheduler; -import net.runelite.client.ui.ClientUI; import net.runelite.client.util.QueryRunner; @Slf4j @@ -67,12 +66,6 @@ public class RuneLiteModule extends AbstractModule return runeLite.client; } - @Provides - ClientUI provideClientUi(RuneLite runelite) - { - return runelite.gui; - } - @Provides @Singleton RuneLiteConfig provideConfig(ConfigManager configManager) diff --git a/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java b/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java index 584583c5ca..f5a84d7fee 100644 --- a/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java +++ b/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java @@ -60,16 +60,14 @@ public class SessionManager private final EventBus eventBus; private ConfigManager configManager; private ScheduledExecutorService executor; - private final LinkBrowser browser; private final AccountClient loginClient = new AccountClient(); @Inject - public SessionManager(ConfigManager configManager, EventBus eventBus, ScheduledExecutorService executor, LinkBrowser browser) + public SessionManager(ConfigManager configManager, EventBus eventBus, ScheduledExecutorService executor) { this.configManager = configManager; this.eventBus = eventBus; this.executor = executor; - this.browser = browser; eventBus.register(this); } @@ -213,7 +211,7 @@ public class SessionManager openSession(new AccountSession(login.getUid(), Instant.now())); // Navigate to login link - browser.browse(login.getOauthUrl()); + LinkBrowser.browse(login.getOauthUrl()); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/events/PluginToolbarButtonAdded.java b/runelite-client/src/main/java/net/runelite/client/events/PluginToolbarButtonAdded.java new file mode 100644 index 0000000000..925a05e485 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/PluginToolbarButtonAdded.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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 lombok.Value; +import net.runelite.client.ui.NavigationButton; + +@Value +public class PluginToolbarButtonAdded +{ + private NavigationButton button; + private int index; +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/PluginToolbarButtonRemoved.java b/runelite-client/src/main/java/net/runelite/client/events/PluginToolbarButtonRemoved.java new file mode 100644 index 0000000000..67f2c5386d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/PluginToolbarButtonRemoved.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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 lombok.Value; +import net.runelite.client.ui.NavigationButton; + +@Value +public class PluginToolbarButtonRemoved +{ + private NavigationButton button; + private int index; +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/TitleToolbarButtonAdded.java b/runelite-client/src/main/java/net/runelite/client/events/TitleToolbarButtonAdded.java new file mode 100644 index 0000000000..a053963a6e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/TitleToolbarButtonAdded.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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 lombok.Value; +import net.runelite.client.ui.NavigationButton; + +@Value +public class TitleToolbarButtonAdded +{ + private NavigationButton button; + private int index; +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/TitleToolbarButtonRemoved.java b/runelite-client/src/main/java/net/runelite/client/events/TitleToolbarButtonRemoved.java new file mode 100644 index 0000000000..b0f39f05cb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/TitleToolbarButtonRemoved.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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 lombok.Value; +import net.runelite.client.ui.NavigationButton; + +@Value +public class TitleToolbarButtonRemoved +{ + private NavigationButton button; + private int index; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/account/AccountPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/account/AccountPlugin.java index d7c367177a..01ceaf4a72 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/account/AccountPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/account/AccountPlugin.java @@ -25,22 +25,20 @@ package net.runelite.client.plugins.account; import com.google.common.eventbus.Subscribe; -import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.concurrent.ScheduledExecutorService; import javax.imageio.ImageIO; import javax.inject.Inject; -import javax.swing.JButton; import javax.swing.JOptionPane; import lombok.extern.slf4j.Slf4j; import net.runelite.api.events.SessionClose; +import net.runelite.api.events.SessionOpen; import net.runelite.client.account.AccountSession; import net.runelite.client.account.SessionManager; -import net.runelite.api.events.SessionOpen; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.TitleToolbar; import net.runelite.client.util.RunnableExceptionLogger; @@ -55,13 +53,13 @@ public class AccountPlugin extends Plugin private SessionManager sessionManager; @Inject - private ClientUI ui; + private TitleToolbar titleToolbar; @Inject private ScheduledExecutorService executor; - private JButton loginButton; - private JButton logoutButton; + private NavigationButton loginButton; + private NavigationButton logoutButton; private static final BufferedImage LOGIN_IMAGE, LOGOUT_IMAGE; @@ -84,47 +82,45 @@ public class AccountPlugin extends Plugin @Override protected void startUp() throws Exception { - loginButton = new JButton(); - loginButton.setToolTipText("Login"); - loginButton.addActionListener(this::loginClick); + loginButton = NavigationButton.builder() + .icon(LOGIN_IMAGE) + .tooltip("Login") + .onClick(this::loginClick) + .build(); - logoutButton = new JButton(); - logoutButton.setToolTipText("Logout"); - logoutButton.addActionListener(this::logoutClick); + logoutButton = NavigationButton.builder() + .icon(LOGOUT_IMAGE) + .tooltip("Logout") + .onClick(this::logoutClick) + .build(); addAndRemoveButtons(); } private void addAndRemoveButtons() { - TitleToolbar tb = ui.getTitleToolbar(); - tb.remove(loginButton); - tb.remove(logoutButton); - if (sessionManager.getAccountSession() == null) - { - tb.addButton(loginButton, LOGIN_IMAGE, LOGIN_IMAGE); - } - else - { - tb.addButton(logoutButton, LOGOUT_IMAGE, LOGOUT_IMAGE); - } + titleToolbar.removeNavigation(loginButton); + titleToolbar.removeNavigation(logoutButton); + titleToolbar.addNavigation(sessionManager.getAccountSession() == null + ? loginButton + : logoutButton); } @Override protected void shutDown() throws Exception { - ui.getTitleToolbar().remove(loginButton); - ui.getTitleToolbar().remove(logoutButton); + titleToolbar.removeNavigation(loginButton); + titleToolbar.removeNavigation(logoutButton); } - private void loginClick(ActionEvent ae) + private void loginClick() { executor.execute(RunnableExceptionLogger.wrap(sessionManager::login)); } - private void logoutClick(ActionEvent ae) + private void logoutClick() { - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(ui, + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(null, "Are you sure you want to logout?", "Logout Confirmation", JOptionPane.YES_NO_OPTION)) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java index 9e3a42fab1..08a379875b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java @@ -36,8 +36,8 @@ import net.runelite.client.events.PluginChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginManager; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; @PluginDescriptor( name = "Configuration", @@ -47,7 +47,7 @@ import net.runelite.client.ui.NavigationButton; public class ConfigPlugin extends Plugin { @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private ConfigManager configManager; @@ -75,18 +75,19 @@ public class ConfigPlugin extends Plugin icon = ImageIO.read(getClass().getResourceAsStream("config_icon.png")); } - navButton = new NavigationButton( - "Configuration", - icon, - () -> configPanel); + navButton = NavigationButton.builder() + .name("Configuration") + .icon(icon) + .panel(configPanel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); } @Override protected void shutDown() throws Exception { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index f46f2bb90b..d7e071c48b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -35,9 +35,9 @@ import net.runelite.api.widgets.Widget; import net.runelite.client.config.ConfigManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; import net.runelite.client.ui.overlay.Overlay; @PluginDescriptor( @@ -47,7 +47,7 @@ import net.runelite.client.ui.overlay.Overlay; public class DevToolsPlugin extends Plugin { @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private DevToolsOverlay overlay; @@ -89,12 +89,13 @@ public class DevToolsPlugin extends Plugin icon = ImageIO.read(getClass().getResourceAsStream("devtools_icon.png")); } - navButton = new NavigationButton( - "Developer Tools", - icon, - () -> panel); + navButton = NavigationButton.builder() + .name("Developer Tools") + .icon(icon) + .panel(panel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); font = FontManager.getRunescapeFont() .deriveFont(Font.BOLD, 16); @@ -103,7 +104,7 @@ public class DevToolsPlugin extends Plugin @Override protected void shutDown() throws Exception { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java index d6d886e801..defce8acb8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java @@ -27,19 +27,25 @@ package net.runelite.client.plugins.discord; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import com.google.inject.Provides; +import java.awt.image.BufferedImage; import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; +import javax.imageio.ImageIO; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.Skill; import net.runelite.api.events.ExperienceChanged; import net.runelite.api.events.GameStateChanged; +import net.runelite.client.RuneLiteProperties; import net.runelite.client.config.ConfigManager; import net.runelite.client.discord.DiscordService; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.task.Schedule; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.TitleToolbar; +import net.runelite.client.util.LinkBrowser; @PluginDescriptor( name = "Discord" @@ -55,9 +61,16 @@ public class DiscordPlugin extends Plugin @Inject private DiscordService discordService; + @Inject + private TitleToolbar titleToolbar; + + @Inject + private RuneLiteProperties properties; + private final DiscordState discordState = new DiscordState(); private Map skillExp = new HashMap<>(); private boolean loggedIn = false; + private NavigationButton discordButton; @Provides private DiscordConfig provideConfig(ConfigManager configManager) @@ -68,12 +81,26 @@ public class DiscordPlugin extends Plugin @Override protected void startUp() throws Exception { + BufferedImage icon; + synchronized (ImageIO.class) + { + icon = ImageIO.read(getClass().getResourceAsStream("discord.png")); + } + + discordButton = NavigationButton.builder() + .tooltip("Join Discord") + .icon(icon) + .onClick(() -> LinkBrowser.browse(properties.getDiscordInvite())) + .build(); + + titleToolbar.addNavigation(discordButton); updateGameStatus(client.getGameState(), true); } @Override protected void shutDown() throws Exception { + titleToolbar.removeNavigation(discordButton); discordService.clearPresence(); discordState.reset(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java index f09ec8be62..19e2e94c30 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPanel.java @@ -120,13 +120,11 @@ class FeedPanel extends PluginPanel private final FeedConfig config; private final Supplier feedSupplier; - private final LinkBrowser linkBrowser; - FeedPanel(FeedConfig config, Supplier feedSupplier, LinkBrowser linkBrowser) + FeedPanel(FeedConfig config, Supplier feedSupplier) { this.config = config; this.feedSupplier = feedSupplier; - this.linkBrowser = linkBrowser; } void rebuildFeed() @@ -294,7 +292,7 @@ class FeedPanel extends PluginPanel public void mouseReleased(MouseEvent e) { avatarAndRight.setBackground(hoverColor); - linkBrowser.browse(item.getUrl()); + LinkBrowser.browse(item.getUrl()); } }); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java index 5ba1a07745..7f011e4fca 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java @@ -41,9 +41,8 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.task.Schedule; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; -import net.runelite.client.util.LinkBrowser; +import net.runelite.client.ui.PluginToolbar; import net.runelite.http.api.feed.FeedClient; import net.runelite.http.api.feed.FeedResult; @@ -55,7 +54,7 @@ import net.runelite.http.api.feed.FeedResult; public class FeedPlugin extends Plugin { @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private FeedConfig config; @@ -63,9 +62,6 @@ public class FeedPlugin extends Plugin @Inject private ScheduledExecutorService executorService; - @Inject - private LinkBrowser linkBrowser; - private FeedPanel feedPanel; private NavigationButton navButton; @@ -86,7 +82,7 @@ public class FeedPlugin extends Plugin @Override protected void startUp() throws Exception { - feedPanel = new FeedPanel(config, feedSupplier, linkBrowser); + feedPanel = new FeedPanel(config, feedSupplier); BufferedImage icon; synchronized (ImageIO.class) @@ -94,20 +90,20 @@ public class FeedPlugin extends Plugin icon = ImageIO.read(getClass().getResourceAsStream("icon.png")); } - navButton = new NavigationButton( - "News Feed", - icon, - () -> feedPanel); - - ui.getPluginToolbar().addNavigation(navButton); + navButton = NavigationButton.builder() + .name("News Feed") + .icon(icon) + .panel(feedPanel) + .build(); + pluginToolbar.addNavigation(navButton); executorService.submit(this::updateFeed); } @Override protected void shutDown() throws Exception { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } private void updateFeed() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java index 4428c17abc..9a33a42fda 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java @@ -45,10 +45,9 @@ import net.runelite.client.util.LinkBrowser; class GrandExchangeItemPanel extends JPanel { private static final NumberFormat NUMBER_FORMATTER = NumberFormat.getInstance(); - private static final Dimension ICON_SIZE = new Dimension(32, 32); - GrandExchangeItemPanel(LinkBrowser linkBrowser, BufferedImage icon, String name, int itemID, int gePrice, Double + GrandExchangeItemPanel(BufferedImage icon, String name, int itemID, int gePrice, Double haPrice) { BorderLayout layout = new BorderLayout(); @@ -75,7 +74,7 @@ class GrandExchangeItemPanel extends JPanel @Override public void mouseReleased(MouseEvent e) { - geLink(linkBrowser, name, itemID); + geLink(name, itemID); } }); @@ -125,13 +124,13 @@ class GrandExchangeItemPanel extends JPanel add(rightPanel, BorderLayout.CENTER); } - private void geLink(LinkBrowser linkBrowser, String name, int itemID) + private void geLink(String name, int itemID) { final String url = "http://services.runescape.com/m=itemdb_oldschool/" + name.replaceAll(" ", "_") + "/viewitem?obj=" + itemID; - linkBrowser.browse(url); + LinkBrowser.browse(url); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java index b43e1e42b5..1c956029cf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java @@ -37,7 +37,6 @@ import net.runelite.api.Client; import net.runelite.api.GrandExchangeOffer; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.PluginPanel; -import net.runelite.client.util.LinkBrowser; @Slf4j class GrandExchangePanel extends PluginPanel @@ -54,7 +53,7 @@ class GrandExchangePanel extends PluginPanel private JTabbedPane tabbedPane = new JTabbedPane(); @Inject - GrandExchangePanel(Client client, ItemManager itemManager, ScheduledExecutorService executor, LinkBrowser linkBrowser) + GrandExchangePanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) { setLayout(new BorderLayout()); add(tabbedPane, BorderLayout.NORTH); @@ -68,7 +67,7 @@ class GrandExchangePanel extends PluginPanel } // Search Panel - searchPanel = new GrandExchangeSearchPanel(client, itemManager, executor, linkBrowser); + searchPanel = new GrandExchangeSearchPanel(client, itemManager, executor); tabbedPane.addTab("Offers", offerPanel); tabbedPane.addTab("Search", searchPanel); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index 6fdf42708c..e78e4f50ca 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -48,8 +48,8 @@ import net.runelite.client.input.MouseListener; import net.runelite.client.input.MouseManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; @PluginDescriptor( name = "Grand Exchange" @@ -69,7 +69,7 @@ public class GrandExchangePlugin extends Plugin private Client client; @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private GrandExchangeConfig config; @@ -84,13 +84,20 @@ public class GrandExchangePlugin extends Plugin protected void startUp() throws IOException { panel = injector.getInstance(GrandExchangePanel.class); + BufferedImage icon; synchronized (ImageIO.class) { icon = ImageIO.read(getClass().getResourceAsStream("ge_icon.png")); } - button = new NavigationButton("GE Offers", icon, () -> panel); - ui.getPluginToolbar().addNavigation(button); + + button = NavigationButton.builder() + .name("GE Offers") + .icon(icon) + .panel(panel) + .build(); + + pluginToolbar.addNavigation(button); itemClick = new MouseListener() { @@ -119,7 +126,8 @@ public class GrandExchangePlugin extends Plugin if (!button.isSelected()) { - button.doClick(); + button.setSelected(true); + button.getOnSelect().run(); } panel.getSearchPanel().priceLookup(itemComp.getName()); @@ -145,8 +153,7 @@ public class GrandExchangePlugin extends Plugin @Override protected void shutDown() { - ui.getPluginToolbar().removeNavigation(button); - + pluginToolbar.removeNavigation(button); mouseManager.unregisterMouseListener(itemClick); } @@ -172,10 +179,6 @@ public class GrandExchangePlugin extends Plugin @Subscribe public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent) { - SwingUtilities.invokeLater(() -> - { - panel.updateOffer(offerEvent.getOffer(), offerEvent.getSlot()); - }); + SwingUtilities.invokeLater(() -> panel.updateOffer(offerEvent.getOffer(), offerEvent.getSlot())); } - } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java index aa5ff86e63..dfeb0966ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -46,7 +46,6 @@ import net.runelite.api.Client; import net.runelite.api.ItemComposition; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.hiscore.IconTextField; -import net.runelite.client.util.LinkBrowser; import net.runelite.http.api.item.Item; import net.runelite.http.api.item.ItemClient; import net.runelite.http.api.item.ItemPrice; @@ -60,7 +59,6 @@ class GrandExchangeSearchPanel extends JPanel private final Client client; private final ItemManager itemManager; private final ScheduledExecutorService executor; - private final LinkBrowser linkBrowser; private ItemClient itemClient; @@ -71,12 +69,11 @@ class GrandExchangeSearchPanel extends JPanel private JPanel searchItemsPanel = new JPanel(); private JLabel searchingLabel = new JLabel(); - GrandExchangeSearchPanel(Client client, ItemManager itemManager, ScheduledExecutorService executor, LinkBrowser linkBrowser) + GrandExchangeSearchPanel(Client client, ItemManager itemManager, ScheduledExecutorService executor) { this.client = client; this.itemManager = itemManager; this.executor = executor; - this.linkBrowser = linkBrowser; init(); } @@ -203,7 +200,7 @@ class GrandExchangeSearchPanel extends JPanel { for (GrandExchangeItems item : ITEMS_LIST) { - GrandExchangeItemPanel panel = new GrandExchangeItemPanel(linkBrowser, item.getIcon(), item.getName(), + GrandExchangeItemPanel panel = new GrandExchangeItemPanel(item.getIcon(), item.getName(), item.getItemId(), item.getGePrice(), item.getHaPrice()); searchItemsPanel.add(panel); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java index 1c806ba30d..c30c4c1669 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java @@ -38,8 +38,8 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.menus.MenuManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; @PluginDescriptor( name = "HiScore", @@ -50,7 +50,7 @@ public class HiscorePlugin extends Plugin private static final String LOOKUP = "Lookup"; @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private MenuManager menuManager; @@ -81,12 +81,13 @@ public class HiscorePlugin extends Plugin icon = ImageIO.read(getClass().getResourceAsStream("hiscore.gif")); } - navButton = new NavigationButton( - "Hiscore", - icon, - () -> hiscorePanel); + navButton = NavigationButton.builder() + .name("Hiscore") + .icon(icon) + .panel(hiscorePanel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); if (config.playerOption()) { @@ -97,8 +98,7 @@ public class HiscorePlugin extends Plugin @Override protected void shutDown() throws Exception { - ui.getPluginToolbar().removeNavigation(navButton); - + pluginToolbar.removeNavigation(navButton); menuManager.removePlayerMenuItem(LOOKUP); } @@ -129,7 +129,8 @@ public class HiscorePlugin extends Plugin { if (!navButton.isSelected()) { - navButton.doClick(); + navButton.setSelected(true); + navButton.getOnSelect().run(); } }); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java index c3d6030631..8465bbbef8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -26,15 +26,15 @@ package net.runelite.client.plugins.info; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; -import java.awt.Font; import com.google.inject.Inject; -import java.lang.reflect.InvocationTargetException; +import java.awt.Font; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; +import javax.inject.Singleton; import javax.swing.BorderFactory; import javax.swing.GroupLayout; +import javax.swing.JButton; import javax.swing.JLabel; -import javax.swing.JPanel; import javax.swing.LayoutStyle; import javax.swing.SwingUtilities; import javax.swing.event.HyperlinkEvent; @@ -46,23 +46,25 @@ import net.runelite.client.RuneLiteProperties; import net.runelite.client.account.SessionManager; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.events.ClientUILoaded; -import net.runelite.client.ui.ClientUI; +import net.runelite.client.events.TitleToolbarButtonAdded; +import net.runelite.client.events.TitleToolbarButtonRemoved; +import net.runelite.client.ui.ClientTitleToolbar; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; import net.runelite.client.util.RunnableExceptionLogger; +import net.runelite.client.util.SwingUtil; @Slf4j +@Singleton public class InfoPanel extends PluginPanel { - private final static String RUNELITE_LOGIN = "https://runelite_login/"; + private static final int TITLEBAR_SIZE = 23; + private static final String RUNELITE_LOGIN = "https://runelite_login/"; @Inject @Nullable private Client client; - @Inject - private ClientUI clientUI; - @Inject private RuneLiteConfig runeliteConfig; @@ -79,9 +81,7 @@ public class InfoPanel extends PluginPanel private ScheduledExecutorService executor; private final GroupLayout layout = new GroupLayout(this); - - private final JPanel toolbarPanelPlaceholder = new JPanel(); - + private final ClientTitleToolbar titleBar = new ClientTitleToolbar(); private final JLabel usernameHeader = new JLabel(); private final JRichTextPane username = new JRichTextPane(); @@ -90,8 +90,7 @@ public class InfoPanel extends PluginPanel setLayout(layout); final Font smallFont = FontManager.getRunescapeSmallFont(); - - toolbarPanelPlaceholder.setVisible(false); + updateTitleBar(); final JLabel runeliteVersionHeader = new JLabel("RuneLite version"); runeliteVersionHeader.setFont(smallFont); @@ -119,7 +118,8 @@ public class InfoPanel extends PluginPanel } } }); - setNotLoggedIn(); + + updateLoggedIn(); final JRichTextPane issueLink = new JRichTextPane("text/html", "RuneLite is open source!
" @@ -132,7 +132,7 @@ public class InfoPanel extends PluginPanel setBorder(BorderFactory.createEmptyBorder(2, 6, 6, 6)); layout.setVerticalGroup(layout.createSequentialGroup() - .addComponent(toolbarPanelPlaceholder) + .addComponent(titleBar) .addGap(3) .addGroup(layout.createParallelGroup() .addComponent(runeliteVersionHeader) @@ -152,16 +152,15 @@ public class InfoPanel extends PluginPanel layout.setHorizontalGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(toolbarPanelPlaceholder) - ).addGroup(layout.createSequentialGroup() + .addComponent(titleBar)) + .addGroup(layout.createSequentialGroup() .addComponent(runeliteVersionHeader) .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(runescapeVersionHeader) - ).addGroup(layout.createSequentialGroup() + .addComponent(runescapeVersionHeader)) + .addGroup(layout.createSequentialGroup() .addComponent(runeliteVersion) .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(runescapeVersion) - ) + .addComponent(runescapeVersion)) .addComponent(usernameHeader) .addComponent(username) .addComponent(issueLink) @@ -170,50 +169,81 @@ public class InfoPanel extends PluginPanel eventBus.register(this); } - private void setNotLoggedIn() + private void updateLoggedIn() { - username.setContentType("text/html"); - username.setText("Login to sync settings to the cloud."); - usernameHeader.setText("Not logged in"); - } + final String name = sessionManager.getAccountSession() != null + ? sessionManager.getAccountSession().getUsername() + : null; - @Subscribe - private void onClientUILoaded(ClientUILoaded e) - { - // Add the title toolbar to the infopanel if the custom chrome is disabled - if (!runeliteConfig.enableCustomChrome()) - { - try - { - SwingUtilities.invokeAndWait(() -> - { - JPanel toolbar = clientUI.getTitleToolbar(); - layout.replace(toolbarPanelPlaceholder, toolbar); - toolbar.revalidate(); - }); - } - catch (InterruptedException | InvocationTargetException ex) - { - throw new RuntimeException(ex); - } - } - } - - @Subscribe - public void onSessionOpen(SessionOpen sessionOpen) - { - String name = sessionManager.getAccountSession().getUsername(); if (name != null) { username.setContentType("text/plain"); username.setText(name); usernameHeader.setText("Logged in as"); } + else + { + username.setContentType("text/html"); + username.setText("Login to sync settings to the cloud."); + usernameHeader.setText("Not logged in"); + } + } + + private void updateTitleBar() + { + titleBar.setVisible(!runeliteConfig.enableCustomChrome()); } @Subscribe - private void onSessionClose(SessionClose e) + public void onClientUILoaded(ClientUILoaded e) { - setNotLoggedIn(); + // Add the title toolbar to the infopanel if the custom chrome is disabled + updateTitleBar(); + } + + @Subscribe + public void onSessionOpen(SessionOpen sessionOpen) + { + updateLoggedIn(); + } + + @Subscribe + public void onSessionClose(SessionClose e) + { + updateLoggedIn(); + } + + @Subscribe + public void onTitleToolbarButtonAdded(TitleToolbarButtonAdded event) + { + if (runeliteConfig.enableCustomChrome()) + { + return; + } + + SwingUtilities.invokeLater(() -> + { + final int iconSize = TITLEBAR_SIZE - 6; + final JButton button = SwingUtil.createSwingButton(event.getButton(), iconSize, null); + titleBar.addComponent(event.getButton(), button); + titleBar.revalidate(); + titleBar.repaint(); + }); + } + + @Subscribe + public void onTitleToolbarButtonRemoved(TitleToolbarButtonRemoved event) + { + if (runeliteConfig.enableCustomChrome()) + { + return; + } + + SwingUtilities.invokeLater(() -> + { + titleBar.removeComponent(event.getButton()); + titleBar.revalidate(); + titleBar.repaint(); + }); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java index ad1c730903..a79bf2bec5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java @@ -27,10 +27,12 @@ package net.runelite.client.plugins.info; import java.awt.image.BufferedImage; import javax.imageio.ImageIO; import javax.inject.Inject; +import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; +import net.runelite.client.ui.TitleToolbar; @PluginDescriptor( name = "Info Panel", @@ -39,7 +41,13 @@ import net.runelite.client.ui.NavigationButton; public class InfoPlugin extends Plugin { @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; + + @Inject + private TitleToolbar titleToolbar; + + @Inject + private RuneLiteConfig runeLiteConfig; private NavigationButton navButton; @@ -55,18 +63,23 @@ public class InfoPlugin extends Plugin icon = ImageIO.read(getClass().getResourceAsStream("info_icon.png")); } - navButton = new NavigationButton( - "Info", - icon, - () -> panel - ); + navButton = NavigationButton.builder() + .name("Info") + .icon(icon) + .panel(panel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); + + if (!runeLiteConfig.enableCustomChrome()) + { + titleToolbar.refresh(); + } } @Override protected void shutDown() { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java index 965efee38f..50213e05e2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java @@ -44,8 +44,8 @@ import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; import net.runelite.client.ui.overlay.Overlay; @PluginDescriptor( @@ -57,7 +57,7 @@ public class KourendLibraryPlugin extends Plugin final static boolean debug = false; @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private Client client; @@ -86,19 +86,19 @@ public class KourendLibraryPlugin extends Plugin icon = ImageIO.read(Book.class.getResourceAsStream("panel_icon.png")); } - navButton = new NavigationButton( - "Kourend Library", - icon, - () -> panel - ); + navButton = NavigationButton.builder() + .name("Kourend Library") + .icon(icon) + .panel(panel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); } @Override protected void shutDown() { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPlugin.java index 4e9e3ecd97..0cd89cfa66 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/notes/NotesPlugin.java @@ -24,19 +24,18 @@ */ package net.runelite.client.plugins.notes; +import com.google.common.eventbus.Subscribe; +import com.google.inject.Provides; import java.awt.image.BufferedImage; import javax.imageio.ImageIO; import javax.inject.Inject; - -import com.google.common.eventbus.Subscribe; -import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.events.SessionOpen; import net.runelite.client.config.ConfigManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; -import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.PluginToolbar; @PluginDescriptor( name = "Notes", @@ -46,7 +45,7 @@ import lombok.extern.slf4j.Slf4j; public class NotesPlugin extends Plugin { @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private NotesConfig config; @@ -72,19 +71,19 @@ public class NotesPlugin extends Plugin icon = ImageIO.read(getClass().getResourceAsStream("notes_icon.png")); } - navButton = new NavigationButton( - "Notes", - icon, - () -> panel - ); + navButton = NavigationButton.builder() + .name("Notes") + .icon(icon) + .panel(panel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); } @Override protected void shutDown() { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java index 981803c119..ab776dbb93 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java @@ -25,19 +25,17 @@ package net.runelite.client.plugins.screenshot; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; import com.google.common.eventbus.Subscribe; import com.google.inject.Provides; import java.awt.Color; import java.awt.Desktop; import java.awt.FontMetrics; import java.awt.Graphics; -import java.awt.Point; import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -52,10 +50,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import javax.inject.Inject; -import javax.swing.JButton; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; @@ -80,6 +74,8 @@ import net.runelite.client.plugins.screenshot.imgur.ImageUploadRequest; import net.runelite.client.plugins.screenshot.imgur.ImageUploadResponse; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.TitleToolbar; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.client.util.Text; import net.runelite.http.api.RuneLiteAPI; @@ -125,13 +121,16 @@ public class ScreenshotPlugin extends Plugin @Inject private ClientUI clientUi; + @Inject + private TitleToolbar titleToolbar; + @Inject private OverlayRenderer overlayRenderer; @Inject private ScheduledExecutorService executor; - private JButton titleBarButton; + private NavigationButton titleBarButton; @Provides ScreenshotConfig getConfig(ConfigManager configManager) @@ -141,66 +140,39 @@ public class ScreenshotPlugin extends Plugin @Override protected void startUp() throws Exception - { - addButtonToTitleBar(); - } - - @Override - protected void shutDown() throws Exception - { - removeButtonFromTitlebar(); - } - - private void addButtonToTitleBar() { try { BufferedImage iconImage; - BufferedImage invertedIconImage; synchronized (ImageIO.class) { iconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot.png")); - invertedIconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot_inverted.png")); } - SwingUtilities.invokeLater(() -> - { - titleBarButton = new JButton(); - titleBarButton.setToolTipText("Take screenshot"); - titleBarButton.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) + titleBarButton = NavigationButton.builder() + .tooltip("Take screenshot") + .icon(iconImage) + .onClick(() -> takeScreenshot( + TIME_FORMAT.format(new Date()), + client.getLocalPlayer() != null)) + .popup(ImmutableMap + .builder() + .put("Open screenshot folder...", () -> { - super.mouseClicked(e); - - if (SwingUtilities.isLeftMouseButton(e)) + try { - takeScreenshot(TIME_FORMAT.format(new Date()), client.getLocalPlayer() != null); + Desktop.getDesktop().open(RuneLite.SCREENSHOT_DIR); } - } - }); + catch (IOException ex) + { + log.warn("Error opening screenshot dir", ex); - JPopupMenu popupMenu = new JPopupMenu(); + } + }) + .build()) + .build(); - JMenuItem folderItem = new JMenuItem("Open screenshot folder..."); - folderItem.addActionListener(e -> - { - try - { - Desktop.getDesktop().open(RuneLite.SCREENSHOT_DIR); - } - catch (IOException ex) - { - log.warn("Error opening screenshot directory", ex); - } - }); - popupMenu.add(folderItem); - - titleBarButton.setComponentPopupMenu(popupMenu); - - clientUi.getTitleToolbar().addButton(titleBarButton, iconImage, invertedIconImage); - }); + titleToolbar.addNavigation(titleBarButton); } catch (IOException ex) { @@ -208,12 +180,10 @@ public class ScreenshotPlugin extends Plugin } } - private void removeButtonFromTitlebar() + @Override + protected void shutDown() throws Exception { - SwingUtilities.invokeLater(() -> - { - clientUi.getTitleToolbar().remove(titleBarButton); - }); + titleToolbar.removeNavigation(titleBarButton); } @Subscribe @@ -410,9 +380,8 @@ public class ScreenshotPlugin extends Plugin clientUi.paint(graphics); // Evaluate the position of the game inside the frame - Point gamePoint = SwingUtilities.convertPoint(client.getCanvas(), 0, 0, clientUi); - gameOffsetX = gamePoint.x; - gameOffsetY = gamePoint.y; + gameOffsetX = 6; + gameOffsetY = 0; } // Draw the game onto the screenshot diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java index fcc3a1f54c..6820c2e55d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java @@ -47,8 +47,8 @@ import net.runelite.client.game.SkillIconManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import static net.runelite.client.plugins.xptracker.XpWorldType.NORMAL; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.PluginToolbar; import net.runelite.http.api.worlds.World; import net.runelite.http.api.worlds.WorldClient; import net.runelite.http.api.worlds.WorldResult; @@ -62,7 +62,7 @@ import net.runelite.http.api.xp.XpClient; public class XpTrackerPlugin extends Plugin { @Inject - private ClientUI ui; + private PluginToolbar pluginToolbar; @Inject private Client client; @@ -104,25 +104,27 @@ public class XpTrackerPlugin extends Plugin log.warn("Error looking up worlds list", e); } + xpPanel = new XpPanel(this, client, skillIconManager); + BufferedImage icon; synchronized (ImageIO.class) { icon = ImageIO.read(getClass().getResourceAsStream("xp.png")); } - xpPanel = new XpPanel(this, client, skillIconManager); - navButton = new NavigationButton( - "XP Tracker", - icon, - () -> xpPanel); + navButton = NavigationButton.builder() + .name("XP Tracker") + .icon(icon) + .panel(xpPanel) + .build(); - ui.getPluginToolbar().addNavigation(navButton); + pluginToolbar.addNavigation(navButton); } @Override protected void shutDown() throws Exception { - ui.getPluginToolbar().removeNavigation(navButton); + pluginToolbar.removeNavigation(navButton); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientPluginToolbar.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientPluginToolbar.java new file mode 100644 index 0000000000..88bad70430 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientPluginToolbar.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017-2018, Adam + * Copyright (c) 2018, Tomas Slusny + * 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.Component; +import java.awt.Dimension; +import java.util.HashMap; +import java.util.Map; +import javax.swing.JToolBar; + +/** + * Client plugin toolbar. + */ +public class ClientPluginToolbar extends JToolBar +{ + private static final int TOOLBAR_WIDTH = 36, TOOLBAR_HEIGHT = 503; + private final Map componentMap = new HashMap<>(); + + /** + * Instantiates a new Client plugin toolbar. + */ + ClientPluginToolbar() + { + super(JToolBar.VERTICAL); + setFloatable(false); + setSize(new Dimension(TOOLBAR_WIDTH, TOOLBAR_HEIGHT)); + setMinimumSize(new Dimension(TOOLBAR_WIDTH, TOOLBAR_HEIGHT)); + setPreferredSize(new Dimension(TOOLBAR_WIDTH, TOOLBAR_HEIGHT)); + setMaximumSize(new Dimension(TOOLBAR_WIDTH, Integer.MAX_VALUE)); + } + + public void addComponent(final int index, final NavigationButton button, final Component component) + { + if (componentMap.put(button, component) == null) + { + add(component, index); + revalidate(); + repaint(); + } + } + + public void removeComponent(final NavigationButton button) + { + final Component component = componentMap.remove(button); + + if (component != null) + { + remove(component); + revalidate(); + repaint(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientTitleToolbar.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientTitleToolbar.java new file mode 100644 index 0000000000..1f1e1b4dd9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientTitleToolbar.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018 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.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager2; +import java.util.HashMap; +import java.util.Map; +import javax.swing.JPanel; + +/** + * Client title toolbar component. + */ +public class ClientTitleToolbar extends JPanel +{ + public static final int TITLEBAR_SIZE = 23; + private static final int ITEM_PADDING = 4; + + private final Map componentMap = new HashMap<>(); + + /** + * Instantiates a new Client title toolbar. + */ + public ClientTitleToolbar() + { + // The only other layout manager that would manage it's preferred size without padding + // was the GroupLayout manager, which doesn't work with dynamic layouts like this one. + // Primarily, it would not remove components unless it was immediately repainted. + setLayout(new LayoutManager2() + { + @Override + public void addLayoutComponent(String name, Component comp) + { + } + + @Override + public void addLayoutComponent(Component comp, Object constraints) + { + } + + @Override + public void removeLayoutComponent(Component comp) + { + } + + @Override + public Dimension preferredLayoutSize(Container parent) + { + int width = parent.getComponentCount() * (TITLEBAR_SIZE + ITEM_PADDING); + return new Dimension(width, TITLEBAR_SIZE); + } + + @Override + public Dimension minimumLayoutSize(Container parent) + { + return preferredLayoutSize(parent); + } + + @Override + public Dimension maximumLayoutSize(Container parent) + { + return preferredLayoutSize(parent); + } + + @Override + public float getLayoutAlignmentX(Container target) + { + return 0; + } + + @Override + public float getLayoutAlignmentY(Container target) + { + return 0; + } + + @Override + public void invalidateLayout(Container target) + { + } + + @Override + public void layoutContainer(Container parent) + { + int x = 0; + for (Component c : parent.getComponents()) + { + x += ITEM_PADDING; + int height = c.getPreferredSize().height; + if (height > TITLEBAR_SIZE) + { + height = TITLEBAR_SIZE; + } + c.setBounds(x, (TITLEBAR_SIZE - height) / 2, TITLEBAR_SIZE, height); + x += TITLEBAR_SIZE; + } + } + }); + } + + public void addComponent(NavigationButton button, Component c) + { + if (componentMap.put(button, c) == null) + { + add(c); + revalidate(); + repaint(); + } + } + + public void removeComponent(NavigationButton button) + { + final Component component = componentMap.remove(button); + if (component != null) + { + remove(component); + revalidate(); + repaint(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 64b1ded59a..39d3d40ee8 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -24,40 +24,32 @@ */ package net.runelite.client.ui; +import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import java.applet.Applet; -import java.awt.AWTException; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; -import java.awt.Frame; +import java.awt.Graphics; import java.awt.LayoutManager; -import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.TrayIcon; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.IOException; -import java.util.Enumeration; +import javax.annotation.Nullable; import javax.imageio.ImageIO; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.swing.BoxLayout; +import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; -import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.SwingUtilities; -import javax.swing.ToolTipManager; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; -import javax.swing.plaf.FontUIResource; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; @@ -65,31 +57,29 @@ import net.runelite.api.GameState; import net.runelite.api.events.ConfigChanged; import net.runelite.client.RuneLite; import net.runelite.client.RuneLiteProperties; +import net.runelite.client.config.RuneLiteConfig; +import net.runelite.client.events.ClientUILoaded; +import net.runelite.client.events.PluginToolbarButtonAdded; +import net.runelite.client.events.PluginToolbarButtonRemoved; +import net.runelite.client.events.TitleToolbarButtonAdded; +import net.runelite.client.events.TitleToolbarButtonRemoved; import net.runelite.client.util.OSType; import net.runelite.client.util.OSXUtil; +import net.runelite.client.util.SwingUtil; import org.pushingpixels.substance.api.skin.SubstanceGraphiteLookAndFeel; import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities; +/** + * Client UI. + */ @Slf4j -public class ClientUI extends JFrame +@Singleton +public class ClientUI { private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH; public static final BufferedImage ICON; - @Getter - private TrayIcon trayIcon; - - private final RuneLite runelite; - private final Applet client; - private final RuneLiteProperties properties; - private JPanel navContainer; - private PluginToolbar pluginToolbar; - private PluginPanel pluginPanel; - - @Getter - private TitleToolbar titleToolbar; - static { BufferedImage icon = null; @@ -109,129 +99,39 @@ public class ClientUI extends JFrame ICON = icon; } - public static ClientUI create(RuneLite runelite, RuneLiteProperties properties, Applet client) - { - // Force heavy-weight popups/tooltips. - // Prevents them from being obscured by the game applet. - ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); - JPopupMenu.setDefaultLightWeightPopupEnabled(false); + @Getter + private TrayIcon trayIcon; - // Do not render shadows under popups/tooltips. - // Fixes black boxes under popups that are above the game applet. - System.setProperty("jgoodies.popupDropShadowEnabled", "false"); + private final RuneLite runelite; + private final RuneLiteProperties properties; + private final RuneLiteConfig config; + private final EventBus eventBus; + private Applet client; + private JFrame frame; + private JPanel navContainer; + private PluginPanel pluginPanel; + private ClientPluginToolbar pluginToolbar; + private ClientTitleToolbar titleToolbar; + private JButton currentButton; - // Do not fill in background on repaint. Reduces flickering when - // the applet is resized. - System.setProperty("sun.awt.noerasebackground", "true"); - - // Use substance look and feel - try - { - UIManager.setLookAndFeel(new SubstanceGraphiteLookAndFeel()); - } - catch (UnsupportedLookAndFeelException ex) - { - log.warn("unable to set look and feel", ex); - } - - // Use custom UI font - setUIFont(new FontUIResource(FontManager.getRunescapeFont())); - - ClientUI gui = new ClientUI(runelite, properties, client); - OSXUtil.tryEnableFullscreen(gui); - return gui; - } - - private ClientUI(RuneLite runelite, RuneLiteProperties properties, Applet client) + @Inject + private ClientUI( + RuneLite runelite, + RuneLiteProperties properties, + RuneLiteConfig config, + EventBus eventBus) { this.runelite = runelite; this.properties = properties; - this.client = client; - this.trayIcon = setupTrayIcon(); - - init(); - setTitle(properties.getTitle()); - setIconImage(ICON); - // Prevent substance from using a resize cursor for pointing - getLayeredPane().setCursor(Cursor.getDefaultCursor()); - setLocationRelativeTo(getOwner()); - setResizable(true); - } - - public void showWithChrome(boolean customChrome) - { - setUndecorated(customChrome); - - if (customChrome) - { - getRootPane().setWindowDecorationStyle(JRootPane.FRAME); - - JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(this); - titleToolbar.putClientProperty(SubstanceTitlePaneUtilities.EXTRA_COMPONENT_KIND, SubstanceTitlePaneUtilities.ExtraComponentKind.TRAILING); - titleBar.add(titleToolbar); - - // Substance's default layout manager for the title bar only lays out substance's components - // This wraps the default manager and lays out the TitleToolbar as well. - LayoutManager delegate = titleBar.getLayout(); - titleBar.setLayout(new LayoutManager() - { - @Override - public void addLayoutComponent(String name, Component comp) - { - delegate.addLayoutComponent(name, comp); - } - - @Override - public void removeLayoutComponent(Component comp) - { - delegate.removeLayoutComponent(comp); - } - - @Override - public Dimension preferredLayoutSize(Container parent) - { - return delegate.preferredLayoutSize(parent); - } - - @Override - public Dimension minimumLayoutSize(Container parent) - { - return delegate.minimumLayoutSize(parent); - } - - @Override - public void layoutContainer(Container parent) - { - delegate.layoutContainer(parent); - final int width = titleToolbar.getPreferredSize().width; - titleToolbar.setBounds(titleBar.getWidth() - 75 - width, 0, width, titleBar.getHeight()); - } - }); - } - - pack(); - revalidateMinimumSize(); - setLocationRelativeTo(getOwner()); - - setVisible(true); - toFront(); - requestFocus(); - giveClientFocus(); - } - - private void giveClientFocus() - { - if (client instanceof Client) - { - final Canvas c = ((Client) client).getCanvas(); - c.requestFocusInWindow(); - } - else if (client != null) - { - client.requestFocusInWindow(); - } + this.config = config; + this.eventBus = eventBus; } + /** + * On config changed. + * + * @param event the event + */ @Subscribe public void onConfigChanged(ConfigChanged event) { @@ -240,147 +140,303 @@ public class ClientUI extends JFrame return; } - if (event.getKey().equals("gameAlwaysOnTop")) - { - if (this.isAlwaysOnTopSupported()) - { - this.setAlwaysOnTop(Boolean.valueOf(event.getNewValue())); - } - } - - if (event.getKey().equals("lockWindowSize")) - { - SwingUtilities.invokeLater(() -> setResizable(!Boolean.valueOf(event.getNewValue()))); - } - - if (!event.getKey().equals("gameSize")) - { - return; - } - - if (client == null) - { - return; - } - - String[] splitStr = event.getNewValue().split("x"); - int width = Integer.parseInt(splitStr[0]); - int height = Integer.parseInt(splitStr[1]); - - // The upper bounds are defined by the applet's max size - // The lower bounds are taken care of by ClientPanel's setMinimumSize - - if (width > 7680) - { - width = 7680; - } - - if (height > 2160) - { - height = 2160; - } - - Dimension size = new Dimension(width, height); - SwingUtilities.invokeLater(() -> { + if (event.getKey().equals("gameAlwaysOnTop")) + { + if (frame.isAlwaysOnTopSupported()) + { + frame.setAlwaysOnTop(config.gameAlwaysOnTop()); + } + } + + if (event.getKey().equals("lockWindowSize")) + { + SwingUtilities.invokeLater(() -> frame.setResizable(!config.lockWindowSize())); + } + + if (!event.getKey().equals("gameSize")) + { + return; + } + + if (client == null) + { + return; + } + + int width = config.gameSize().width; + int height = config.gameSize().height; + + // The upper bounds are defined by the applet's max size + // The lower bounds are taken care of by ClientPanel's setMinimumSize + + if (width > 7680) + { + width = 7680; + } + + if (height > 2160) + { + height = 2160; + } + + final Dimension size = new Dimension(width, height); + client.setSize(size); client.setPreferredSize(size); - client.getParent().setPreferredSize(size); client.getParent().setSize(size); - if (isVisible()) + if (frame.isVisible()) { - pack(); + frame.pack(); } }); } - private static void setUIFont(FontUIResource f) + @Subscribe + public void onPluginToolbarButtonAdded(final PluginToolbarButtonAdded event) { - final Enumeration keys = UIManager.getDefaults().keys(); - - while (keys.hasMoreElements()) + SwingUtilities.invokeLater(() -> { - final Object key = keys.nextElement(); - final Object value = UIManager.get(key); - - if (value instanceof FontUIResource) + final JButton button = SwingUtil.createSwingButton(event.getButton(), 0, (jButton) -> { - UIManager.put(key, f); - } - } + final PluginPanel panel = event.getButton().getPanel(); + + if (panel == null) + { + return; + } + + if (currentButton != null) + { + currentButton.setSelected(false); + } + + if (currentButton == jButton) + { + contract(); + currentButton = null; + } + else + { + currentButton = jButton; + currentButton.setSelected(true); + expand(panel); + } + }); + + pluginToolbar.addComponent(event.getIndex(), event.getButton(), button); + }); } - private TrayIcon setupTrayIcon() + @Subscribe + public void onPluginToolbarButtonRemoved(final PluginToolbarButtonRemoved event) { - if (!SystemTray.isSupported()) + SwingUtilities.invokeLater(() -> pluginToolbar.removeComponent(event.getButton())); + } + + @Subscribe + public void onTitleToolbarButtonAdded(final TitleToolbarButtonAdded event) + { + if (!config.enableCustomChrome() && !SwingUtil.isCustomTitlePanePresent(frame)) { - return null; + return; } - SystemTray systemTray = SystemTray.getSystemTray(); - TrayIcon trayIcon = new TrayIcon(ICON, properties.getTitle()); - trayIcon.setImageAutoSize(true); + SwingUtilities.invokeLater(() -> + { + final int iconSize = ClientTitleToolbar.TITLEBAR_SIZE - 6; + final JButton button = SwingUtil.createSwingButton(event.getButton(), iconSize, null); + titleToolbar.addComponent(event.getButton(), button); + }); + } - try + @Subscribe + public void onTitleToolbarButtonRemoved(final TitleToolbarButtonRemoved event) + { + if (!config.enableCustomChrome() && !SwingUtil.isCustomTitlePanePresent(frame)) { - systemTray.add(trayIcon); - } - catch (AWTException ex) - { - log.debug("Unable to add system tray icon", ex); - return trayIcon; + return; } - // bring to front when tray icon is clicked - trayIcon.addMouseListener(new MouseAdapter() + SwingUtilities.invokeLater(() -> titleToolbar.removeComponent(event.getButton())); + } + + /** + * Initialize UI. + * + * @param client the client + * @throws Exception exception that can occur during creation of the UI + */ + public void init(@Nullable final Applet client) throws Exception + { + this.client = client; + + SwingUtilities.invokeAndWait(() -> { - @Override - public void mouseClicked(MouseEvent e) + // Set some sensible swing defaults + SwingUtil.setupDefaults(); + + // Use substance look and feel + SwingUtil.setTheme(new SubstanceGraphiteLookAndFeel()); + + // Use custom UI font + SwingUtil.setFont(FontManager.getRunescapeFont()); + + // Create main window + frame = new JFrame(); + + // Try to enable fullscreen on OSX + OSXUtil.tryEnableFullscreen(frame); + + trayIcon = SwingUtil.createTrayIcon(ICON, properties.getTitle(), frame); + + frame.setTitle(properties.getTitle()); + frame.setIconImage(ICON); + frame.getLayeredPane().setCursor(Cursor.getDefaultCursor()); // Prevent substance from using a resize cursor for pointing + frame.setLocationRelativeTo(frame.getOwner()); + frame.setResizable(true); + + SwingUtil.addGracefulExitCallback(frame, runelite::shutdown, + () -> client != null + && client instanceof Client + && ((Client) client).getGameState() != GameState.LOGIN_SCREEN); + + final JPanel container = new JPanel(); + container.setLayout(new BoxLayout(container, BoxLayout.X_AXIS)); + container.add(new ClientPanel(client)); + + navContainer = new JPanel(); + navContainer.setLayout(new BorderLayout(0, 0)); + navContainer.setMinimumSize(new Dimension(0, 0)); + navContainer.setMaximumSize(new Dimension(0, Integer.MAX_VALUE)); + container.add(navContainer); + + pluginToolbar = new ClientPluginToolbar(); + container.add(pluginToolbar); + + titleToolbar = new ClientTitleToolbar(); + frame.add(container); + }); + } + + /** + * Show client UI after everything else is done. + * + * @throws Exception exception that can occur during modification of the UI + */ + public void show() throws Exception + { + final boolean withTitleBar = config.enableCustomChrome(); + + SwingUtilities.invokeAndWait(() -> + { + frame.setUndecorated(withTitleBar); + + if (withTitleBar) { - setVisible(true); - setState(Frame.NORMAL); // unminimize + frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME); + + final JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(frame); + titleToolbar.putClientProperty(SubstanceTitlePaneUtilities.EXTRA_COMPONENT_KIND, SubstanceTitlePaneUtilities.ExtraComponentKind.TRAILING); + titleBar.add(titleToolbar); + + // Substance's default layout manager for the title bar only lays out substance's components + // This wraps the default manager and lays out the TitleToolbar as well. + LayoutManager delegate = titleBar.getLayout(); + titleBar.setLayout(new LayoutManager() + { + @Override + public void addLayoutComponent(String name, Component comp) + { + delegate.addLayoutComponent(name, comp); + } + + @Override + public void removeLayoutComponent(Component comp) + { + delegate.removeLayoutComponent(comp); + } + + @Override + public Dimension preferredLayoutSize(Container parent) + { + return delegate.preferredLayoutSize(parent); + } + + @Override + public Dimension minimumLayoutSize(Container parent) + { + return delegate.minimumLayoutSize(parent); + } + + @Override + public void layoutContainer(Container parent) + { + delegate.layoutContainer(parent); + final int width = titleToolbar.getPreferredSize().width; + titleToolbar.setBounds(titleBar.getWidth() - 75 - width, 0, width, titleBar.getHeight()); + } + }); } + + frame.pack(); + SwingUtil.revalidateMinimumSize(frame); + frame.setLocationRelativeTo(frame.getOwner()); + frame.setVisible(true); + frame.toFront(); + requestFocus(); + giveClientFocus(); }); - return trayIcon; + eventBus.post(new ClientUILoaded()); } - private void init() + /** + * Paint this component to target graphics + * + * @param graphics the graphics + */ + public void paint(final Graphics graphics) { - assert SwingUtilities.isEventDispatchThread(); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent e) - { - checkExit(); - } - }); - - final JPanel container = new JPanel(); - container.setLayout(new BoxLayout(container, BoxLayout.X_AXIS)); - container.add(new ClientPanel(client)); - - navContainer = new JPanel(); - navContainer.setLayout(new BorderLayout(0, 0)); - navContainer.setMinimumSize(new Dimension(0, 0)); - navContainer.setMaximumSize(new Dimension(0, Integer.MAX_VALUE)); - container.add(navContainer); - - pluginToolbar = new PluginToolbar(this); - container.add(pluginToolbar); - - titleToolbar = new TitleToolbar(properties); - - add(container); + frame.paint(graphics); } - @Override + /** + * Gets component width. + * + * @return the width + */ + public int getWidth() + { + return frame.getWidth(); + } + + /** + * Gets component height. + * + * @return the height + */ + public int getHeight() + { + return frame.getHeight(); + } + + /** + * Returns true if this component has focus. + * + * @return true if component has focus + */ + public boolean isFocused() + { + return frame.isFocused(); + } + + /** + * Request focus on this component and then on client component + */ public void requestFocus() { if (OSType.getOSType() == OSType.MacOS) @@ -388,17 +444,11 @@ public class ClientUI extends JFrame OSXUtil.requestFocus(); } - super.requestFocus(); + frame.requestFocus(); giveClientFocus(); } - private void revalidateMinimumSize() - { - // The JFrame only respects minimumSize if it was set by setMinimumSize, for some reason. (atleast on windows/native) - this.setMinimumSize(this.getLayout().minimumLayoutSize(this)); - } - - void expand(PluginPanel panel) + private void expand(PluginPanel panel) { if (pluginPanel != null) { @@ -406,9 +456,11 @@ public class ClientUI extends JFrame } else { - if (isInScreenBounds((int) getLocationOnScreen().getX() + getWidth() + PANEL_EXPANDED_WIDTH, (int) getLocationOnScreen().getY())) + if (SwingUtil.isInScreenBounds( + frame.getLocationOnScreen().y + frame.getWidth() + PANEL_EXPANDED_WIDTH, + frame.getLocationOnScreen().y)) { - this.setSize(getWidth() + PANEL_EXPANDED_WIDTH, getHeight()); + frame.setSize(frame.getWidth() + PANEL_EXPANDED_WIDTH, frame.getHeight()); } } @@ -425,57 +477,42 @@ public class ClientUI extends JFrame panel.onActivate(); wrappedPanel.repaint(); - revalidateMinimumSize(); + SwingUtil.revalidateMinimumSize(frame); } - void contract() + private void contract() { - boolean wasMinimumWidth = this.getWidth() == (int) this.getMinimumSize().getWidth(); + boolean wasMinimumWidth = frame.getWidth() == frame.getMinimumSize().width; pluginPanel.onDeactivate(); navContainer.remove(0); navContainer.setMinimumSize(new Dimension(0, 0)); - navContainer.setMaximumSize(new Dimension(0, Integer.MAX_VALUE)); + navContainer.setMaximumSize(new Dimension(0, 0)); navContainer.revalidate(); giveClientFocus(); - revalidateMinimumSize(); + SwingUtil.revalidateMinimumSize(frame); + if (wasMinimumWidth) { - this.setSize((int) this.getMinimumSize().getWidth(), getHeight()); + frame.setSize(frame.getMinimumSize().width, frame.getHeight()); } - else if (getWidth() < Toolkit.getDefaultToolkit().getScreenSize().getWidth()) + else if (frame.getWidth() < Toolkit.getDefaultToolkit().getScreenSize().getWidth()) { - this.setSize(getWidth() - PANEL_EXPANDED_WIDTH, getHeight()); + frame.setSize(frame.getWidth() - PANEL_EXPANDED_WIDTH, frame.getHeight()); } pluginPanel = null; } - private boolean isInScreenBounds(int x, int y) + private void giveClientFocus() { - Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); - return x >= 0 && x <= size.getWidth() && y >= 0 && y <= size.getHeight(); - } - - private void checkExit() - { - int result = JOptionPane.OK_OPTION; - - // only ask if not logged out - if (client != null && client instanceof Client && ((Client) client).getGameState() != GameState.LOGIN_SCREEN) + if (client instanceof Client) { - result = JOptionPane.showConfirmDialog(this, "Are you sure you want to exit?", "Exit", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + final Canvas c = ((Client) client).getCanvas(); + c.requestFocusInWindow(); } - - if (result == JOptionPane.OK_OPTION) + else if (client != null) { - runelite.shutdown(); - System.exit(0); + client.requestFocusInWindow(); } } - - public PluginToolbar getPluginToolbar() - { - return pluginToolbar; - } - } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/NavigationButton.java b/runelite-client/src/main/java/net/runelite/client/ui/NavigationButton.java index df6cff5320..18205a89ca 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/NavigationButton.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/NavigationButton.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2017, Adam + * Copyright (c) 2017-2018, Adam + * Copyright (c) 2018, Tomas Slusny * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,30 +25,57 @@ */ package net.runelite.client.ui; -import java.awt.Image; -import java.util.function.Supplier; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; +import java.awt.image.BufferedImage; +import java.util.Map; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; -@Slf4j -public class NavigationButton extends JButton +/** + * UI navigation button. + */ +@Data +@Builder +@EqualsAndHashCode(of = {"name", "tooltip"}) +public class NavigationButton { - @Getter - private final Supplier panelSupplier; + /** + * Button name. + */ + private final String name; - public NavigationButton(String name, Image icon) - { - this(name, icon, null); - } + /** + * Icon of button. + */ + private final BufferedImage icon; - public NavigationButton(String name, Image icon, Supplier panelSupplier) - { - super(); - setName(name); - setToolTipText(name); - setIcon(new ImageIcon(icon)); - this.panelSupplier = panelSupplier; - } + /** + * Tooltip to show when hovered. + */ + private String tooltip; + + /** + * Button selection state + */ + private boolean selected; + + /** + * On select action of the button. + */ + private Runnable onSelect; + + /** + * On click action of the button. + */ + private Runnable onClick; + + /** + * Plugin panel, used when expanding and contracting sidebar. + */ + private PluginPanel panel; + + /** + * Map of key-value pairs for setting the popup menu + */ + private Map popup; } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/PluginToolbar.java b/runelite-client/src/main/java/net/runelite/client/ui/PluginToolbar.java index 134a278a0f..4ef441f7a6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/PluginToolbar.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/PluginToolbar.java @@ -24,87 +24,60 @@ */ package net.runelite.client.ui; -import java.awt.Component; -import java.awt.Dimension; +import com.google.common.eventbus.EventBus; import java.util.Comparator; import java.util.TreeSet; -import java.util.function.Supplier; -import javax.swing.JToolBar; -import lombok.extern.slf4j.Slf4j; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.client.events.PluginToolbarButtonAdded; +import net.runelite.client.events.PluginToolbarButtonRemoved; -@Slf4j -public class PluginToolbar extends JToolBar +/** + * Plugin toolbar buttons holder. + */ +@Singleton +public class PluginToolbar { - public static final int TOOLBAR_WIDTH = 36, TOOLBAR_HEIGHT = 503; + private final EventBus eventBus; + private final TreeSet buttons = new TreeSet<>(Comparator.comparing(NavigationButton::getName)); - private final ClientUI ui; - private final TreeSet buttons = new TreeSet<>(Comparator.comparing(Component::getName)); - - private NavigationButton current; - - public PluginToolbar(ClientUI ui) + @Inject + private PluginToolbar(final EventBus eventBus) { - super(JToolBar.VERTICAL); - this.ui = ui; - - super.setFloatable(false); - super.setSize(new Dimension(TOOLBAR_WIDTH, TOOLBAR_HEIGHT)); - super.setMinimumSize(new Dimension(TOOLBAR_WIDTH, TOOLBAR_HEIGHT)); - super.setPreferredSize(new Dimension(TOOLBAR_WIDTH, TOOLBAR_HEIGHT)); - super.setMaximumSize(new Dimension(TOOLBAR_WIDTH, Integer.MAX_VALUE)); + this.eventBus = eventBus; } - public void addNavigation(NavigationButton button) + /** + * Add navigation. + * + * @param button the button + */ + public void addNavigation(final NavigationButton button) { - button.addActionListener((ae) -> onClick(button)); - button.setToolTipText(button.getName()); - if (buttons.contains(button)) { - log.warn("Button already in container '{}'", button.getName()); return; } - buttons.add(button); + if (buttons.add(button)) + { + int index = buttons.headSet(button).size(); + eventBus.post(new PluginToolbarButtonAdded(button, index)); + } + } + + /** + * Remove navigation. + * + * @param button the button + */ + public void removeNavigation(final NavigationButton button) + { int index = buttons.headSet(button).size(); - add(button, index); - revalidate(); - repaint(); - } - public void removeNavigation(NavigationButton button) - { - buttons.remove(button); - remove(button); - revalidate(); - repaint(); - } - - private void onClick(NavigationButton button) - { - Supplier panelSupplier = button.getPanelSupplier(); - if (panelSupplier == null) + if (buttons.remove(button)) { - return; - } - - if (current != null) - { - current.setSelected(false); - } - - if (current == button) - { - ui.contract(); - current = null; - } - else - { - current = button; - current.setSelected(true); - - PluginPanel pluginPanel = panelSupplier.get(); - ui.expand(pluginPanel); + eventBus.post(new PluginToolbarButtonRemoved(button, index)); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/TitleToolbar.java b/runelite-client/src/main/java/net/runelite/client/ui/TitleToolbar.java index 9a5ffc1be4..80333c2da6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/TitleToolbar.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/TitleToolbar.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018 Abex + * Copyright (c) 2017-2018, Adam + * Copyright (c) 2018, Tomas Slusny * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,174 +25,78 @@ */ package net.runelite.client.ui; -import java.awt.Component; -import java.awt.Container; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.Image; -import java.awt.LayoutManager2; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import javax.imageio.ImageIO; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JPanel; -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.RuneLiteProperties; -import org.pushingpixels.substance.internal.SubstanceSynapse; +import com.google.common.eventbus.EventBus; +import java.util.Comparator; +import java.util.Iterator; +import java.util.TreeSet; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.client.events.TitleToolbarButtonAdded; +import net.runelite.client.events.TitleToolbarButtonRemoved; -@Slf4j -public class TitleToolbar extends JPanel +/** + * Title toolbar buttons holder. + */ +@Singleton +public class TitleToolbar { - private static final int TITLEBAR_SIZE = 23; - private static final int ITEM_PADDING = 4; + private final EventBus eventBus; + private final TreeSet buttons = new TreeSet<>(Comparator.comparing(NavigationButton::getTooltip)); - public TitleToolbar(RuneLiteProperties properties) + @Inject + private TitleToolbar(final EventBus eventBus) { - // The only other layout manager that would manage it's preferred size without padding - // was the GroupLayout manager, which doesn't work with dynamic layouts like this one. - // Primarily, it would not remove components unless it was immediately repainted. - setLayout(new LayoutManager2() + this.eventBus = eventBus; + } + + /** + * Add navigation. + * + * @param button the button + */ + public void addNavigation(final NavigationButton button) + { + if (buttons.contains(button)) { - @Override - public void addLayoutComponent(String name, Component comp) - { - } - - @Override - public void addLayoutComponent(Component comp, Object constraints) - { - } - - @Override - public void removeLayoutComponent(Component comp) - { - } - - @Override - public Dimension preferredLayoutSize(Container parent) - { - int width = parent.getComponentCount() * (TITLEBAR_SIZE + ITEM_PADDING); - return new Dimension(width, TITLEBAR_SIZE); - } - - @Override - public Dimension minimumLayoutSize(Container parent) - { - return preferredLayoutSize(parent); - } - - @Override - public Dimension maximumLayoutSize(Container parent) - { - return preferredLayoutSize(parent); - } - - @Override - public float getLayoutAlignmentX(Container target) - { - return 0; - } - - @Override - public float getLayoutAlignmentY(Container target) - { - return 0; - } - - @Override - public void invalidateLayout(Container target) - { - } - - @Override - public void layoutContainer(Container parent) - { - int x = 0; - for (Component c : parent.getComponents()) - { - x += ITEM_PADDING; - int height = c.getPreferredSize().height; - if (height > TITLEBAR_SIZE) - { - height = TITLEBAR_SIZE; - } - c.setBounds(x, (TITLEBAR_SIZE - height) / 2, TITLEBAR_SIZE, height); - x += TITLEBAR_SIZE; - } - } - }); - - try - { - BufferedImage discordIcon; - BufferedImage invertedIcon; - synchronized (ImageIO.class) - { - discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png")); - invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png")); - } - - JButton discordButton = new JButton(); - discordButton.setToolTipText("Join Discord"); - discordButton.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - super.mouseClicked(e); - try - { - Desktop.getDesktop().browse(new URL(properties.getDiscordInvite()).toURI()); - } - catch (IOException | URISyntaxException ex) - { - log.warn("error opening browser", ex); - } - } - }); - - addButton(discordButton, discordIcon, invertedIcon); + return; } - catch (IOException ex) + + if (buttons.add(button)) { - log.warn("unable to load discord button", ex); + int index = buttons.headSet(button).size(); + eventBus.post(new TitleToolbarButtonAdded(button, index)); } } - public void addButton(JButton button, Image iconImage, Image invertedIconImage) + /** + * Remove navigation. + * + * @param button the button + */ + public void removeNavigation(final NavigationButton button) { - final int iconSize = TITLEBAR_SIZE - 6; - ImageIcon icon = new ImageIcon(iconImage.getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH)); - ImageIcon invertedIcon; - if (invertedIconImage == null) - { - invertedIcon = icon; - } - else - { - invertedIcon = new ImageIcon(invertedIconImage.getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH)); - } + int index = buttons.headSet(button).size(); - button.setIcon(icon); - button.setRolloverIcon(invertedIcon); - button.putClientProperty(SubstanceSynapse.FLAT_LOOK, Boolean.TRUE); - button.setFocusable(false); - - add(button); - revalidate(); - repaint(); + if (buttons.remove(button)) + { + eventBus.post(new TitleToolbarButtonRemoved(button, index)); + } } - @Override - public void remove(Component c) + /** + * Refresh all buttons + */ + public void refresh() { - super.remove(c); - revalidate(); - repaint(); + final Iterator iterator = buttons.iterator(); + int index = 0; + + while (iterator.hasNext()) + { + final NavigationButton button = iterator.next(); + eventBus.post(new TitleToolbarButtonRemoved(button, index)); + eventBus.post(new TitleToolbarButtonAdded(button, index)); + index++; + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java index 0d0d8b7313..6f6444ae9c 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java +++ b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java @@ -30,13 +30,10 @@ import java.awt.datatransfer.StringSelection; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; -import net.runelite.client.ui.ClientUI; /** * Utility class used for browser navigation @@ -45,21 +42,13 @@ import net.runelite.client.ui.ClientUI; @Slf4j public class LinkBrowser { - private final Provider clientUIProvider; - - @Inject - private LinkBrowser(final Provider clientUIProvider) - { - this.clientUIProvider = clientUIProvider; - } - /** * Tries to navigate to specified URL in browser. In case operation fails, displays message box with message * and copies link to clipboard to navigate to. * @param url url to open * @return true if operation was successful */ - public boolean browse(final String url) + public static boolean browse(final String url) { if (!Desktop.isDesktopSupported()) { @@ -93,18 +82,11 @@ public class LinkBrowser * Open swing message box with specified message and copy data to clipboard * @param message message to show */ - private void showMessageBox(final String message, final String data) + private static void showMessageBox(final String message, final String data) { - final ClientUI clientUI = clientUIProvider.get(); - - if (clientUI == null) - { - return; - } - SwingUtilities.invokeLater(() -> { - final int result = JOptionPane.showConfirmDialog(clientUI, message, "Message", + final int result = JOptionPane.showConfirmDialog(null, message, "Message", JOptionPane.OK_CANCEL_OPTION); if (result == JOptionPane.OK_OPTION) diff --git a/runelite-client/src/main/java/net/runelite/client/util/OSXUtil.java b/runelite-client/src/main/java/net/runelite/client/util/OSXUtil.java index 393e57445b..f521349b94 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/OSXUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/OSXUtil.java @@ -26,8 +26,8 @@ package net.runelite.client.util; import com.apple.eawt.Application; import com.apple.eawt.FullScreenUtilities; +import javax.swing.JFrame; import lombok.extern.slf4j.Slf4j; -import net.runelite.client.ui.ClientUI; /** * A class with OSX-specific functions to improve integration. @@ -40,7 +40,7 @@ public class OSXUtil * * @param gui The gui to enable the fullscreen on. */ - public static void tryEnableFullscreen(ClientUI gui) + public static void tryEnableFullscreen(JFrame gui) { if (OSType.getOSType() == OSType.MacOS) { diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java new file mode 100644 index 0000000000..0eaac05395 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.util; + +import java.awt.AWTException; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.awt.TrayIcon; +import java.awt.Window; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.util.Enumeration; +import java.util.concurrent.Callable; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.LookAndFeel; +import javax.swing.ToolTipManager; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE; +import javax.swing.plaf.FontUIResource; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.NavigationButton; +import org.pushingpixels.substance.internal.SubstanceSynapse; +import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; + +/** + * Various Swing utilities. + */ +@Slf4j +public class SwingUtil +{ + /** + * Sets some sensible defaults for swing. + * IMPORTANT! Needs to be called before main frame creation + */ + public static void setupDefaults() + { + // Force heavy-weight popups/tooltips. + // Prevents them from being obscured by the game applet. + ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + + // Do not render shadows under popups/tooltips. + // Fixes black boxes under popups that are above the game applet. + System.setProperty("jgoodies.popupDropShadowEnabled", "false"); + + // Do not fill in background on repaint. Reduces flickering when + // the applet is resized. + System.setProperty("sun.awt.noerasebackground", "true"); + } + + /** + * Safely sets Swing theme + * + * @param laf the swing look and feel + */ + public static void setTheme(@Nonnull final LookAndFeel laf) + { + try + { + UIManager.setLookAndFeel(laf); + } + catch (UnsupportedLookAndFeelException ex) + { + log.warn("Unable to set look and feel", ex); + } + } + + /** + * Sets default Swing font. + * IMPORTANT! Needs to be called before main frame creation + * + * @param font the new font to use + */ + public static void setFont(@Nonnull final Font font) + { + final FontUIResource f = new FontUIResource(font); + final Enumeration keys = UIManager.getDefaults().keys(); + + while (keys.hasMoreElements()) + { + final Object key = keys.nextElement(); + final Object value = UIManager.get(key); + + if (value instanceof FontUIResource) + { + UIManager.put(key, f); + } + } + } + + /** + * Create tray icon. + * + * @param icon the icon + * @param title the title + * @param frame the frame + * @return the tray icon + */ + @Nullable + public static TrayIcon createTrayIcon(@Nonnull final Image icon, @Nonnull final String title, @Nonnull final Frame frame) + { + if (!SystemTray.isSupported()) + { + return null; + } + + final SystemTray systemTray = SystemTray.getSystemTray(); + final TrayIcon trayIcon = new TrayIcon(icon, title); + trayIcon.setImageAutoSize(true); + + try + { + systemTray.add(trayIcon); + } + catch (AWTException ex) + { + log.debug("Unable to add system tray icon", ex); + return trayIcon; + } + + // Bring to front when tray icon is clicked + trayIcon.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + frame.setVisible(true); + frame.setState(Frame.NORMAL); // Restore + } + }); + + return trayIcon; + } + + /** + * Check if point is in screen bounds. + * + * @param x the x + * @param y the y + * @return the boolean + */ + public static boolean isInScreenBounds(final int x, final int y) + { + final Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + final Rectangle bounds = new Rectangle(size); + return bounds.contains(x, y); + } + + /** + * Add graceful exit callback. + * + * @param frame the frame + * @param callback the callback + * @param confirmRequired the confirm required + */ + public static void addGracefulExitCallback(@Nonnull final JFrame frame, @Nonnull final Runnable callback, @Nonnull final Callable confirmRequired) + { + frame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() + { + @Override + public void windowClosing(WindowEvent event) + { + int result = JOptionPane.OK_OPTION; + + try + { + if (confirmRequired.call()) + { + result = JOptionPane.showConfirmDialog( + frame, + "Are you sure you want to exit?", "Exit", + JOptionPane .OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + } + } + catch (Exception e) + { + log.warn("Unexpected exception occurred while check for confirm required", e); + } + + if (result == JOptionPane.OK_OPTION) + { + callback.run(); + System.exit(0); + } + } + }); + } + + /** + * Revalidate minimum frame size. + * + * @param frame the frame + */ + public static void revalidateMinimumSize(final JFrame frame) + { + // The JFrame only respects minimumSize if it was set by setMinimumSize, for some reason. (atleast on windows/native) + frame.setMinimumSize(frame.getLayout().minimumLayoutSize(frame)); + } + + private static BufferedImage resizeImage(BufferedImage image, int newWidth, int newHeight) + { + final Image tmp = image.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + final BufferedImage dimg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + + final Graphics2D g2d = dimg.createGraphics(); + g2d.drawImage(tmp, 0, 0, null); + g2d.dispose(); + return dimg; + } + + /** + * Create swing button from navigation button. + * + * @param navigationButton the navigation button + * @param iconSize the icon size (in case it is 0 default icon size will be used) + * @param specialCallback the special callback + * @return the swing button + */ + public static JButton createSwingButton( + @Nonnull final NavigationButton navigationButton, + int iconSize, + @Nullable final Consumer specialCallback) + { + + final BufferedImage scaledImage = iconSize > 0 + ? resizeImage(navigationButton.getIcon(), iconSize, iconSize) + : navigationButton.getIcon(); + + final JButton button = new JButton(); + button.setName(navigationButton.getName()); + button.setToolTipText(navigationButton.getTooltip()); + button.setIcon(new ImageIcon(scaledImage)); + button.putClientProperty(SubstanceSynapse.FLAT_LOOK, Boolean.TRUE); + button.setFocusable(false); + button.addActionListener(e -> + { + if (specialCallback != null) + { + specialCallback.accept(button); + } + + if (navigationButton.getOnClick() != null) + { + navigationButton.getOnClick().run(); + } + }); + + if (navigationButton.getPopup() != null) + { + final JPopupMenu popupMenu = new JPopupMenu(); + + navigationButton.getPopup().forEach((name, callback) -> + { + final JMenuItem menuItem = new JMenuItem(name); + menuItem.addActionListener((e) -> callback.run()); + popupMenu.add(menuItem); + }); + + button.setComponentPopupMenu(popupMenu); + } + + navigationButton.setOnSelect(() -> button.setSelected(navigationButton.isSelected())); + return button; + } + + /** + * Checks if custom substance title pane is present. + * + * @param frame the parent frame + * @return true if title pane is present + */ + public static boolean isCustomTitlePanePresent(final Window frame) + { + return SubstanceCoreUtilities.getTitlePaneComponent(frame) != null; + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/discord.png b/runelite-client/src/main/resources/net/runelite/client/plugins/discord/discord.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/ui/discord.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/discord/discord.png diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/discord_inverted.png b/runelite-client/src/main/resources/net/runelite/client/ui/discord_inverted.png deleted file mode 100644 index ab5fccaf29..0000000000 Binary files a/runelite-client/src/main/resources/net/runelite/client/ui/discord_inverted.png and /dev/null differ diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java index da87c58bc3..efd2ce9f24 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java @@ -45,7 +45,6 @@ import joptsimple.OptionSet; import net.runelite.api.Client; import net.runelite.client.RuneLite; import net.runelite.client.RuneLiteModule; -import net.runelite.client.ui.ClientUI; import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Rule; @@ -67,9 +66,6 @@ public class PluginManagerTest private RuneLite runelite; private Set pluginClasses; - @Mock - ClientUI clientUi; - @Mock Client client; @@ -83,7 +79,6 @@ public class PluginManagerTest RuneLite.setInjector(injector); runelite = injector.getInstance(RuneLite.class); - runelite.setGui(clientUi); // Find plugins we expect to have pluginClasses = new HashSet<>();