diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 9a08b0ee66..3dd65d26f3 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -30,7 +30,7 @@ import java.util.Map; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; -public interface Client +public interface Client extends GameEngine { List getPlayers(); diff --git a/runelite-api/src/main/java/net/runelite/api/GameEngine.java b/runelite-api/src/main/java/net/runelite/api/GameEngine.java new file mode 100644 index 0000000000..a1c23d9b4f --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/GameEngine.java @@ -0,0 +1,32 @@ +/* + * 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.api; + +import java.awt.Canvas; + +public interface GameEngine +{ + Canvas getCanvas(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java index df386b89fc..c6f0c25d86 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java @@ -24,23 +24,16 @@ */ package net.runelite.client.plugins.config; -import static javax.swing.JOptionPane.WARNING_MESSAGE; -import static javax.swing.JOptionPane.YES_NO_OPTION; -import static javax.swing.JOptionPane.YES_OPTION; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; -import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.ItemEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import javax.swing.JButton; @@ -52,12 +45,18 @@ import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; +import static javax.swing.JOptionPane.WARNING_MESSAGE; +import static javax.swing.JOptionPane.YES_NO_OPTION; +import static javax.swing.JOptionPane.YES_OPTION; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import lombok.extern.slf4j.Slf4j; import net.runelite.client.config.ConfigDescriptor; import net.runelite.client.config.ConfigItem; @@ -72,75 +71,107 @@ public class ConfigPanel extends PluginPanel private static final int SPINNER_FIELD_WIDTH = 6; private final ConfigManager configManager; - private JTextField searchBar; + private final JTextField searchBar = new JTextField(); + private Map children = new TreeMap<>(); + private int scrollBarPosition = 0; public ConfigPanel(ConfigManager configManager) { super(); this.configManager = configManager; - populateConfig(); + + searchBar.getDocument().addDocumentListener(new DocumentListener() + { + @Override + public void insertUpdate(DocumentEvent e) + { + onSearchBarChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) + { + onSearchBarChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) + { + onSearchBarChanged(); + } + }); + + rebuildPluginList(); + openConfigList(); + } + + final void rebuildPluginList() + { + Map newChildren = new TreeMap<>(); + configManager.getConfigProxies().stream() + .map(configManager::getConfigDescriptor) + .filter(configDescriptor -> configDescriptor.getItems().stream() + .anyMatch(cid -> !cid.getItem().hidden())) + .forEach(cd -> + { + String groupName = cd.getGroup().name(); + if (children.containsKey(groupName)) + { + newChildren.put(groupName, children.get(groupName)); + return; + } + JPanel groupPanel = new JPanel(); + groupPanel.setLayout(new BorderLayout()); + JButton viewGroupItemsButton = new JButton(groupName); + viewGroupItemsButton.addActionListener(ae -> openGroupConfigPanel(cd, configManager)); + groupPanel.add(viewGroupItemsButton); + newChildren.put(groupName, groupPanel); + }); + children = newChildren; + openConfigList(); + } + + private void onSearchBarChanged() + { + children.forEach((key, value) -> + { + final String text = searchBar.getText().toLowerCase(); + final String labelToSearch = key.toLowerCase(); + + if (text.isEmpty() || labelToSearch.contains(text)) + { + add(value); + } + else + { + remove(value); + } + }); + + revalidate(); } @Override - public void invalidate() + public void onActivate() { - super.invalidate(); - - if (searchBar != null) + super.onActivate(); + if (searchBar.getParent() != null) { searchBar.requestFocusInWindow(); } } - private void populateConfig() + private void openConfigList() { removeAll(); add(new JLabel("Plugin Configuration", SwingConstants.CENTER)); - searchBar = new JTextField(); add(searchBar); - final Map children = new TreeMap<>(); - configManager.getConfigProxies().stream() - .map(configManager::getConfigDescriptor) - .filter(configDescriptor -> configDescriptor.getItems().stream() - .anyMatch(cid -> !cid.getItem().hidden())) - .sorted(Comparator.comparing(left -> left.getGroup().name())) - .forEach(cd -> - { - JPanel groupPanel = new JPanel(); - groupPanel.setLayout(new BorderLayout()); - JButton viewGroupItemsButton = new JButton(cd.getGroup().name()); - viewGroupItemsButton.addActionListener(ae -> openGroupConfigPanel(cd, configManager)); - groupPanel.add(viewGroupItemsButton); - children.put(cd.getGroup().name(), groupPanel); - add(groupPanel); - }); - - searchBar.addKeyListener(new KeyAdapter() - { - @Override - public void keyTyped(KeyEvent e) - { - children.forEach((key, value) -> - { - final String text = searchBar.getText().toLowerCase(); - final String labelToSearch = key.toLowerCase(); - - if (text.isEmpty() || labelToSearch.contains(text)) - { - add(value); - } - else - { - remove(value); - } - }); - - revalidate(); - } - }); - - revalidate(); + onSearchBarChanged(); + searchBar.requestFocusInWindow(); + JScrollPane scrollbar = getScrollPane(); + scrollbar.validate(); + scrollbar.getVerticalScrollBar().setValue(scrollBarPosition); } private void changeConfiguration(JComponent component, ConfigDescriptor cd, ConfigItemDescriptor cid) @@ -156,7 +187,7 @@ public class ConfigPanel extends PluginPanel { int value = JOptionPane.showOptionDialog(component, configItem.confirmationWarining(), "Are you sure?", YES_NO_OPTION, WARNING_MESSAGE, - null, new String[] { "Yes", "No" }, "No"); + null, new String[]{"Yes", "No"}, "No"); if (value != YES_OPTION) { checkbox.setSelected(originalState); @@ -194,6 +225,7 @@ public class ConfigPanel extends PluginPanel private void openGroupConfigPanel(ConfigDescriptor cd, ConfigManager configManager) { + scrollBarPosition = getScrollPane().getVerticalScrollBar().getValue(); removeAll(); String name = cd.getGroup().name() + " Configuration"; JLabel title = new JLabel(name); @@ -319,15 +351,9 @@ public class ConfigPanel extends PluginPanel } JButton backButton = new JButton("Back"); - backButton.addActionListener(this::getBackButtonListener); + backButton.addActionListener(e -> openConfigList()); add(backButton); revalidate(); + getScrollPane().getVerticalScrollBar().setValue(0); } - - public void getBackButtonListener(ActionEvent e) - { - - populateConfig(); - } - } 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 9a675b4d07..e5f0c7f8ee 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 @@ -24,9 +24,12 @@ */ package net.runelite.client.plugins.config; +import com.google.common.eventbus.Subscribe; import javax.imageio.ImageIO; import javax.inject.Inject; +import javax.swing.SwingUtilities; import net.runelite.client.config.ConfigManager; +import net.runelite.client.events.PluginChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.ClientUI; @@ -44,15 +47,18 @@ public class ConfigPlugin extends Plugin @Inject ConfigManager configManager; + private ConfigPanel configPanel; private NavigationButton navButton; @Override protected void startUp() throws Exception { + configPanel = new ConfigPanel(configManager); + navButton = new NavigationButton( "Configuration", ImageIO.read(getClass().getResourceAsStream("config_icon.png")), - () -> new ConfigPanel(configManager)); + () -> configPanel); ui.getPluginToolbar().addNavigation(navButton); } @@ -62,4 +68,10 @@ public class ConfigPlugin extends Plugin { ui.getPluginToolbar().removeNavigation(navButton); } + + @Subscribe + public void onPluginChanged(PluginChanged event) + { + SwingUtilities.invokeLater(configPanel::rebuildPluginList); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java index 13cdf6e9f8..023b65f7b3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java @@ -326,6 +326,13 @@ public class HiscorePanel extends PluginPanel details.setText(text); } + @Override + public void onActivate() + { + super.onActivate(); + input.requestFocusInWindow(); + } + private JPanel makeSkillPanel(String skillName, HiscoreSkill skill) { JLabel label = new JLabel(); 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 3fa22dc23c..7756517b04 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 @@ -28,6 +28,7 @@ import com.google.common.base.Strings; import java.applet.Applet; import java.awt.AWTException; import java.awt.BorderLayout; +import java.awt.Canvas; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Frame; @@ -46,9 +47,7 @@ import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; import javax.swing.JRootPane; -import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.UIManager; @@ -65,8 +64,7 @@ import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI; @Slf4j public class ClientUI extends JFrame { - private static final int SCROLLBAR_WIDTH = 17; - private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + SCROLLBAR_WIDTH; + private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH; private static final BufferedImage ICON; @Getter @@ -74,7 +72,6 @@ public class ClientUI extends JFrame private final Applet client; private final RuneLiteProperties properties; - private JPanel container; private JPanel navContainer; private PluginToolbar pluginToolbar; private PluginPanel pluginPanel; @@ -158,6 +155,21 @@ public class ClientUI extends JFrame setVisible(true); toFront(); + requestFocus(); + giveClientFocus(); + } + + private void giveClientFocus() + { + if (client instanceof Client) + { + final Canvas c = ((Client) client).getCanvas(); + c.requestFocusInWindow(); + } + else + { + client.requestFocusInWindow(); + } } private static void setUIFont(FontUIResource f) @@ -211,7 +223,6 @@ public class ClientUI extends JFrame return trayIcon; } - @Override public void setTitle(String extra) { @@ -239,14 +250,14 @@ public class ClientUI extends JFrame } }); - container = new JPanel(); + 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)); + 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); @@ -271,18 +282,28 @@ public class ClientUI extends JFrame pluginPanel = panel; navContainer.setMinimumSize(new Dimension(PANEL_EXPANDED_WIDTH, 0)); navContainer.setMaximumSize(new Dimension(PANEL_EXPANDED_WIDTH, Integer.MAX_VALUE)); - navContainer.add(wrapPanel(pluginPanel)); + + final JPanel wrappedPanel = panel.getWrappedPanel(); + navContainer.add(wrappedPanel); navContainer.revalidate(); + + // panel.onActivate has to go after giveClientFocus so it can get focus if it needs. + giveClientFocus(); + panel.onActivate(); + + wrappedPanel.repaint(); revalidateMinimumSize(); } void contract() { boolean wasMinimumWidth = this.getWidth() == (int) this.getMinimumSize().getWidth(); + pluginPanel.onDeactivate(); navContainer.remove(0); navContainer.setMinimumSize(new Dimension(0, 0)); navContainer.setMaximumSize(new Dimension(0, Integer.MAX_VALUE)); navContainer.revalidate(); + giveClientFocus(); revalidateMinimumSize(); if (wasMinimumWidth) { @@ -291,35 +312,12 @@ public class ClientUI extends JFrame pluginPanel = null; } - private JPanel wrapPanel(PluginPanel panel) - { - final JPanel northPanel = new JPanel(); - northPanel.setLayout(new BorderLayout()); - northPanel.add(panel, BorderLayout.NORTH); - - final JScrollPane scrollPane = new JScrollPane(northPanel); - scrollPane.getVerticalScrollBar().setUnitIncrement(16); //Otherwise scrollspeed is really slow - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - - final JPanel panelWrap = new JPanel(); - - // Adjust the preferred size to expand to width of scrollbar to - // to preven scrollbar overlapping over contents - panelWrap.setPreferredSize(new Dimension( - PluginPanel.PANEL_WIDTH + SCROLLBAR_WIDTH, - 0)); - - panelWrap.setLayout(new BorderLayout()); - panelWrap.add(scrollPane, BorderLayout.CENTER); - return panelWrap; - } - 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 != null && client instanceof Client && ((Client) client).getGameState() != GameState.LOGIN_SCREEN) { result = JOptionPane.showConfirmDialog(this, "Are you sure you want to exit?", "Exit", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java b/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java index 3d4399dbd9..aa30a440cc 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/PluginPanel.java @@ -24,22 +24,50 @@ */ package net.runelite.client.ui; +import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; import javax.swing.border.EmptyBorder; +import lombok.AccessLevel; +import lombok.Getter; public abstract class PluginPanel extends JPanel { - public static final int PANEL_WIDTH = 225; + static final int PANEL_WIDTH = 225; + static final int SCROLLBAR_WIDTH = 17; private static final int OFFSET = 6; private static final EmptyBorder BORDER_PADDING = new EmptyBorder(OFFSET, OFFSET, OFFSET, OFFSET); + @Getter(AccessLevel.PROTECTED) + private final JScrollPane scrollPane; + + @Getter(AccessLevel.PACKAGE) + private final JPanel wrappedPanel; + public PluginPanel() { super(); setBorder(BORDER_PADDING); setLayout(new GridLayout(0, 1, 0, 3)); + + final JPanel northPanel = new JPanel(); + northPanel.setLayout(new BorderLayout()); + northPanel.add(this, BorderLayout.NORTH); + + scrollPane = new JScrollPane(northPanel); + scrollPane.getVerticalScrollBar().setUnitIncrement(16); //Otherwise scrollspeed is really slow + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + wrappedPanel = new JPanel(); + + // Adjust the preferred size to expand to width of scrollbar to + // to preven scrollbar overlapping over contents + wrappedPanel.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH + SCROLLBAR_WIDTH, 0)); + wrappedPanel.setLayout(new BorderLayout()); + wrappedPanel.add(scrollPane, BorderLayout.CENTER); } @Override @@ -47,4 +75,12 @@ public abstract class PluginPanel extends JPanel { return new Dimension(PANEL_WIDTH, super.getPreferredSize().height); } + + public void onActivate() + { + } + + public void onDeactivate() + { + } } 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 810137d78c..6dd3e9944c 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 @@ -88,12 +88,11 @@ public class PluginToolbar extends JToolBar } else { + current = button; + current.setSelected(true); PluginPanel pluginPanel = panelSupplier.get(); ui.expand(pluginPanel); - - current = button; - current.setSelected(true); } } }